global support license and validation

This commit is contained in:
Gary Sharp
2024-01-26 16:23:26 +11:00
parent f90eda4101
commit d8eb8fec83
8 changed files with 739 additions and 196 deletions
+20 -25
View File
@@ -319,20 +319,27 @@ namespace Disco.Data.Configuration
#endregion #endregion
#region UpdateCheck #region UpdateCheck
public string DeploymentId public bool IsLicensed
{ {
get get => LicenseKey != null && LicenseExpiresOn != null && LicenseExpiresOn > DateTime.UtcNow && LicenseError == null;
{
return Get<string>(null);
}
} }
public string DeploymentSecret public string LicenseKey
{ {
get get => Get<string>(null);
{ set => Set(value);
return Get<string>(null);
}
} }
public DateTime? LicenseExpiresOn
{
get => Get<DateTime?>(null);
set => Set(value);
}
public string LicenseError
{
get => Get<string>(null);
set => Set(value);
}
public string DeploymentId => Get<string>(null);
public string DeploymentSecret => Get<string>(null);
public short DeploymentChecksum public short DeploymentChecksum
{ {
get get
@@ -351,22 +358,10 @@ namespace Disco.Data.Configuration
} }
public UpdateResponseV2 UpdateLastCheckResponse public UpdateResponseV2 UpdateLastCheckResponse
{ {
get get => Get<UpdateResponseV2>(null);
{ set => Set(value);
return Get<UpdateResponseV2>(null);
}
set
{
Set(value);
}
}
public bool UpdateBetaDeployment
{
get
{
return Get(false);
}
} }
public bool UpdateBetaDeployment => Get(false);
public Version InstalledDatabaseVersion public Version InstalledDatabaseVersion
{ {
get get
+1
View File
@@ -367,6 +367,7 @@
<Compile Include="Interop\ActiveDirectory\IADObject.cs" /> <Compile Include="Interop\ActiveDirectory\IADObject.cs" />
<Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" /> <Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" />
<Compile Include="Interop\DiscoServices\Jobs.cs" /> <Compile Include="Interop\DiscoServices\Jobs.cs" />
<Compile Include="Interop\DiscoServices\LicenseValidationTask.cs" />
<Compile Include="Interop\DiscoServices\PluginLibrary.cs" /> <Compile Include="Interop\DiscoServices\PluginLibrary.cs" />
<Compile Include="Interop\DiscoServices\PluginLibraryUpdateTask.cs" /> <Compile Include="Interop\DiscoServices\PluginLibraryUpdateTask.cs" />
<Compile Include="Interop\MimeTypes.cs" /> <Compile Include="Interop\MimeTypes.cs" />
@@ -0,0 +1,167 @@
using Disco.Data.Repository;
using Disco.Services.Tasks;
using Newtonsoft.Json;
using Quartz;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
namespace Disco.Services.Interop.DiscoServices
{
public class LicenseValidationTask : ScheduledTask
{
private const string jobMapLicenseKey = "License";
public override string TaskName { get { return "License Validation"; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
if (Database.DiscoConfiguration.LicenseKey != null)
{
// 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);
}
}
public static ScheduledTaskStatus ScheduleNow(string license)
{
var taskStatus = RunningStatus;
if (taskStatus != null)
return taskStatus;
else
{
var task = new LicenseValidationTask();
var taskData = new JobDataMap() { { jobMapLicenseKey, license } };
return task.ScheduleTask(taskData);
}
}
public static ScheduledTaskStatus RunningStatus =>
ScheduledTasks.GetTaskStatuses(typeof(LicenseValidationTask)).Where(ts => ts.IsRunning).FirstOrDefault();
protected override void ExecuteTask()
{
var license = ExecutionContext.JobDetail.JobDataMap.GetString(jobMapLicenseKey);
string orgName;
Guid deploymentId;
using (var database = new DiscoDataContext())
{
if (license == null)
license = database.DiscoConfiguration.LicenseKey;
orgName = database.DiscoConfiguration.OrganisationName;
deploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId);
}
if (!string.IsNullOrWhiteSpace(license))
{
var result = ValidateLicense(deploymentId, orgName, license, true, out var infrastructureError);
string infrastructureError2 = null;
if (result == null)
result = ValidateLicense(deploymentId, orgName, license, false, out infrastructureError2);
if (result == null)
{
var error = $"Validation failed. {infrastructureError ?? infrastructureError2}";
Status.SetTaskException(new Exception(error));
}
else
{
if (result.IsValid)
{
using (var database = new DiscoDataContext())
{
database.DiscoConfiguration.LicenseKey = license;
database.DiscoConfiguration.LicenseExpiresOn = result.ExpiresOn;
database.DiscoConfiguration.LicenseError = null;
database.SaveChanges();
}
Status.UpdateStatus(100, "License validated");
}
else
{
var error = result.ErrorMessage ?? "Validation failed";
Status.SetTaskException(new Exception(error));
}
}
}
}
private LicenseResponseV1 ValidateLicense(Guid deploymentId, string organisationName, string license, bool useProxy, out string infrastructureError)
{
Status.UpdateStatus(10, $"Validating license for {organisationName}");
var appVersion = typeof(LicenseValidationTask).Assembly.GetName().Version.ToString(4);
var updateUrl = $"{DiscoServiceHelpers.ServicesUrl}API/License/V1";
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(updateUrl);
// Fix for Proxy Servers which don't support KeepAlive
request.KeepAlive = false;
if (!useProxy)
request.Proxy = new WebProxy();
request.ContentType = "application/x-www-form-urlencoded";
request.Method = WebRequestMethods.Http.Post;
request.UserAgent = $"Disco/{appVersion} (License)";
using (var requestStream = request.GetRequestStream())
{
using (var writer = new StreamWriter(requestStream, Encoding.GetEncoding(28591)))
{
writer.Write("deploymentId=");
writer.Write(Uri.EscapeDataString(deploymentId.ToString()));
writer.Write($"&license={Uri.EscapeDataString(license)}");
}
}
Status.UpdateStatus(50, "Waiting for validation response");
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode == HttpStatusCode.OK)
{
Status.UpdateStatus(90, "Reading validation response");
string validationJson;
LicenseResponseV1 updateResult;
using (var responseStream = response.GetResponseStream())
{
using (var responseReader = new StreamReader(responseStream))
{
validationJson = responseReader.ReadToEnd();
}
}
updateResult = JsonConvert.DeserializeObject<LicenseResponseV1>(validationJson);
infrastructureError = null;
return updateResult;
}
else
{
infrastructureError = $"Server responded with: [{response.StatusCode}] {response.StatusDescription}";
return null;
}
}
}
private class LicenseResponseV1
{
public bool IsValid { get; set; }
public DateTime? ValidOn { get; set; }
public DateTime? ExpiresOn { get; set; }
public string ErrorMessage { get; set; }
}
}
}
@@ -42,6 +42,26 @@ namespace Disco.Web.Areas.API.Controllers
return RedirectToAction(MVC.Config.Logging.TaskStatus(ts.SessionId)); return RedirectToAction(MVC.Config.Logging.TaskStatus(ts.SessionId));
} }
[DiscoAuthorize(Claims.Config.System.Show)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult LicenseCheck(string license)
{
if (string.IsNullOrWhiteSpace(license))
{
Database.DiscoConfiguration.LicenseKey = null;
Database.DiscoConfiguration.LicenseExpiresOn = null;
Database.DiscoConfiguration.LicenseError = null;
Database.SaveChanges();
return RedirectToAction(MVC.Config.SystemConfig.Index());
}
else
{
var ts = Disco.Services.Interop.DiscoServices.LicenseValidationTask.ScheduleNow(license);
ts.SetFinishedUrl(Url.Action(MVC.Config.SystemConfig.Index()));
return RedirectToAction(MVC.Config.Logging.TaskStatus(ts.SessionId));
}
}
[DiscoAuthorize(Claims.Config.System.Show)] [DiscoAuthorize(Claims.Config.System.Show)]
public virtual ActionResult UpdateCheck() public virtual ActionResult UpdateCheck()
{ {
@@ -112,6 +112,11 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
public bool EmailIsConfigured { get; set; } public bool EmailIsConfigured { get; set; }
#endregion #endregion
public ScheduledTaskStatus LicenseValidationRunningStatus { get; set; }
public string License { get; set; }
public DateTime? LicenseExpires { get; set; }
public string LicenseError { get; set; }
public ScheduledTaskStatus UpdateRunningStatus { get; set; } public ScheduledTaskStatus UpdateRunningStatus { get; set; }
public DateTime? UpdateNextScheduled { get; set; } public DateTime? UpdateNextScheduled { get; set; }
public UpdateResponseV2 UpdateLatestResponse { get; set; } public UpdateResponseV2 UpdateLatestResponse { get; set; }
@@ -136,6 +141,10 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
EmailUsername = config.EmailUsername, EmailUsername = config.EmailUsername,
EmailPassword = null, EmailPassword = null,
EmailIsConfigured = EmailService.IsConfigured, EmailIsConfigured = EmailService.IsConfigured,
License = config.LicenseKey,
LicenseExpires = config.LicenseExpiresOn,
LicenseError = config.LicenseError,
LicenseValidationRunningStatus = LicenseValidationTask.RunningStatus,
UpdateLatestResponse = config.UpdateLastCheckResponse, UpdateLatestResponse = config.UpdateLastCheckResponse,
UpdateRunningStatus = UpdateQueryTask.RunningStatus, UpdateRunningStatus = UpdateQueryTask.RunningStatus,
UpdateNextScheduled = UpdateQueryTask.NextScheduled, UpdateNextScheduled = UpdateQueryTask.NextScheduled,
@@ -69,6 +69,86 @@
</tr> </tr>
</table> </table>
</div> </div>
<div class="form" style="width: 450px; margin-top: 15px;">
<h2>License</h2>
<table>
@{
<tr>
<th style="width: 135px">
License:
</th>
<td>
@using (Html.BeginForm(MVC.API.System.LicenseCheck(), FormMethod.Post))
{
@Html.AntiForgeryToken();
<input id="license" type="text" name="license" value="@Model.License" />
<button type="submit" class="button small">Activate</button>
}
<script type="text/javascript">
$(function () {
const $element = $('#license');
const original = $element.val();
$element.on('keyup', function (e) {
const $button = $element.next('button');
const value = $element.val();
if (!original) {
$element.prop('required', true);
$button.text('Activate');
} else {
if (original === value) {
$button.text('Validate');
} else {
if (!value) {
$button.text('Clear');
} else {
$button.text('Activate');
}
}
}
}).trigger('keyup');
});
</script>
</td>
</tr>
if (Model.License != null)
{
<tr>
<th style="width: 135px">
Status:
</th>
<td>
@if (Model.LicenseError != null)
{
<div class="error"><i class="fa fa-exclamation-circle fa-lg"></i> @Model.LicenseError</div>
}
else
{
<span>Expires @CommonHelpers.FriendlyDate(Model.LicenseExpires)</span>
}
</td>
</tr>
}
else
{
<tr>
<td colspan="2">
<strong>Official support is available</strong>
<ul>
<li>Initial implementation assistance.</li>
<li>Commitment to maintaining Disco ICT functionality and associated plugins.</li>
<li>Direct support.</li>
<li>Access to additional functionality.</li>
<li>Ability to suggest additional functionality, with a voice in feature prioritisation.</li>
</ul>
<div style="text-align: right; margin-top: 4px;">
<a href="https://discoict.com.au/support.aspx" target="_blank" class="button small">Request More Information</a>
</div>
</td>
</tr>
}
}
</table>
</div>
<div class="form" style="width: 450px; margin-top: 15px;"> <div class="form" style="width: 450px; margin-top: 15px;">
<h2>Updates</h2> <h2>Updates</h2>
<table> <table>
File diff suppressed because it is too large Load Diff
@@ -59,6 +59,12 @@ namespace Disco.Web.Areas.API.Controllers
return RedirectToActionPermanent(taskResult.Result); return RedirectToActionPermanent(taskResult.Result);
} }
[NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult LicenseCheck()
{
return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.LicenseCheck);
}
[NonAction] [NonAction]
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public virtual System.Web.Mvc.ActionResult UpdateOrganisationName() public virtual System.Web.Mvc.ActionResult UpdateOrganisationName()
@@ -162,6 +168,7 @@ namespace Disco.Web.Areas.API.Controllers
public readonly string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates"; public readonly string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates";
public readonly string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails"; public readonly string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails";
public readonly string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions"; public readonly string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions";
public readonly string LicenseCheck = "LicenseCheck";
public readonly string UpdateCheck = "UpdateCheck"; public readonly string UpdateCheck = "UpdateCheck";
public readonly string UpdateOrganisationName = "UpdateOrganisationName"; public readonly string UpdateOrganisationName = "UpdateOrganisationName";
public readonly string OrganisationLogo = "OrganisationLogo"; public readonly string OrganisationLogo = "OrganisationLogo";
@@ -187,6 +194,7 @@ namespace Disco.Web.Areas.API.Controllers
public const string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates"; public const string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates";
public const string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails"; public const string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails";
public const string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions"; public const string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions";
public const string LicenseCheck = "LicenseCheck";
public const string UpdateCheck = "UpdateCheck"; public const string UpdateCheck = "UpdateCheck";
public const string UpdateOrganisationName = "UpdateOrganisationName"; public const string UpdateOrganisationName = "UpdateOrganisationName";
public const string OrganisationLogo = "OrganisationLogo"; public const string OrganisationLogo = "OrganisationLogo";
@@ -207,6 +215,14 @@ namespace Disco.Web.Areas.API.Controllers
} }
static readonly ActionParamsClass_LicenseCheck s_params_LicenseCheck = new ActionParamsClass_LicenseCheck();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_LicenseCheck LicenseCheckParams { get { return s_params_LicenseCheck; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_LicenseCheck
{
public readonly string license = "license";
}
static readonly ActionParamsClass_UpdateOrganisationName s_params_UpdateOrganisationName = new ActionParamsClass_UpdateOrganisationName(); static readonly ActionParamsClass_UpdateOrganisationName s_params_UpdateOrganisationName = new ActionParamsClass_UpdateOrganisationName();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_UpdateOrganisationName UpdateOrganisationNameParams { get { return s_params_UpdateOrganisationName; } } public ActionParamsClass_UpdateOrganisationName UpdateOrganisationNameParams { get { return s_params_UpdateOrganisationName; } }
@@ -404,6 +420,18 @@ namespace Disco.Web.Areas.API.Controllers
return callInfo; return callInfo;
} }
[NonAction]
partial void LicenseCheckOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string license);
[NonAction]
public override System.Web.Mvc.ActionResult LicenseCheck(string license)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.LicenseCheck);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "license", license);
LicenseCheckOverride(callInfo, license);
return callInfo;
}
[NonAction] [NonAction]
partial void UpdateCheckOverride(T4MVC_System_Web_Mvc_ActionResult callInfo); partial void UpdateCheckOverride(T4MVC_System_Web_Mvc_ActionResult callInfo);