feature: online activation

This commit is contained in:
Gary Sharp
2024-12-27 14:28:56 +11:00
parent 39ba206831
commit b6dfaa3445
35 changed files with 2287 additions and 346 deletions
+44 -3
View File
@@ -242,9 +242,34 @@ namespace Disco.Data.Configuration
}
else if (itemType.BaseType != null && itemType.BaseType == typeof(Enum))
{
// Enum
// enum
itemValue = Enum.Parse(typeof(T), item.Item1.Value);
}
else if (itemType.IsGenericType &&
itemType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
IsConvertableFromString(Nullable.GetUnderlyingType(itemType)))
{
// nullable
itemValue = (T)Convert.ChangeType(item.Item1.Value, Nullable.GetUnderlyingType(itemType));
}
else if (itemType == typeof(Guid))
{
// guid
itemValue = new Guid(item.Item1.Value);
}
else if (itemType == typeof(Guid?))
{
// guid
if (string.IsNullOrEmpty(item.Item1.Value))
itemValue = null;
else
itemValue = new Guid(item.Item1.Value);
}
else if (itemType == typeof(byte[]))
{
// byte[]
itemValue = Convert.FromBase64String(item.Item1.Value);
}
else
{
// JSON Deserialize
@@ -269,7 +294,7 @@ namespace Disco.Data.Configuration
}
else if (valueType == typeof(object))
{
throw new ArgumentException(string.Format("Cannot serialize the configuration item [{0}].[{1}] which defines a type of [System.Object]", Scope, Key), "Value");
throw new ArgumentException($"Cannot serialize the configuration item [{Scope}].[{Key}] which has the type [System.Object]", "Value");
}
else if (IsConvertableFromString(valueType))
{
@@ -278,9 +303,25 @@ namespace Disco.Data.Configuration
}
else if (valueType.BaseType != null && valueType.BaseType == typeof(Enum))
{
// Enum
// enum
stringValue = Value.ToString();
}
else if (valueType.IsGenericType &&
valueType.GetGenericTypeDefinition() == typeof(Nullable<>) &&
IsConvertableFromString(Nullable.GetUnderlyingType(valueType)))
{
// nullable
stringValue = Value.ToString();
}
else if (valueType == typeof(Guid) || valueType == typeof(Guid?))
{
stringValue = Value.ToString();
}
else if (Value is byte[] valueBytes)
{
// byte[]
stringValue = Convert.ToBase64String(valueBytes);
}
else
{
// JSON Serialize
@@ -319,6 +319,29 @@ namespace Disco.Data.Configuration
#endregion
#region UpdateCheck
public bool IsActivated => ActivationId.HasValue;
public DateTime? ActivatedOn
{
get => Get((DateTime?)null);
set => Set(value);
}
public string ActivatedBy
{
get => Get((string)null);
set => Set(value);
}
public Guid? ActivationId
{
get => Get((Guid?)null);
set => Set(value);
}
public byte[] ActivationKey
{
get => Get((byte[])null);
set => Set(value);
}
public bool IsLicensed
{
get => LicenseKey != null && LicenseExpiresOn != null && LicenseExpiresOn > DateTime.UtcNow && LicenseError == null;
@@ -10,6 +10,10 @@ namespace Disco.Data.Migrations
AlterColumn("dbo.UserAttachments", "Comments", c => c.String(maxLength: 500));
AlterColumn("dbo.JobAttachments", "Comments", c => c.String(maxLength: 500));
AlterColumn("dbo.DeviceAttachments", "Comments", c => c.String(maxLength: 500));
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='System' AND [Key] IN ('ActivatedOn', 'ActivationId', 'LicenseExpiresOn')");
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='DocFill' AND [Key] IN ('LicenseExpiresOn')");
Sql("DELETE [dbo].[Configuration] WHERE [Scope]='JobPreferences' AND [Key] IN ('LastExportDate')");
}
public override void Down()
+2
View File
@@ -80,6 +80,8 @@
<Compile Include="Services\Documents\DocumentTemplatePackage.cs" />
<Compile Include="Services\Documents\OnImportUserFlagRule.cs" />
<Compile Include="Exporting\IExportOptions.cs" />
<Compile Include="Services\Interop\DiscoServices\Activation\CallbackModel.cs" />
<Compile Include="Services\Interop\DiscoServices\Activation\ChallengeModel.cs" />
<Compile Include="Services\Jobs\Exporting\JobExportOptions.cs" />
<Compile Include="Services\Jobs\Exporting\JobExportRecord.cs" />
<Compile Include="Services\Jobs\LocationModes.cs" />
@@ -0,0 +1,11 @@
using System;
namespace Disco.Models.Services.Interop.DiscoServices.Activation
{
public class CallbackModel
{
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
public string User { get; set; }
}
}
@@ -0,0 +1,15 @@
using System;
namespace Disco.Models.Services.Interop.DiscoServices.Activation
{
public class ChallengeModel
{
public byte[] Key { get; set; }
public Guid ActivationId { get; set; }
public string UserId { get; set; }
public long TimeStamp { get; set; }
public byte[] ChallengeResponse { get; set; }
public byte[] ChallengeResponseIv { get; set; }
public string RedirectUrl { get; set; }
}
}
+8 -1
View File
@@ -374,9 +374,12 @@
<Compile Include="Interop\ActiveDirectory\ADUserAccountControlFlags.cs" />
<Compile Include="Interop\ActiveDirectory\Description.cs" />
<Compile Include="Interop\ActiveDirectory\IADObject.cs" />
<Compile Include="Interop\DiscoServices\ActivationCleanupTask.cs" />
<Compile Include="Interop\DiscoServices\ActivationService.cs" />
<Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" />
<Compile Include="Interop\DiscoServices\Jobs.cs" />
<Compile Include="Interop\DiscoServices\LicenseValidationTask.cs" />
<Compile Include="Interop\DiscoServices\OnlineServicesAuthentication.cs" />
<Compile Include="Interop\DiscoServices\PluginLibrary.cs" />
<Compile Include="Interop\DiscoServices\PluginLibraryUpdateTask.cs" />
<Compile Include="Interop\IIS\PreserveIisBindingsTask.cs" />
@@ -536,6 +539,9 @@
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Claims.cs</LastGenOutput>
</None>
<EmbeddedResource Include="Interop\DiscoServices\Activation.key">
<LogicalName>DiscoIct.OnlineServices.Activation.key</LogicalName>
</EmbeddedResource>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
@@ -564,7 +570,8 @@
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"
</PostBuildEvent>
</PropertyGroup>
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
@@ -123,7 +123,7 @@ namespace Disco
{
var epoc = new DateTime(unixEpocOffset, DateTimeKind.Utc);
var offset = d.ToUniversalTime() - epoc;
return offset.Ticks / 10000;
return offset.Ticks / TimeSpan.TicksPerMillisecond;
}
public static long? ToUnixEpoc(this DateTime? d)
{
@@ -132,6 +132,11 @@ namespace Disco
else
return null;
}
public static DateTime FromUnixEpoc(this long d)
{
var epoc = new DateTime(unixEpocOffset, DateTimeKind.Utc);
return epoc.AddMilliseconds(d);
}
public static string ToISO8601(this DateTime d)
{
Binary file not shown.
@@ -0,0 +1,42 @@
using Disco.Data.Repository;
using Disco.Services.Tasks;
using Quartz;
using System;
namespace Disco.Services.Interop.DiscoServices
{
public class ActivationCleanupTask : ScheduledTask
{
public override string TaskName { get { return "Activation Cleanup"; } }
public override bool LogExceptionsOnly => true;
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
var service = new ActivationService(Database);
if (service.RequiresCleanup)
{
// Trigger in 1 + 0-29 minutes
var rng = new Random();
var delay = rng.Next(30) + 1;
TriggerBuilder triggerBuilder = TriggerBuilder.Create()
.StartAt(DateTimeOffset.Now.AddMinutes(delay));
ScheduleTask(triggerBuilder);
base.InitalizeScheduledTask(Database);
}
}
protected override void ExecuteTask()
{
using (var database = new DiscoDataContext())
{
var service = new ActivationService(database);
service.CleanupExpiredActivations();
}
}
}
}
@@ -0,0 +1,434 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Models.Services.Interop.DiscoServices.Activation;
using Disco.Services.Interop.VicEduDept;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Disco.Services.Interop.DiscoServices
{
public class ActivationService
{
private static readonly byte[] onlineServicesActivationKey;
internal const string baseUrl = "https://activate.discoict.com.au";
private readonly DiscoDataContext database;
static ActivationService()
{
using (var resourceStream = typeof(ActivationService).Assembly.GetManifestResourceStream("DiscoIct.OnlineServices.Activation.key"))
{
var key = new byte[resourceStream.Length];
resourceStream.Read(key, 0, key.Length);
onlineServicesActivationKey = key;
}
}
public ActivationService(DiscoDataContext database)
{
this.database = database;
}
public string GetDataStoreLocation => Path.Combine(database.DiscoConfiguration.DataStoreLocation, "Activations");
public bool RequiresCleanup => Directory.Exists(GetDataStoreLocation);
public string GetCallbackUrl()
=> $"{baseUrl}/api/callback";
/// <summary>
/// Begin the activation process
/// </summary>
/// <returns>A redirect URL where the user can attempt activation</returns>
public async Task<ChallengeModel> BeginActivation(User user, string completeUrl, string finalUrl)
{
if (database.DiscoConfiguration.IsActivated)
throw new InvalidOperationException("Deployment is already activated");
// generate activation key
var (privateKey, publicKey) = GenerateActivationKey();
// get machine ip addresses
var networkInterfaces = GetMachineIpAddresses();
var vicSchool = VicSmart.WhoAmI();
// get challenge
ChallengeResponse challenge;
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(baseUrl);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var body = new ChallengeRequest()
{
DeploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId),
DeploymentVersion = typeof(ActivationService).Assembly.GetName().Version.ToString(4),
OrganisationName = database.DiscoConfiguration.OrganisationName,
PublicKey = publicKey,
UserId = user.UserId,
UserName = user.DisplayName,
UserEmail = user.EmailAddress,
CompleteUrl = completeUrl,
DeploymentIpAddresses = string.Join(";", networkInterfaces),
VicGovSchoolId = vicSchool?.Item1,
VicGovSchoolName = vicSchool?.Item2,
};
var requestJson = JsonConvert.SerializeObject(body);
using (var request = new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes(requestJson)))
{
request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync("/api/challenge", request);
response.EnsureSuccessStatusCode();
var responseJson = await response.Content.ReadAsStringAsync();
challenge = JsonConvert.DeserializeObject<ChallengeResponse>(responseJson);
}
}
// validate signature
if (!ValidateSignature(challenge))
throw new InvalidOperationException("Invalid challenge signature");
// decrypt challenge token
var token = Decrypt(privateKey, challenge.ChallengeIv, challenge.TimeStamp, challenge.Challenge);
if (token.Length != 32)
throw new InvalidOperationException("Unexpected challenge length");
// invert token
for (int i = 0; i < token.Length; i++)
token[i] = (byte)~token[i];
var challengeResponseIv = new byte[16];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(challengeResponseIv);
// encrypt token
var challengeResponse = Encrypt(privateKey, challengeResponseIv, challenge.TimeStamp, token);
if (challengeResponse.Length != 48)
throw new InvalidOperationException("Unexpected challenge response length");
var result = new ChallengeModel
{
Key = privateKey,
ActivationId = challenge.ActivationId,
UserId = user.UserId,
TimeStamp = challenge.TimeStamp,
ChallengeResponse = challengeResponse,
ChallengeResponseIv = challengeResponseIv,
RedirectUrl = $"{baseUrl}/",
};
// store activation
var datastore = GetDataStoreLocation;
if (!Directory.Exists(datastore))
Directory.CreateDirectory(datastore);
var resultJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result));
var protectedResult = ProtectedData.Protect(resultJson, challenge.ActivationId.ToByteArray(), DataProtectionScope.LocalMachine);
var datastoreFile = Path.Combine(datastore, $"{challenge.ActivationId:N}.bin");
File.WriteAllBytes(datastoreFile, protectedResult);
return result;
}
public async Task CompleteActivation(Guid activationId, byte[] challenge, byte[] challengeIv, byte[] signature)
{
if (database.DiscoConfiguration.IsActivated)
throw new InvalidOperationException("Deployment is already activated");
// validate signature
if (!ValidateSignature(activationId, challenge, challengeIv, signature))
throw new InvalidOperationException("Invalid signature");
// load activation
var datastoreFile = Path.Combine(GetDataStoreLocation, $"{activationId:N}.bin");
if (!File.Exists(datastoreFile))
throw new InvalidOperationException("Activation not found");
var protectedActivation = File.ReadAllBytes(datastoreFile);
var activationJson = ProtectedData.Unprotect(protectedActivation, activationId.ToByteArray(), DataProtectionScope.LocalMachine);
var activation = JsonConvert.DeserializeObject<ChallengeModel>(Encoding.UTF8.GetString(activationJson));
// decrypt challenge token
var token = Decrypt(activation.Key, challengeIv, activation.TimeStamp, challenge);
if (token.Length != 32)
throw new InvalidOperationException("Unexpected challenge length");
// reverse token
Array.Reverse(token);
var challengeResponseIv = new byte[16];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(challengeResponseIv);
// encrypt token
var challengeResponse = Encrypt(activation.Key, challengeResponseIv, activation.TimeStamp, token);
if (challengeResponse.Length != 48)
throw new InvalidOperationException("Unexpected challenge response length");
var responseSignature = Sign(activation.Key, activationId, challengeResponse, challengeResponseIv);
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(baseUrl);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var body = new CompleteRequest()
{
DeploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId),
ActivationId = activationId,
ChallengeResponse = challengeResponse,
ChallengeResponseIv = challengeResponseIv,
Signature = responseSignature,
};
var requestJson = JsonConvert.SerializeObject(body);
using (var request = new ByteArrayContent(Encoding.UTF8.GetBytes(requestJson)))
{
request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync("/api/complete", request);
response.EnsureSuccessStatusCode();
database.DiscoConfiguration.ActivationId = activationId;
database.DiscoConfiguration.ActivatedOn = DateTime.Now;
database.DiscoConfiguration.ActivatedBy = activation.UserId;
database.DiscoConfiguration.ActivationKey = activation.Key;
database.SaveChanges();
OnlineServicesAuthentication.UpdateActivation(database);
}
}
}
public void CleanupExpiredActivations()
{
var dataStore = GetDataStoreLocation;
if (Directory.Exists(dataStore))
{
if (database.DiscoConfiguration.IsActivated)
{
Directory.Delete(dataStore, true);
}
else
{
var threshold = DateTime.Now.AddDays(-14);
foreach (var file in Directory.EnumerateFiles(dataStore, "*.bin"))
{
if (File.GetCreationTime(file) < threshold)
File.Delete(file);
}
if (!Directory.EnumerateFileSystemEntries(dataStore).Any())
Directory.Delete(dataStore);
}
}
}
private static bool ValidateSignature(ChallengeResponse response)
{
var stream = new MemoryStream();
stream.Write(response.ActivationId.ToByteArray(), 0, 16);
stream.Write(BitConverter.GetBytes(response.TimeStamp), 0, 8);
stream.Write(response.Challenge, 0, response.Challenge.Length);
stream.Write(response.ChallengeIv, 0, response.ChallengeIv.Length);
return ValidateSignature(stream.ToArray(), response.Signature);
}
private static bool ValidateSignature(Guid activationId, byte[] challenge, byte[] challengeIv, byte[] signature)
{
var stream = new MemoryStream();
stream.Write(activationId.ToByteArray(), 0, 16);
stream.Write(challenge, 0, challenge.Length);
stream.Write(challengeIv, 0, challengeIv.Length);
return ValidateSignature(stream.ToArray(), signature);
}
private static bool ValidateSignature(byte[] bytes, byte[] signature)
{
byte[] hash;
using (var hasher = SHA256.Create())
{
hash = hasher.ComputeHash(bytes);
}
using (var serverKey = CngKey.Import(onlineServicesActivationKey, CngKeyBlobFormat.EccPublicBlob))
{
using (var ecdsa = new ECDsaCng(serverKey))
{
ecdsa.HashAlgorithm = CngAlgorithm.Sha256;
return ecdsa.VerifyHash(hash, signature);
}
}
}
private static byte[] Sign(byte[] privateKey, Guid activationId, byte[] challengeResponse, byte[] challengeResponseIv)
{
using (var stream = new MemoryStream())
{
stream.Write(activationId.ToByteArray(), 0, 16);
stream.Write(challengeResponse, 0, challengeResponse.Length);
stream.Write(challengeResponseIv, 0, challengeResponseIv.Length);
byte[] hash;
using (var hasher = SHA256.Create())
{
hash = hasher.ComputeHash(stream.ToArray());
return SignHash(privateKey, hash);
}
}
}
internal static byte[] SignHash(byte[] privateKey, byte[] hash)
{
using (var key = CngKey.Import(privateKey, CngKeyBlobFormat.EccPrivateBlob))
{
using (var ecdsa = new ECDsaCng(key))
{
ecdsa.HashAlgorithm = CngAlgorithm.Sha256;
return ecdsa.SignHash(hash);
}
}
}
private static byte[] Encrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{
var key = DeriveEncryptionKey(privateKey, iv, timeStamp);
using (var aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var encryptor = aes.CreateEncryptor())
{
var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
}
return ms.ToArray();
}
}
}
private static byte[] Decrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{
var key = DeriveEncryptionKey(privateKey, iv, timeStamp);
using (var aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var decryptor = aes.CreateDecryptor())
{
var ms = new MemoryStream(data);
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
var output = new MemoryStream();
cs.CopyTo(output);
return output.ToArray();
}
}
}
}
private static byte[] DeriveEncryptionKey(byte[] privateKey, byte[] iv, long timeStamp)
{
using (var serverKey = CngKey.Import(onlineServicesActivationKey, CngKeyBlobFormat.EccPublicBlob))
{
using (var clientKey = CngKey.Import(privateKey, CngKeyBlobFormat.EccPrivateBlob))
{
using (var serverEcdh = new ECDiffieHellmanCng(serverKey))
{
using (var ecdh = new ECDiffieHellmanCng(clientKey))
{
return ecdh.DeriveKeyFromHash(serverEcdh.PublicKey, HashAlgorithmName.SHA256, iv, BitConverter.GetBytes(timeStamp));
}
}
}
}
}
private static (byte[] privateKey, byte[] publicKey) GenerateActivationKey()
{
using (var key = CngKey.Create(CngAlgorithm.ECDiffieHellmanP521, null, new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyUsage = CngKeyUsages.KeyAgreement,
}))
{
var privateKey = key.Export(CngKeyBlobFormat.EccPrivateBlob);
var publicKey = key.Export(CngKeyBlobFormat.EccPublicBlob);
return (privateKey, publicKey);
}
}
private static List<string> GetMachineIpAddresses()
{
var ipAddresses = new List<string>();
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
{
foreach (var address in networkInterface.GetIPProperties().UnicastAddresses)
{
if (address.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
ipAddresses.Add(address.Address.ToString());
}
}
return ipAddresses;
}
private class ChallengeRequest
{
public Guid DeploymentId { get; set; }
[MaxLength(20)]
public string DeploymentVersion { get; set; }
[MaxLength(200)]
public string OrganisationName { get; set; }
[MaxLength(158)]
public byte[] PublicKey { get; set; }
[StringLength(50)]
public string UserId { get; set; }
[StringLength(150)]
public string UserName { get; set; }
[StringLength(150)]
public string UserEmail { get; set; }
[StringLength(200)]
public string CompleteUrl { get; set; }
[StringLength(200)]
public string DeploymentIpAddresses { get; set; }
public string VicGovSchoolId { get; set; }
[StringLength(150)]
public string VicGovSchoolName { get; set; }
}
private class ChallengeResponse
{
public Guid ActivationId { get; set; }
public long TimeStamp { get; set; }
public byte[] Challenge { get; set; }
public byte[] ChallengeIv { get; set; }
public byte[] Signature { get; set; }
}
private class CompleteRequest
{
public Guid DeploymentId { get; set; }
public Guid ActivationId { get; set; }
public byte[] ChallengeResponse { get; set; }
public byte[] ChallengeResponseIv { get; set; }
public byte[] Signature { get; set; }
}
}
}
@@ -0,0 +1,167 @@
using Disco.Data.Repository;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.Services.Interop.DiscoServices
{
public static class OnlineServicesAuthentication
{
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
private static readonly Guid deploymentId;
private static Guid? activationId;
private static byte[] key;
private static string token;
private static DateTime? tokenExpires;
static OnlineServicesAuthentication()
{
using (var database = new DiscoDataContext())
{
deploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId);
UpdateActivation(database);
}
}
public static bool IsActivated => activationId.HasValue;
public static string GetToken()
=> GetTokenAsync().Result;
public async static Task<string> GetTokenAsync()
{
var localExpires = tokenExpires;
var localToken = token;
if (tokenExpires != null && tokenExpires.Value > DateTime.UtcNow && localToken != null)
return localToken;
if (!IsActivated)
throw new InvalidOperationException("Not activated");
await semaphore.WaitAsync();
try
{
localExpires = tokenExpires;
localToken = token;
if (tokenExpires != null && tokenExpires.Value < DateTime.UtcNow && localToken != null)
return localToken;
if (!IsActivated)
throw new InvalidOperationException("Not activated");
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new Uri(ActivationService.baseUrl);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var timeStamp = DateTime.UtcNow.ToUnixEpoc();
var iv = new byte[32];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(iv);
var dataStream = new MemoryStream(16 + 16 + 8 + iv.Length);
dataStream.Write(deploymentId.ToByteArray(), 0, 16);
dataStream.Write(activationId.Value.ToByteArray(), 0, 16);
dataStream.Write(BitConverter.GetBytes(timeStamp), 0, 8);
dataStream.Write(iv, 0, iv.Length);
byte[] hash;
using (var hasher = SHA256.Create())
hash = hasher.ComputeHash(dataStream.ToArray());
var signature = ActivationService.SignHash(key, hash);
var body = new AuthenticationRequest()
{
DeploymentId = deploymentId,
ActivationId = activationId.Value,
TimeStamp = timeStamp,
IV = iv,
Signature = signature,
};
var requestJson = JsonConvert.SerializeObject(body);
using (var request = new ByteArrayContent(Encoding.UTF8.GetBytes(requestJson)))
{
request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync("/api/authenticate", request);
if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.BadRequest)
{
var responseJson = await response.Content.ReadAsStringAsync();
var authResponse = JsonConvert.DeserializeObject<AuthenticationResponse>(responseJson);
if (authResponse == null)
throw new InvalidOperationException("Failed to authenticate (empty response)");
if (!authResponse.Success)
throw new InvalidOperationException($"Failed to authenticate ({authResponse.ErrorMessage})");
token = authResponse.Token;
tokenExpires = DateTimeExtensions.FromUnixEpoc(authResponse.Expiry.Value).AddMinutes(-5);
return token;
}
else
throw new InvalidOperationException($"Failed to authenticate ({response.StatusCode} {response.ReasonPhrase})");
}
}
}
finally
{
semaphore.Release();
}
}
internal static void UpdateActivation(DiscoDataContext database)
{
semaphore.Wait();
try
{
var config = database.DiscoConfiguration;
if (config.IsActivated)
{
activationId = config.ActivationId;
key = config.ActivationKey;
token = null;
tokenExpires = null;
}
else
{
activationId = null;
key = null;
token = null;
tokenExpires = null;
}
}
finally
{
semaphore.Release();
}
}
private class AuthenticationRequest
{
public Guid DeploymentId { get; set; }
public Guid ActivationId { get; set; }
public long TimeStamp { get; set; }
public byte[] IV { get; set; }
public byte[] Signature { get; set; }
}
private class AuthenticationResponse
{
public bool Success { get; set; }
public string Token { get; set; }
public long? Expiry { get; set; }
public string ErrorMessage { get; set; }
}
}
}
@@ -0,0 +1,54 @@
using Disco.Services.Authorization;
using Disco.Services.Interop.DiscoServices;
using Disco.Services.Web;
using Disco.Web.Areas.API.Models.Activation;
using System;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace Disco.Web.Areas.API.Controllers
{
[DiscoAuthorize(Claims.DiscoAdminAccount)]
public partial class ActivationController : AuthorizedDatabaseController
{
[HttpPost]
public virtual ActionResult TestCallback(CallbackModel model)
{
return this.PrecompiledPartialView<API.Views.Activation._ActivateCallback>(model);
}
[HttpPost, ValidateAntiForgeryToken]
public virtual async Task<ActionResult> Begin()
{
var service = new ActivationService(Database);
var challengeModel = await service.BeginActivation(CurrentUser, Url.ActionAbsolute(MVC.API.Activation.Complete()), Url.ActionAbsolute(MVC.Config.SystemConfig.Index()));
var model = new BeginModel()
{
ActivationId = challengeModel.ActivationId,
ChallengeResponse = Convert.ToBase64String(challengeModel.ChallengeResponse),
ChallengeResponseIv = Convert.ToBase64String(challengeModel.ChallengeResponseIv),
RedirectUrl = challengeModel.RedirectUrl
};
return View(model);
}
[HttpGet]
public virtual async Task<ActionResult> Complete(Guid activationId, string challenge, string challengeIv, string signature)
{
var service = new ActivationService(Database);
var challengeBytes = Convert.FromBase64String(challenge.Replace('-', '+').Replace('_', '/'));
var challengeIvBytes = Convert.FromBase64String(challengeIv.Replace('-', '+').Replace('_', '/'));
var signatureBytes = Convert.FromBase64String(signature.Replace('-', '+').Replace('_', '/'));
await service.CompleteActivation(activationId, challengeBytes, challengeIvBytes, signatureBytes);
return RedirectToAction(MVC.Config.SystemConfig.Index());
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Web.Areas.API.Models.Activation
{
public class BeginModel
{
public Guid ActivationId { get; set; }
public string ChallengeResponse { get; set; }
public string ChallengeResponseIv { get; set; }
public string RedirectUrl { get; set; }
}
}
@@ -0,0 +1,13 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Disco.Web.Areas.API.Models.Activation
{
public class CallbackModel
{
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
[StringLength(50)]
public string UserId { get; set; }
}
}
@@ -0,0 +1,21 @@
@model Disco.Web.Areas.API.Models.Activation.BeginModel
@{
Authorization.Require(Claims.DiscoAdminAccount);
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "System", MVC.Config.SystemConfig.Index(), "Activate");
}
<div style="min-height: 300px;">
<div class="form" style="width: 500px;">
<h2><i class="fa fa-lg fa-cog fa-spin"></i> Redirecting to Disco ICT Online Services</h2>
</div>
</div>
<form id="activateRedirect" action="@Model.RedirectUrl" method="post">
<input type="hidden" name="ActivationId" value="@Model.ActivationId" />
<input type="hidden" name="ChallengeResponse" value="@Model.ChallengeResponse" />
<input type="hidden" name="ChallengeResponseIv" value="@Model.ChallengeResponseIv" />
</form>
<script>
$('#activateRedirect').trigger('submit');
</script>
@@ -0,0 +1,140 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Disco.Web.Areas.API.Views.Activation
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using Disco;
using Disco.Models.Repository;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Web;
using Disco.Web;
using Disco.Web.Extensions;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Areas/API/Views/Activation/Begin.cshtml")]
public partial class Begin : Disco.Services.Web.WebViewPage<Disco.Web.Areas.API.Models.Activation.BeginModel>
{
public Begin()
{
}
public override void Execute()
{
#line 2 "..\..\Areas\API\Views\Activation\Begin.cshtml"
Authorization.Require(Claims.DiscoAdminAccount);
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "System", MVC.Config.SystemConfig.Index(), "Activate");
#line default
#line hidden
WriteLiteral("\r\n\r\n<div");
WriteLiteral(" style=\"min-height: 300px;\"");
WriteLiteral(">\r\n <div");
WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 500px;\"");
WriteLiteral(">\r\n <h2><i");
WriteLiteral(" class=\"fa fa-lg fa-cog fa-spin\"");
WriteLiteral("></i> Redirecting to Disco ICT Online Services</h2>\r\n </div>\r\n</div>\r\n\r\n<form");
WriteLiteral(" id=\"activateRedirect\"");
WriteAttribute("action", Tuple.Create(" action=\"", 489), Tuple.Create("\"", 516)
#line 14 "..\..\Areas\API\Views\Activation\Begin.cshtml"
, Tuple.Create(Tuple.Create("", 498), Tuple.Create<System.Object, System.Int32>(Model.RedirectUrl
#line default
#line hidden
, 498), false)
);
WriteLiteral(" method=\"post\"");
WriteLiteral(">\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"ActivationId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 578), Tuple.Create("\"", 605)
#line 15 "..\..\Areas\API\Views\Activation\Begin.cshtml"
, Tuple.Create(Tuple.Create("", 586), Tuple.Create<System.Object, System.Int32>(Model.ActivationId
#line default
#line hidden
, 586), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"ChallengeResponse\"");
WriteAttribute("value", Tuple.Create(" value=\"", 660), Tuple.Create("\"", 692)
#line 16 "..\..\Areas\API\Views\Activation\Begin.cshtml"
, Tuple.Create(Tuple.Create("", 668), Tuple.Create<System.Object, System.Int32>(Model.ChallengeResponse
#line default
#line hidden
, 668), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"ChallengeResponseIv\"");
WriteAttribute("value", Tuple.Create(" value=\"", 749), Tuple.Create("\"", 783)
#line 17 "..\..\Areas\API\Views\Activation\Begin.cshtml"
, Tuple.Create(Tuple.Create("", 757), Tuple.Create<System.Object, System.Int32>(Model.ChallengeResponseIv
#line default
#line hidden
, 757), false)
);
WriteLiteral(" />\r\n</form>\r\n<script>\r\n $(\'#activateRedirect\').trigger(\'submit\');\r\n</script>\r" +
"\n");
}
}
}
#pragma warning restore 1591
@@ -0,0 +1,17 @@
@model Disco.Web.Areas.API.Models.Activation.CallbackModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body data-deploymentid="@Model.DeploymentId" data-correlationid="@Model.CorrelationId" data-userid="@Model.UserId">
<script>
const deploymentId = document.body.dataset.deploymentid;
const correlationId = document.body.dataset.correlationid;
const userId = document.body.dataset.userid;
window.parent.activateCallbackResponse(deploymentId, correlationId, userId);
</script>
</body>
</html>
@@ -0,0 +1,107 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Disco.Web.Areas.API.Views.Activation
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using Disco;
using Disco.Models.Repository;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Web;
using Disco.Web;
using Disco.Web.Extensions;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Areas/API/Views/Activation/_ActivateCallback.cshtml")]
public partial class _ActivateCallback : Disco.Services.Web.WebViewPage<Disco.Web.Areas.API.Models.Activation.CallbackModel>
{
public _ActivateCallback()
{
}
public override void Execute()
{
#line 2 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Layout = null;
#line default
#line hidden
WriteLiteral("\r\n<!DOCTYPE html>\r\n<html");
WriteLiteral(" lang=\"en\"");
WriteLiteral(">\r\n<head>\r\n</head>\r\n<body");
WriteLiteral(" data-deploymentid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.DeploymentId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-correlationid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.CorrelationId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-userid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.UserId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(@">
<script>
const deploymentId = document.body.dataset.deploymentid;
const correlationId = document.body.dataset.correlationid;
const userId = document.body.dataset.userid;
window.parent.activateCallbackResponse(deploymentId, correlationId, userId);
</script>
</body>
</html>
");
}
}
}
#pragma warning restore 1591
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="Disco.Services.Web.WebViewPage">
<namespaces>
<add namespace="Disco" />
<add namespace="Disco.Models.Repository" />
<add namespace="Disco.Services" />
<add namespace="Disco.Services.Authorization" />
<add namespace="Disco.Services.Web" />
<add namespace="Disco.Web" />
<add namespace="Disco.Web.Extensions" />
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</assemblies>
</compilation>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<controls>
<add assembly="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
</configuration>
@@ -0,0 +1,4 @@
@{
Layout = MVC.Shared.Views._Layout;
ViewContext.ViewData["MenuArea"] = MVC.Config.Name;
}
@@ -0,0 +1,60 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Disco.Web.Areas.API.Views
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using Disco;
using Disco.Models.Repository;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Web;
using Disco.Web;
using Disco.Web.Extensions;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Areas/API/Views/_ViewStart.cshtml")]
public partial class _ViewStart : System.Web.Mvc.ViewStartPage
{
public _ViewStart()
{
}
public override void Execute()
{
#line 1 "..\..\Areas\API\Views\_ViewStart.cshtml"
Layout = MVC.Shared.Views._Layout;
ViewContext.ViewData["MenuArea"] = MVC.Config.Name;
#line default
#line hidden
WriteLiteral("\r\n");
}
}
}
#pragma warning restore 1591
@@ -1,5 +1,8 @@
using Disco.Services.Authorization;
using Disco.Services.Interop.DiscoServices;
using Disco.Services.Web;
using Disco.Web.Areas.Config.Models.SystemConfig;
using System;
using System.Web.Mvc;
namespace Disco.Web.Areas.Config.Controllers
@@ -9,8 +12,27 @@ namespace Disco.Web.Areas.Config.Controllers
[DiscoAuthorize(Claims.Config.System.Show), HttpGet]
public virtual ActionResult Index()
{
var m = Models.SystemConfig.IndexModel.FromConfiguration(Database.DiscoConfiguration);
var m = IndexModel.FromConfiguration(Database.DiscoConfiguration);
return View(m);
}
[DiscoAuthorize(Claims.DiscoAdminAccount), HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Activate()
{
if (Database.DiscoConfiguration.IsActivated)
return RedirectToAction(MVC.Config.SystemConfig.Index());
var service = new ActivationService(Database);
var model = new ActivateModel()
{
CallbackUrl = service.GetCallbackUrl(),
DeploymentId = Guid.Parse(Database.DiscoConfiguration.DeploymentId),
CorrelationId = Guid.NewGuid(),
UserId = CurrentUser.UserId,
};
return View(model);
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Web.Areas.Config.Models.SystemConfig
{
public class ActivateModel
{
public string CallbackUrl { get; set; }
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
public string UserId { get; set; }
}
}
@@ -117,6 +117,10 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
public DateTime? LicenseExpires { get; set; }
public string LicenseError { get; set; }
public bool IsActivated { get; set; }
public DateTime? ActivatedOn { get; set; }
public string ActivatedBy { get; set; }
public ScheduledTaskStatus UpdateRunningStatus { get; set; }
public DateTime? UpdateNextScheduled { get; set; }
public UpdateResponseV2 UpdateLatestResponse { get; set; }
@@ -145,6 +149,9 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
LicenseExpires = config.LicenseExpiresOn,
LicenseError = config.LicenseError,
LicenseValidationRunningStatus = LicenseValidationTask.RunningStatus,
IsActivated = config.IsActivated,
ActivatedBy = config.ActivatedBy,
ActivatedOn = config.ActivatedOn,
UpdateLatestResponse = config.UpdateLastCheckResponse,
UpdateRunningStatus = UpdateQueryTask.RunningStatus,
UpdateNextScheduled = UpdateQueryTask.NextScheduled,
@@ -0,0 +1,55 @@
@model Disco.Web.Areas.Config.Models.SystemConfig.ActivateModel
@{
Authorization.Require(Claims.DiscoAdminAccount);
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "System", MVC.Config.SystemConfig.Index(), "Activate");
}
<div style="min-height: 300px;">
<div class="form" style="width: 500px;">
<h2><i class="fa fa-lg fa-cog fa-spin"></i> Testing Connectivity to Disco ICT Online Services</h2>
</div>
</div>
<iframe name="callbackFrame" class="hidden">
</iframe>
<form id="callbackSubmit" action="@Model.CallbackUrl" method="post" target="callbackFrame">
<input type="hidden" name="callbackUrl" value="@(new Uri(Request.Url, Url.Action(MVC.API.Activation.TestCallback())))" />
<input type="hidden" name="deploymentId" value="@Model.DeploymentId" />
<input type="hidden" name="correlationId" value="@Model.CorrelationId" />
<input type="hidden" name="userId" value="@Model.UserId" />
</form>
@using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
@Html.AntiForgeryToken()
}
<a id="callbackFailedUrl" href="@Url.Action(MVC.Config.SystemConfig.Index())" class="hidden"></a>
<script>
$(function () {
const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href');
const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}, 1000 * 35);
window.activateCallbackResponse = function (deploymentId, correlationId, userId) {
window.clearTimeout(timeout);
const originalDeploymentId = callbackForm.find('input[name="deploymentId"]').val();
const originalCorrelationId = callbackForm.find('input[name="correlationId"]').val();
const originalUserId = callbackForm.find('input[name="userId"]').val();
if (deploymentId !== originalDeploymentId || correlationId !== originalCorrelationId || userId !== originalUserId) {
alert('Invalid response when communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}
$('#activationBegin').trigger('submit');
};
callbackForm.trigger('submit');
});
</script>
@@ -0,0 +1,232 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Disco.Web.Areas.Config.Views.SystemConfig
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using Disco;
using Disco.Models.Repository;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Web;
using Disco.Web;
using Disco.Web.Extensions;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Areas/Config/Views/SystemConfig/Activate.cshtml")]
public partial class Activate : Disco.Services.Web.WebViewPage<Disco.Web.Areas.Config.Models.SystemConfig.ActivateModel>
{
public Activate()
{
}
public override void Execute()
{
#line 2 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
Authorization.Require(Claims.DiscoAdminAccount);
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "System", MVC.Config.SystemConfig.Index(), "Activate");
#line default
#line hidden
WriteLiteral("\r\n\r\n<div");
WriteLiteral(" style=\"min-height: 300px;\"");
WriteLiteral(">\r\n <div");
WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 500px;\"");
WriteLiteral(">\r\n <h2><i");
WriteLiteral(" class=\"fa fa-lg fa-cog fa-spin\"");
WriteLiteral("></i> Testing Connectivity to Disco ICT Online Services</h2>\r\n </div>\r\n</div>\r" +
"\n\r\n<iframe");
WriteLiteral(" name=\"callbackFrame\"");
WriteLiteral(" class=\"hidden\"");
WriteLiteral(">\r\n</iframe>\r\n\r\n<form");
WriteLiteral(" id=\"callbackSubmit\"");
WriteAttribute("action", Tuple.Create(" action=\"", 563), Tuple.Create("\"", 590)
#line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 572), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line default
#line hidden
, 572), false)
);
WriteLiteral(" method=\"post\"");
WriteLiteral(" target=\"callbackFrame\"");
WriteLiteral(">\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"callbackUrl\"");
WriteAttribute("value", Tuple.Create(" value=\"", 674), Tuple.Create("\"", 752)
#line 18 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 682), Tuple.Create<System.Object, System.Int32>(new Uri(Request.Url, Url.Action(MVC.API.Activation.TestCallback()))
#line default
#line hidden
, 682), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"deploymentId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 802), Tuple.Create("\"", 829)
#line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 810), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line default
#line hidden
, 810), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"correlationId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 880), Tuple.Create("\"", 908)
#line 20 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 888), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line default
#line hidden
, 888), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"userId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 952), Tuple.Create("\"", 973)
#line 21 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 960), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line default
#line hidden
, 960), false)
);
WriteLiteral(" />\r\n</form>\r\n\r\n");
#line 24 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
#line default
#line hidden
#line 26 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
Write(Html.AntiForgeryToken());
#line default
#line hidden
#line 26 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
}
#line default
#line hidden
WriteLiteral("\r\n<a");
WriteLiteral(" id=\"callbackFailedUrl\"");
WriteAttribute("href", Tuple.Create(" href=\"", 1154), Tuple.Create("\"", 1205)
#line 29 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 1161), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.SystemConfig.Index())
#line default
#line hidden
, 1161), false)
);
WriteLiteral(" class=\"hidden\"");
WriteLiteral(@"></a>
<script>
$(function () {
const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href');
const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}, 1000 * 35);
window.activateCallbackResponse = function (deploymentId, correlationId, userId) {
window.clearTimeout(timeout);
const originalDeploymentId = callbackForm.find('input[name=""deploymentId""]').val();
const originalCorrelationId = callbackForm.find('input[name=""correlationId""]').val();
const originalUserId = callbackForm.find('input[name=""userId""]').val();
if (deploymentId !== originalDeploymentId || correlationId !== originalCorrelationId || userId !== originalUserId) {
alert('Invalid response when communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}
$('#activationBegin').trigger('submit');
};
callbackForm.trigger('submit');
});
</script>
");
}
}
}
#pragma warning restore 1591
@@ -70,9 +70,32 @@
</table>
</div>
<div class="form" style="width: 450px; margin-top: 15px;">
<h2>License</h2>
<h2>Activation &amp; License</h2>
<table>
@{
<tr>
<th style="width: 135px">
Activation:
</th>
<td>
@if (Model.IsActivated)
{
@CommonHelpers.FriendlyDate(Model.ActivatedOn) <span>by @Model.ActivatedBy</span>
}
else
{
using (Html.BeginForm(MVC.Config.SystemConfig.Activate(), FormMethod.Post))
{
@Html.AntiForgeryToken();
<button type="submit" class="button small">Activate Now</button>
}
<div class="info-box">
<p class="fa-p">
<i class="fa fa-info-circle"></i>Activating Disco ICT with Online Services unlocks additional functionality.
</p>
</div>
}
</td>
</tr>
<tr>
<th style="width: 135px">
License:
@@ -110,7 +133,7 @@
</script>
</td>
</tr>
if (Model.License != null)
@if (Model.License != null)
{
<tr>
<th style="width: 135px">
@@ -146,7 +169,6 @@
</td>
</tr>
}
}
</table>
</div>
<div class="form" style="width: 450px; margin-top: 15px;">
File diff suppressed because it is too large Load Diff
@@ -53,6 +53,8 @@ namespace Disco.Web.Areas.Config.Views
#line default
#line hidden
WriteLiteral("\r\n");
}
}
}
+44
View File
@@ -205,6 +205,7 @@
<DependentUpon>CommonHelpers.cshtml</DependentUpon>
</Compile>
<Compile Include="App_Start\OwinStartupConfig.cs" />
<Compile Include="Areas\API\Controllers\ActivationController.cs" />
<Compile Include="Areas\API\Controllers\AuthorizationRoleController.cs" />
<Compile Include="Areas\API\Controllers\DocumentTemplatePackageController.cs" />
<Compile Include="Areas\API\Controllers\DeviceFlagAssignmentController.cs" />
@@ -217,6 +218,8 @@
<Compile Include="Areas\API\Controllers\JobQueueJobController.cs" />
<Compile Include="Areas\API\Controllers\PluginController.cs" />
<Compile Include="Areas\API\Controllers\SearchController.cs" />
<Compile Include="Areas\API\Models\Activation\BeginModel.cs" />
<Compile Include="Areas\API\Models\Activation\CallbackModel.cs" />
<Compile Include="Areas\API\Models\DeviceModel\TestComputerNameTemplateModel.cs" />
<Compile Include="Areas\API\Models\DocumentTemplate\AddOnImportUserFlagRuleModel.cs" />
<Compile Include="Areas\API\Models\DocumentTemplate\BulkGenerateUserModel.cs" />
@@ -224,6 +227,11 @@
<Compile Include="Areas\API\Models\Job\DeviceHeldLocationModel.cs" />
<Compile Include="Areas\API\Models\Shared\SubjectDescriptorModel.cs" />
<Compile Include="Areas\API\Models\System\DomainOrganisationalUnitsModel.cs" />
<Compile Include="Areas\API\Views\_ViewStart.generated.cs">
<DependentUpon>_ViewStart.cshtml</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Areas\Config\Controllers\AuthorizationRoleController.cs" />
<Compile Include="Areas\Config\Controllers\DeviceFlagController.cs" />
<Compile Include="Areas\Config\Controllers\UserFlagController.cs" />
@@ -242,6 +250,7 @@
<Compile Include="Areas\Config\Models\DeviceFlag\ExportModel.cs" />
<Compile Include="Areas\Config\Models\DeviceFlag\IndexModel.cs" />
<Compile Include="Areas\Config\Models\DeviceFlag\ShowModel.cs" />
<Compile Include="Areas\Config\Models\SystemConfig\ActivateModel.cs" />
<Compile Include="Areas\Config\Models\UserFlag\CreateModel.cs" />
<Compile Include="Areas\Config\Models\UserFlag\ExportModel.cs" />
<Compile Include="Areas\Config\Models\UserFlag\IndexModel.cs" />
@@ -377,6 +386,21 @@
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Areas\Config\Views\SystemConfig\Activate.generated.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Activate.cshtml</DependentUpon>
</Compile>
<Compile Include="Areas\API\Views\Activation\Begin.generated.cs">
<DependentUpon>Begin.cshtml</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Areas\API\Views\Activation\_ActivateCallback.generated.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>_ActivateCallback.cshtml</DependentUpon>
</Compile>
<Compile Include="Areas\Config\Views\UserFlag\Create.generated.cs">
<DependentUpon>Create.cshtml</DependentUpon>
<AutoGen>True</AutoGen>
@@ -675,6 +699,9 @@
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Include="Extensions\T4MVC\API.ActivationController.generated.cs">
<DependentUpon>T4MVC.tt</DependentUpon>
</Compile>
<Compile Include="Extensions\T4MVC\API.DeviceFlagAssignmentController.generated.cs">
<DependentUpon>T4MVC.tt</DependentUpon>
</Compile>
@@ -1299,6 +1326,11 @@
<Content Include="ClientBin\Disco.ClientBootstrapper.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Areas\API\Views\_ViewStart.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>_ViewStart.generated.cs</LastGenOutput>
</None>
<None Include="Areas\API\Views\Web.config" />
<None Include="Areas\Config\Views\AuthorizationRole\Create.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>Create.generated.cs</LastGenOutput>
@@ -1395,6 +1427,18 @@
<Generator>RazorGenerator</Generator>
<LastGenOutput>Show.generated.cs</LastGenOutput>
</None>
<None Include="Areas\Config\Views\SystemConfig\Activate.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>Activate.generated.cs</LastGenOutput>
</None>
<None Include="Areas\API\Views\Activation\Begin.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>Begin.generated.cs</LastGenOutput>
</None>
<None Include="Areas\API\Views\Activation\_ActivateCallback.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>_ActivateCallback.generated.cs</LastGenOutput>
</None>
<None Include="Areas\Config\Views\UserFlag\Create.cshtml">
<Generator>RazorGenerator</Generator>
<LastGenOutput>Create.generated.cs</LastGenOutput>
@@ -0,0 +1,189 @@
// <auto-generated />
// This file was generated by a T4 template.
// Don't change it directly as your change would get overwritten. Instead, make changes
// to the .tt file (i.e. the T4 template) and save it to regenerate this file.
// Make sure the compiler doesn't complain about missing Xml comments and CLS compliance
// 0108: suppress "Foo hides inherited member Foo. Use the new keyword if hiding was intended." when a controller and its abstract parent are both processed
// 0114: suppress "Foo.BarController.Baz()' hides inherited member 'Qux.BarController.Baz()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." when an action (with an argument) overrides an action in a parent controller
#pragma warning disable 1591, 3008, 3009, 0108, 0114
#region T4MVC
using System;
using System.Diagnostics;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using T4MVC;
namespace Disco.Web.Areas.API.Controllers
{
public partial class ActivationController
{
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActivationController() { }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected ActivationController(Dummy d) { }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected RedirectToRouteResult RedirectToAction(ActionResult result)
{
var callInfo = result.GetT4MVCResult();
return RedirectToRoute(callInfo.RouteValueDictionary);
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected RedirectToRouteResult RedirectToAction(Task<ActionResult> taskResult)
{
return RedirectToAction(taskResult.Result);
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected RedirectToRouteResult RedirectToActionPermanent(ActionResult result)
{
var callInfo = result.GetT4MVCResult();
return RedirectToRoutePermanent(callInfo.RouteValueDictionary);
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected RedirectToRouteResult RedirectToActionPermanent(Task<ActionResult> taskResult)
{
return RedirectToActionPermanent(taskResult.Result);
}
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult TestCallback()
{
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.TestCallback);
}
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Threading.Tasks.Task<System.Web.Mvc.ActionResult> Complete()
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Complete);
return System.Threading.Tasks.Task.FromResult(callInfo as System.Web.Mvc.ActionResult);
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActivationController Actions { get { return MVC.API.Activation; } }
[GeneratedCode("T4MVC", "2.0")]
public readonly string Area = "API";
[GeneratedCode("T4MVC", "2.0")]
public readonly string Name = "Activation";
[GeneratedCode("T4MVC", "2.0")]
public const string NameConst = "Activation";
[GeneratedCode("T4MVC", "2.0")]
static readonly ActionNamesClass s_actions = new ActionNamesClass();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionNamesClass ActionNames { get { return s_actions; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionNamesClass
{
public readonly string TestCallback = "TestCallback";
public readonly string Begin = "Begin";
public readonly string Complete = "Complete";
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionNameConstants
{
public const string TestCallback = "TestCallback";
public const string Begin = "Begin";
public const string Complete = "Complete";
}
static readonly ActionParamsClass_TestCallback s_params_TestCallback = new ActionParamsClass_TestCallback();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_TestCallback TestCallbackParams { get { return s_params_TestCallback; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_TestCallback
{
public readonly string model = "model";
}
static readonly ActionParamsClass_Complete s_params_Complete = new ActionParamsClass_Complete();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_Complete CompleteParams { get { return s_params_Complete; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_Complete
{
public readonly string activationId = "activationId";
public readonly string challenge = "challenge";
public readonly string challengeIv = "challengeIv";
public readonly string signature = "signature";
}
static readonly ViewsClass s_views = new ViewsClass();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ViewsClass Views { get { return s_views; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ViewsClass
{
static readonly _ViewNamesClass s_ViewNames = new _ViewNamesClass();
public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
public class _ViewNamesClass
{
public readonly string _ActivateCallback = "_ActivateCallback";
public readonly string Begin = "Begin";
}
public readonly string _ActivateCallback = "~/Areas/API/Views/Activation/_ActivateCallback.cshtml";
public readonly string Begin = "~/Areas/API/Views/Activation/Begin.cshtml";
}
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public partial class T4MVC_ActivationController : Disco.Web.Areas.API.Controllers.ActivationController
{
public T4MVC_ActivationController() : base(Dummy.Instance) { }
[NonAction]
partial void TestCallbackOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, Disco.Web.Areas.API.Models.Activation.CallbackModel model);
[NonAction]
public override System.Web.Mvc.ActionResult TestCallback(Disco.Web.Areas.API.Models.Activation.CallbackModel model)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.TestCallback);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "model", model);
TestCallbackOverride(callInfo, model);
return callInfo;
}
[NonAction]
partial void BeginOverride(T4MVC_System_Web_Mvc_ActionResult callInfo);
[NonAction]
public override System.Threading.Tasks.Task<System.Web.Mvc.ActionResult> Begin()
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Begin);
BeginOverride(callInfo);
return System.Threading.Tasks.Task.FromResult(callInfo as System.Web.Mvc.ActionResult);
}
[NonAction]
partial void CompleteOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, System.Guid activationId, string challenge, string challengeIv, string signature);
[NonAction]
public override System.Threading.Tasks.Task<System.Web.Mvc.ActionResult> Complete(System.Guid activationId, string challenge, string challengeIv, string signature)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Complete);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "activationId", activationId);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "challenge", challenge);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "challengeIv", challengeIv);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "signature", signature);
CompleteOverride(callInfo, activationId, challenge, challengeIv, signature);
return System.Threading.Tasks.Task.FromResult(callInfo as System.Web.Mvc.ActionResult);
}
}
}
#endregion T4MVC
#pragma warning restore 1591, 3008, 3009, 0108, 0114
@@ -76,12 +76,14 @@ namespace Disco.Web.Areas.Config.Controllers
public class ActionNamesClass
{
public readonly string Index = "Index";
public readonly string Activate = "Activate";
}
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionNameConstants
{
public const string Index = "Index";
public const string Activate = "Activate";
}
@@ -95,8 +97,10 @@ namespace Disco.Web.Areas.Config.Controllers
public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
public class _ViewNamesClass
{
public readonly string Activate = "Activate";
public readonly string Index = "Index";
}
public readonly string Activate = "~/Areas/Config/Views/SystemConfig/Activate.cshtml";
public readonly string Index = "~/Areas/Config/Views/SystemConfig/Index.cshtml";
}
}
@@ -117,6 +121,17 @@ namespace Disco.Web.Areas.Config.Controllers
return callInfo;
}
[NonAction]
partial void ActivateOverride(T4MVC_System_Web_Mvc_ActionResult callInfo);
[NonAction]
public override System.Web.Mvc.ActionResult Activate()
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Activate);
ActivateOverride(callInfo);
return callInfo;
}
}
}
+1
View File
@@ -51,6 +51,7 @@ namespace T4MVC
public class APIClass
{
public readonly string Name = "API";
public Disco.Web.Areas.API.Controllers.ActivationController Activation = new Disco.Web.Areas.API.Controllers.T4MVC_ActivationController();
public Disco.Web.Areas.API.Controllers.AuthorizationRoleController AuthorizationRole = new Disco.Web.Areas.API.Controllers.T4MVC_AuthorizationRoleController();
public Disco.Web.Areas.API.Controllers.BootstrapperController Bootstrapper = new Disco.Web.Areas.API.Controllers.T4MVC_BootstrapperController();
public Disco.Web.Areas.API.Controllers.DeviceBatchController DeviceBatch = new Disco.Web.Areas.API.Controllers.T4MVC_DeviceBatchController();