fix activation registration after local-network-access policy introduction

This commit is contained in:
Gary Sharp
2025-12-29 13:14:52 +11:00
parent f975c55b8a
commit 4e7c7c117b
12 changed files with 128 additions and 268 deletions
@@ -42,6 +42,24 @@ namespace Disco.Services.Interop.DiscoServices
public Uri GetCallbackUrl()
=> new Uri(DiscoServiceHelpers.ActivationServiceUrl, "/api/callback");
public string CalculateCallbackProof(Guid correlationId, string userId, long timestamp)
{
var deploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId);
var secret = Guid.Parse(database.DiscoConfiguration.DeploymentSecret);
using (var hmac = new HMACSHA256(secret.ToByteArray()))
{
var data = new MemoryStream();
data.Write(deploymentId.ToByteArray(), 0, 16);
data.Write(correlationId.ToByteArray(), 0, 16);
var userIdBytes = Encoding.UTF8.GetBytes(userId);
data.Write(BitConverter.GetBytes(userIdBytes.Length), 0, 4);
data.Write(userIdBytes, 0, userIdBytes.Length);
data.Write(BitConverter.GetBytes(timestamp), 0, 8);
var hash = hmac.ComputeHash(data.ToArray());
return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
}
/// <summary>
/// Begin the activation process
/// </summary>
@@ -43,7 +43,10 @@ namespace Disco.Services.Interop.DiscoServices
connection.Closed += ex =>
{
SystemLog.LogException("Online Services: Connection Closed", ex);
if (ex != null)
SystemLog.LogException("Online Services: Connection Closed", ex);
else
SystemLog.LogInformation("Online Services: Connection Closed");
return Task.CompletedTask;
};
connection.Reconnected += connectionId =>
@@ -53,7 +56,11 @@ namespace Disco.Services.Interop.DiscoServices
};
connection.Reconnecting += ex =>
{
SystemLog.LogInformation("Online Services: Connection Reconnecting");
if (ex != null)
SystemLog.LogException("Online Services: Connection Reconnecting", ex);
else
SystemLog.LogInformation("Online Services: Connection Reconnecting");
return Task.CompletedTask;
};
}
@@ -12,10 +12,22 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.DiscoAdminAccount)]
public partial class ActivationController : AuthorizedDatabaseController
{
[HttpPost]
public virtual ActionResult TestCallback(CallbackModel model)
[HttpGet]
public virtual async Task<ActionResult> Begin(CallbackModel model)
{
return this.PrecompiledPartialView<Views.Activation._ActivateCallback>(model);
// validate timestamp
var thresholdStart = DateTimeOffset.UtcNow.AddSeconds(-20).ToUnixTimeMilliseconds();
var thresholdEnd = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (model.Timestamp < thresholdStart || model.Timestamp > thresholdEnd)
return new HttpStatusCodeResult(400, "Invalid timestamp");
// validate proof
var service = new ActivationService(Database);
var expectedProof = service.CalculateCallbackProof(model.CorrelationId, model.UserId, model.Timestamp);
if (model.Proof != expectedProof)
return new HttpStatusCodeResult(400, "Invalid proof");
return await Begin();
}
[HttpPost, ValidateAntiForgeryToken]
@@ -33,7 +45,7 @@ namespace Disco.Web.Areas.API.Controllers
RedirectUrl = challengeModel.RedirectUrl
};
return View(model);
return View(MVC.API.Activation.Views.Begin, model);
}
[HttpGet]
@@ -5,9 +5,12 @@ namespace Disco.Web.Areas.API.Models.Activation
{
public class CallbackModel
{
public string Origin { get; set; }
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
[StringLength(50)]
public string UserId { get; set; }
public long Timestamp { get; set; }
public string Proof { get; set; }
}
}
@@ -1,17 +0,0 @@
@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>
@@ -1,107 +0,0 @@
#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
@@ -30,7 +30,9 @@ namespace Disco.Web.Areas.Config.Controllers
DeploymentId = Guid.Parse(Database.DiscoConfiguration.DeploymentId),
CorrelationId = Guid.NewGuid(),
UserId = CurrentUser.UserId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
};
model.Proof = service.CalculateCallbackProof(model.CorrelationId, model.UserId, model.Timestamp);
return View(model);
}
@@ -4,9 +4,11 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
{
public class ActivateModel
{
public Uri CallbackUrl { get; set; }
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
public string UserId { get; set; }
public long Timestamp { get; set; }
public string Proof { get; set; }
public Uri CallbackUrl { get; set; }
}
}
@@ -11,45 +11,23 @@
</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" />
<form id="callbackSubmit" action="@Model.CallbackUrl" method="post" data-failedurl="@Url.Action(MVC.Config.SystemConfig.Index())">
<input type="hidden" name="correlationId" value="@Model.CorrelationId" />
<input type="hidden" name="deploymentId" value="@Model.DeploymentId" />
<input type="hidden" name="timestamp" value="@Model.Timestamp" />
<input type="hidden" name="proof" value="@Model.Proof" />
<input type="hidden" name="userId" value="@Model.UserId" />
<input type="hidden" name="callbackUrl" value="@(new Uri(Request.Url, Url.Action(MVC.API.Activation.Begin())))" />
</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 callbackFailedUrl = callbackForm.attr('data-failedurl');
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');
};
}, 1000 * 18);
callbackForm.trigger('submit');
});
</script>
@@ -68,44 +68,47 @@ 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");
"\n\r\n<form");
WriteLiteral(" id=\"callbackSubmit\"");
WriteAttribute("action", Tuple.Create(" action=\"", 563), Tuple.Create("\"", 590)
WriteAttribute("action", Tuple.Create(" action=\"", 504), Tuple.Create("\"", 531)
#line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 572), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line 14 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 513), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line default
#line hidden
, 572), false)
, 513), false)
);
WriteLiteral(" method=\"post\"");
WriteLiteral(" target=\"callbackFrame\"");
WriteLiteral(" data-failedurl=\"");
#line 14 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
Write(Url.Action(MVC.Config.SystemConfig.Index()));
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(">\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"callbackUrl\"");
WriteLiteral(" name=\"correlationId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 674), Tuple.Create("\"", 752)
WriteAttribute("value", Tuple.Create(" value=\"", 656), Tuple.Create("\"", 684)
#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 15 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 664), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line default
#line hidden
, 682), false)
, 664), false)
);
WriteLiteral(" />\r\n <input");
@@ -114,30 +117,46 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"deploymentId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 802), Tuple.Create("\"", 829)
WriteAttribute("value", Tuple.Create(" value=\"", 734), Tuple.Create("\"", 761)
#line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 810), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line 16 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 742), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line default
#line hidden
, 810), false)
, 742), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"correlationId\"");
WriteLiteral(" name=\"timestamp\"");
WriteAttribute("value", Tuple.Create(" value=\"", 880), Tuple.Create("\"", 908)
WriteAttribute("value", Tuple.Create(" value=\"", 808), Tuple.Create("\"", 832)
#line 20 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 888), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 816), Tuple.Create<System.Object, System.Int32>(Model.Timestamp
#line default
#line hidden
, 888), false)
, 816), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"proof\"");
WriteAttribute("value", Tuple.Create(" value=\"", 875), Tuple.Create("\"", 895)
#line 18 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 883), Tuple.Create<System.Object, System.Int32>(Model.Proof
#line default
#line hidden
, 883), false)
);
WriteLiteral(" />\r\n <input");
@@ -146,81 +165,43 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"userId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 952), Tuple.Create("\"", 973)
WriteAttribute("value", Tuple.Create(" value=\"", 939), Tuple.Create("\"", 960)
#line 21 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 960), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 947), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line default
#line hidden
, 960), false)
, 947), false)
);
WriteLiteral(" />\r\n</form>\r\n\r\n");
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"callbackUrl\"");
WriteAttribute("value", Tuple.Create(" value=\"", 1009), Tuple.Create("\"", 1080)
#line 24 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
#line 20 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 1017), Tuple.Create<System.Object, System.Int32>(new Uri(Request.Url, Url.Action(MVC.API.Activation.Begin()))
#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)
, 1017), false)
);
WriteLiteral(" class=\"hidden\"");
WriteLiteral(@"></a>
WriteLiteral(@" />
</form>
<script>
$(function () {
const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href');
const callbackFailedUrl = callbackForm.attr('data-failedurl');
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');
};
}, 1000 * 18);
callbackForm.trigger('submit');
});
</script>
-9
View File
@@ -442,11 +442,6 @@
<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>
@@ -1518,10 +1513,6 @@
<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>
@@ -59,12 +59,6 @@ namespace Disco.Web.Areas.API.Controllers
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()
@@ -88,7 +82,6 @@ namespace Disco.Web.Areas.API.Controllers
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionNamesClass
{
public readonly string TestCallback = "TestCallback";
public readonly string Begin = "Begin";
public readonly string Complete = "Complete";
}
@@ -96,17 +89,16 @@ namespace Disco.Web.Areas.API.Controllers
[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();
static readonly ActionParamsClass_Begin s_params_Begin = new ActionParamsClass_Begin();
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public ActionParamsClass_TestCallback TestCallbackParams { get { return s_params_TestCallback; } }
public ActionParamsClass_Begin BeginParams { get { return s_params_Begin; } }
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
public class ActionParamsClass_TestCallback
public class ActionParamsClass_Begin
{
public readonly string model = "model";
}
@@ -131,10 +123,8 @@ namespace Disco.Web.Areas.API.Controllers
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";
}
}
@@ -145,15 +135,15 @@ namespace Disco.Web.Areas.API.Controllers
public T4MVC_ActivationController() : base(Dummy.Instance) { }
[NonAction]
partial void TestCallbackOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, Disco.Web.Areas.API.Models.Activation.CallbackModel model);
partial void BeginOverride(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)
public override System.Threading.Tasks.Task<System.Web.Mvc.ActionResult> Begin(Disco.Web.Areas.API.Models.Activation.CallbackModel model)
{
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.TestCallback);
var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Begin);
ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "model", model);
TestCallbackOverride(callInfo, model);
return callInfo;
BeginOverride(callInfo, model);
return System.Threading.Tasks.Task.FromResult(callInfo as System.Web.Mvc.ActionResult);
}
[NonAction]