-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Skeleton authentication scheme setup for Authorize project (#1115)
- Loading branch information
Showing
14 changed files
with
415 additions
and
30 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
.../src/TeachingRecordSystem.AuthorizeAccessToATeacherRecord/AuthorizeAccessLinkGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Microsoft.AspNetCore.WebUtilities; | ||
using TeachingRecordSystem.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord; | ||
|
||
public class AuthorizeAccessLinkGenerator(LinkGenerator linkGenerator) | ||
{ | ||
public string Start(JourneyInstanceId journeyInstanceId) => Nino(journeyInstanceId); | ||
|
||
public string Nino(JourneyInstanceId journeyInstanceId) => GetRequiredPathByPage("/Nino", journeyInstanceId: journeyInstanceId); | ||
|
||
private string GetRequiredPathByPage(string page, string? handler = null, object? routeValues = null, JourneyInstanceId? journeyInstanceId = null) | ||
{ | ||
var url = linkGenerator.GetPathByPage(page, handler, values: routeValues) ?? throw new InvalidOperationException("Page was not found."); | ||
|
||
if (journeyInstanceId?.UniqueKey is string journeyInstanceUniqueKey) | ||
{ | ||
url = QueryHelpers.AddQueryString(url, Constants.UniqueKeyQueryParameterName, journeyInstanceUniqueKey); | ||
} | ||
|
||
return url; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
...tem.AuthorizeAccessToATeacherRecord/Infrastructure/FormFlow/DummyCurrentUserIdProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using TeachingRecordSystem.SupportUi.Infrastructure.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.FormFlow; | ||
|
||
public class DummyCurrentUserIdProvider : ICurrentUserIdProvider | ||
{ | ||
public string GetCurrentUserId() => Guid.Empty.ToString(); | ||
} |
73 changes: 73 additions & 0 deletions
73
...ingRecordSystem.AuthorizeAccessToATeacherRecord/Infrastructure/FormFlow/FormFlowHelper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using Microsoft.AspNetCore.Mvc.ModelBinding; | ||
using Microsoft.Extensions.Options; | ||
using TeachingRecordSystem.FormFlow; | ||
using TeachingRecordSystem.FormFlow.State; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.FormFlow; | ||
|
||
public class SignInJourneyHelper | ||
{ | ||
private readonly IUserInstanceStateProvider _userInstanceStateProvider; | ||
private readonly JourneyDescriptor _journeyDescriptor; | ||
|
||
public SignInJourneyHelper(IUserInstanceStateProvider userInstanceStateProvider, IOptions<FormFlowOptions> formFlowOptionsAccessor) | ||
{ | ||
_userInstanceStateProvider = userInstanceStateProvider; | ||
|
||
_journeyDescriptor = formFlowOptionsAccessor.Value.JourneyRegistry.GetJourneyByName(SignInJourneyState.JourneyName) ?? | ||
throw new InvalidOperationException($"Cannot find {SignInJourneyState.JourneyName} journey."); | ||
} | ||
|
||
public async Task<JourneyInstance<SignInJourneyState>?> GetInstanceAsync(HttpContext httpContext, JourneyInstanceId? instanceIdHint = null) | ||
{ | ||
if (httpContext.Items.TryGetValue(typeof(JourneyInstance), out var journeyInstanceObj) && | ||
journeyInstanceObj is JourneyInstance<SignInJourneyState> instance) | ||
{ | ||
return instance; | ||
} | ||
|
||
var valueProvider = CreateValueProvider(httpContext); | ||
|
||
if (!JourneyInstanceId.TryResolve(_journeyDescriptor, valueProvider, out var instanceId) && instanceIdHint is null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (await _userInstanceStateProvider.GetInstanceAsync(instanceIdHint ?? instanceId, typeof(SignInJourneyState)) | ||
is not JourneyInstance<SignInJourneyState> persistedInstance) | ||
{ | ||
return null; | ||
} | ||
|
||
httpContext.Items[typeof(JourneyInstance)] = persistedInstance; | ||
|
||
return persistedInstance; | ||
} | ||
|
||
public async Task<JourneyInstance<SignInJourneyState>> GetOrCreateInstanceAsync( | ||
HttpContext httpContext, | ||
Func<SignInJourneyState> createState, | ||
Action<SignInJourneyState> updateState) | ||
{ | ||
var existingInstance = await GetInstanceAsync(httpContext); | ||
|
||
if (existingInstance is not null) | ||
{ | ||
await existingInstance.UpdateStateAsync(updateState); | ||
return existingInstance; | ||
} | ||
|
||
var valueProvider = CreateValueProvider(httpContext); | ||
var instanceId = JourneyInstanceId.Create(_journeyDescriptor, valueProvider); | ||
|
||
var newState = createState(); | ||
var instance = (JourneyInstance<SignInJourneyState>)await _userInstanceStateProvider.CreateInstanceAsync(instanceId, typeof(SignInJourneyState), newState, properties: null); | ||
|
||
httpContext.Items[typeof(JourneyInstance)] = instance; | ||
|
||
return instance; | ||
} | ||
|
||
private static IValueProvider CreateValueProvider(HttpContext httpContext) => | ||
new QueryStringValueProvider(BindingSource.Query, httpContext.Request.Query, culture: null); | ||
} |
8 changes: 8 additions & 0 deletions
8
...rdSystem.AuthorizeAccessToATeacherRecord/Infrastructure/Security/AuthenticationSchemes.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.Security; | ||
|
||
public static class AuthenticationSchemes | ||
{ | ||
public const string FormFlowJourney = nameof(FormFlowJourney); | ||
|
||
public const string MatchToTeachingRecord = nameof(MatchToTeachingRecord); | ||
} |
92 changes: 92 additions & 0 deletions
92
...m.AuthorizeAccessToATeacherRecord/Infrastructure/Security/FormFlowJourneySignInHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Security.Claims; | ||
using Microsoft.AspNetCore.Authentication; | ||
using TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.FormFlow; | ||
using TeachingRecordSystem.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.Security; | ||
|
||
/// <summary> | ||
/// An <see cref="IAuthenticationSignInHandler"/> that persists an <see cref="AuthenticationTicket"/> to | ||
/// the current FormFlow instance's state. | ||
/// </summary> | ||
public class FormFlowJourneySignInHandler(SignInJourneyHelper signInJourneyHelper) : IAuthenticationSignInHandler | ||
{ | ||
private AuthenticationScheme? _scheme; | ||
private HttpContext? _context; | ||
|
||
public async Task<AuthenticateResult> AuthenticateAsync() | ||
{ | ||
EnsureInitialized(); | ||
|
||
var journeyInstance = await signInJourneyHelper.GetInstanceAsync(_context); | ||
|
||
if (journeyInstance is null || journeyInstance.State.OneLoginAuthenticationTicket is null) | ||
{ | ||
return AuthenticateResult.NoResult(); | ||
} | ||
|
||
return AuthenticateResult.Success(journeyInstance.State.OneLoginAuthenticationTicket); | ||
} | ||
|
||
public Task ChallengeAsync(AuthenticationProperties? properties) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public Task ForbidAsync(AuthenticationProperties? properties) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) | ||
{ | ||
_scheme = scheme; | ||
_context = context; | ||
return Task.CompletedTask; | ||
} | ||
|
||
public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) | ||
{ | ||
EnsureInitialized(); | ||
|
||
if (properties is null || !properties.Items.TryGetValue(PropertyKeys.JourneyInstanceId, out var serializedInstanceId) || | ||
serializedInstanceId is null) | ||
{ | ||
throw new InvalidOperationException($"{PropertyKeys.JourneyInstanceId} must be specified in {nameof(properties)}."); | ||
} | ||
|
||
var journeyInstanceId = JourneyInstanceId.Deserialize(serializedInstanceId); | ||
|
||
var journeyInstance = await signInJourneyHelper.GetInstanceAsync(_context, journeyInstanceId) ?? | ||
throw new InvalidOperationException("No FormFlow journey."); | ||
|
||
var ticket = new AuthenticationTicket(user, properties, _scheme.Name); | ||
|
||
await journeyInstance.UpdateStateAsync(state => state.OnSignedInWithOneLogin(ticket)); | ||
} | ||
|
||
public async Task SignOutAsync(AuthenticationProperties? properties) | ||
{ | ||
EnsureInitialized(); | ||
|
||
var journeyInstance = await signInJourneyHelper.GetInstanceAsync(_context) ?? | ||
throw new InvalidOperationException("No FormFlow journey."); | ||
|
||
await journeyInstance.UpdateStateAsync(state => state.Reset()); | ||
} | ||
|
||
[MemberNotNull(nameof(_context), nameof(_scheme))] | ||
private void EnsureInitialized() | ||
{ | ||
if (_context is null || _scheme is null) | ||
{ | ||
throw new InvalidOperationException("Not initialized."); | ||
} | ||
} | ||
|
||
public static class PropertyKeys | ||
{ | ||
public const string JourneyInstanceId = nameof(JourneyInstanceId); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
...essToATeacherRecord/Infrastructure/Security/MatchToTeachingRecordAuthenticationHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using GovUk.OneLogin.AspNetCore; | ||
using Microsoft.AspNetCore.Authentication; | ||
using TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Infrastructure.Security; | ||
|
||
public class MatchToTeachingRecordAuthenticationHandler( | ||
SignInJourneyHelper signInJourneyHelper, | ||
AuthorizeAccessLinkGenerator linkGenerator) : IAuthenticationHandler | ||
{ | ||
private AuthenticationScheme? _scheme; | ||
private HttpContext? _context; | ||
|
||
public async Task<AuthenticateResult> AuthenticateAsync() | ||
{ | ||
EnsureInitialized(); | ||
|
||
var journeyInstance = await signInJourneyHelper.GetInstanceAsync(_context); | ||
|
||
if (journeyInstance is null) | ||
{ | ||
return AuthenticateResult.NoResult(); | ||
} | ||
|
||
var ticket = journeyInstance.State.AuthenticationTicket; | ||
|
||
if (ticket is null) | ||
{ | ||
return AuthenticateResult.NoResult(); | ||
} | ||
|
||
return AuthenticateResult.Success(ticket); | ||
} | ||
|
||
public async Task ChallengeAsync(AuthenticationProperties? properties) | ||
{ | ||
EnsureInitialized(); | ||
|
||
properties ??= new(); | ||
properties.RedirectUri ??= "/"; | ||
|
||
var journeyInstance = await signInJourneyHelper.GetOrCreateInstanceAsync( | ||
_context, | ||
createState: () => new SignInJourneyState(properties.RedirectUri!, OneLoginDefaults.AuthenticationScheme), | ||
updateState: state => state.Reset()); | ||
|
||
properties.RedirectUri = linkGenerator.Start(journeyInstance.InstanceId); | ||
properties.Items.Add(FormFlowJourneySignInHandler.PropertyKeys.JourneyInstanceId, journeyInstance.InstanceId.Serialize()); | ||
await _context!.ChallengeAsync(OneLoginDefaults.AuthenticationScheme, properties); | ||
} | ||
|
||
public Task ForbidAsync(AuthenticationProperties? properties) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) | ||
{ | ||
_scheme = scheme; | ||
_context = context; | ||
return Task.CompletedTask; | ||
} | ||
|
||
[MemberNotNull(nameof(_context), nameof(_scheme))] | ||
private void EnsureInitialized() | ||
{ | ||
if (_context is null || _scheme is null) | ||
{ | ||
throw new InvalidOperationException("Not initialized."); | ||
} | ||
} | ||
} |
5 changes: 4 additions & 1 deletion
5
...cordSystem/src/TeachingRecordSystem.AuthorizeAccessToATeacherRecord/Pages/Index.cshtml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
...ngRecordSystem/src/TeachingRecordSystem.AuthorizeAccessToATeacherRecord/Pages/Nino.cshtml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
@page "/nino" | ||
@model TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Pages.NinoModel | ||
@{ | ||
} | ||
|
||
<h1>NINO</h1> |
14 changes: 14 additions & 0 deletions
14
...ecordSystem/src/TeachingRecordSystem.AuthorizeAccessToATeacherRecord/Pages/Nino.cshtml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc.RazorPages; | ||
using TeachingRecordSystem.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccessToATeacherRecord.Pages; | ||
|
||
[Authorize] | ||
[Journey(SignInJourneyState.JourneyName), RequireJourneyInstance] | ||
public class NinoModel : PageModel | ||
{ | ||
public void OnGet() | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.