-
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.
Store One Login user info in DB (#1123)
- Loading branch information
Showing
26 changed files
with
1,990 additions
and
75 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/ClaimTypes.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,7 @@ | ||
namespace TeachingRecordSystem.AuthorizeAccess; | ||
|
||
public static class ClaimTypes | ||
{ | ||
public const string Trn = "trn"; | ||
public const string PersonId = "person_id"; | ||
} |
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
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
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
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
84 changes: 84 additions & 0 deletions
84
TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/SignInJourneyHelper.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,84 @@ | ||
using System.Security.Claims; | ||
using System.Text.Json; | ||
using GovUk.OneLogin.AspNetCore; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.EntityFrameworkCore; | ||
using TeachingRecordSystem.AuthorizeAccess.Infrastructure.Security; | ||
using TeachingRecordSystem.Core; | ||
using TeachingRecordSystem.Core.DataStore.Postgres; | ||
using TeachingRecordSystem.FormFlow.State; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccess; | ||
|
||
public class SignInJourneyHelper(TrsDbContext dbContext, IUserInstanceStateProvider userInstanceStateProvider, IClock clock) | ||
{ | ||
public IUserInstanceStateProvider UserInstanceStateProvider { get; } = userInstanceStateProvider; | ||
|
||
public async Task OnSignedInWithOneLogin(SignInJourneyState state, AuthenticationTicket ticket) | ||
{ | ||
var subject = ticket.Principal.FindFirstValue("sub") ?? throw new InvalidOperationException("No sub claim."); | ||
var email = ticket.Principal.FindFirstValue("email") ?? throw new InvalidOperationException("No email claim."); | ||
var vc = ticket.Principal.FindFirstValue("vc") is string vcStr ? JsonDocument.Parse(vcStr) : null; | ||
|
||
state.Reset(); | ||
state.OneLoginAuthenticationTicket = ticket; | ||
|
||
if (vc is not null) | ||
{ | ||
state.VerifiedNames = ticket.Principal.GetCoreIdentityNames().Select(n => n.NameParts.Select(part => part.Value).ToArray()).ToArray(); | ||
state.VerifiedDatesOfBirth = ticket.Principal.GetCoreIdentityBirthDates().Select(d => d.Value).ToArray(); | ||
} | ||
|
||
var oneLoginUser = await dbContext.OneLoginUsers | ||
.Include(o => o.Person) | ||
.SingleOrDefaultAsync(o => o.Subject == subject); | ||
|
||
if (oneLoginUser is not null) | ||
{ | ||
oneLoginUser.CoreIdentityVc = vc; | ||
oneLoginUser.LastOneLoginSignIn = clock.UtcNow; | ||
oneLoginUser.Email = email; | ||
|
||
if (oneLoginUser.Person is not null) | ||
{ | ||
oneLoginUser.LastSignIn = clock.UtcNow; | ||
|
||
CreateAndAssignPrincipal(state, oneLoginUser.Person.PersonId, oneLoginUser.Person.Trn!); | ||
} | ||
} | ||
else | ||
{ | ||
oneLoginUser = new() | ||
{ | ||
Subject = subject, | ||
Email = email, | ||
FirstOneLoginSignIn = clock.UtcNow, | ||
LastOneLoginSignIn = clock.UtcNow, | ||
CoreIdentityVc = vc | ||
}; | ||
dbContext.OneLoginUsers.Add(oneLoginUser); | ||
} | ||
|
||
await dbContext.SaveChangesAsync(); | ||
} | ||
|
||
private static void CreateAndAssignPrincipal(SignInJourneyState state, Guid personId, string trn) | ||
{ | ||
if (state.OneLoginAuthenticationTicket is null) | ||
{ | ||
throw new InvalidOperationException("User is not authenticated with One Login."); | ||
} | ||
|
||
var oneLoginIdentity = (ClaimsIdentity)state.OneLoginAuthenticationTicket.Principal.Identity!; | ||
|
||
var teachingRecordIdentity = new ClaimsIdentity(new[] | ||
{ | ||
new Claim(ClaimTypes.Trn, trn), | ||
new Claim(ClaimTypes.PersonId, personId.ToString()) | ||
}); | ||
|
||
var principal = new ClaimsPrincipal(new[] { oneLoginIdentity, teachingRecordIdentity }); | ||
|
||
state.AuthenticationTicket = new AuthenticationTicket(principal, state.AuthenticationProperties, AuthenticationSchemes.MatchToTeachingRecord); | ||
} | ||
} |
66 changes: 42 additions & 24 deletions
66
TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/SignInJourneyState.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 |
---|---|---|
@@ -1,46 +1,64 @@ | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using Microsoft.AspNetCore.Authentication; | ||
using TeachingRecordSystem.FormFlow; | ||
|
||
namespace TeachingRecordSystem.AuthorizeAccess; | ||
|
||
public class SignInJourneyState | ||
[method: JsonConstructor] | ||
public class SignInJourneyState(string redirectUri, AuthenticationProperties? authenticationProperties) | ||
{ | ||
public const string JourneyName = "SignInJourney"; | ||
|
||
private readonly TicketSerializer _ticketSerializer = TicketSerializer.Default; | ||
public static JourneyDescriptor JourneyDescriptor { get; } = | ||
new JourneyDescriptor(JourneyName, typeof(SignInJourneyState), requestDataKeys: [], appendUniqueKey: true); | ||
|
||
[JsonInclude] | ||
private byte[]? _oneLoginAuthenticationTicket; | ||
public string RedirectUri { get; } = redirectUri; | ||
|
||
[JsonConstructor] | ||
public SignInJourneyState(string redirectUri, string oneLoginAuthenticationScheme) | ||
{ | ||
RedirectUri = redirectUri; | ||
OneLoginAuthenticationScheme = oneLoginAuthenticationScheme; | ||
} | ||
[JsonConverter(typeof(AuthenticationTicketJsonConverter))] | ||
public AuthenticationTicket? AuthenticationTicket { get; set; } | ||
|
||
public AuthenticationTicket? AuthenticationTicket { get; private set; } | ||
[JsonConverter(typeof(AuthenticationTicketJsonConverter))] | ||
public AuthenticationTicket? OneLoginAuthenticationTicket { get; set; } | ||
|
||
[JsonIgnore] | ||
public AuthenticationTicket? OneLoginAuthenticationTicket => | ||
_oneLoginAuthenticationTicket is not null ? _ticketSerializer.Deserialize(_oneLoginAuthenticationTicket) : null; | ||
public string[][]? VerifiedNames { get; set; } | ||
|
||
[JsonIgnore] | ||
public bool AuthenticatedWithOneLogin => OneLoginAuthenticationTicket is not null; | ||
public DateOnly[]? VerifiedDatesOfBirth { get; set; } | ||
|
||
public string RedirectUri { get; } | ||
public AuthenticationProperties? AuthenticationProperties { get; } = authenticationProperties; | ||
|
||
public string OneLoginAuthenticationScheme { get; } | ||
public void Reset() | ||
{ | ||
AuthenticationTicket = null; | ||
OneLoginAuthenticationTicket = null; | ||
VerifiedNames = null; | ||
VerifiedDatesOfBirth = null; | ||
} | ||
} | ||
|
||
public void OnSignedInWithOneLogin(AuthenticationTicket ticket) | ||
public class AuthenticationTicketJsonConverter : JsonConverter<AuthenticationTicket> | ||
{ | ||
private readonly TicketSerializer _ticketSerializer = TicketSerializer.Default; | ||
|
||
public override AuthenticationTicket? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||
{ | ||
_oneLoginAuthenticationTicket = _ticketSerializer.Serialize(ticket); | ||
// TODO Should we reset all other state here? | ||
if (reader.TokenType == JsonTokenType.Null) | ||
{ | ||
return null; | ||
} | ||
|
||
if (reader.TokenType == JsonTokenType.String) | ||
{ | ||
var bytes = reader.GetBytesFromBase64(); | ||
return _ticketSerializer.Deserialize(bytes); | ||
} | ||
|
||
throw new JsonException($"Unknown TokenType: '{reader.TokenType}'."); | ||
} | ||
|
||
public void Reset() | ||
public override void Write(Utf8JsonWriter writer, AuthenticationTicket value, JsonSerializerOptions options) | ||
{ | ||
AuthenticationTicket = null; | ||
_oneLoginAuthenticationTicket = null; | ||
var bytes = _ticketSerializer.Serialize(value); | ||
writer.WriteBase64StringValue(bytes); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...rdSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Mappings/OneLoginUserMapping.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,16 @@ | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||
using TeachingRecordSystem.Core.DataStore.Postgres.Models; | ||
|
||
namespace TeachingRecordSystem.Core.DataStore.Postgres.Mappings; | ||
|
||
public class OneLoginUserMapping : IEntityTypeConfiguration<OneLoginUser> | ||
{ | ||
public void Configure(EntityTypeBuilder<OneLoginUser> builder) | ||
{ | ||
builder.HasKey(o => o.Subject); | ||
builder.Property(o => o.Subject).HasMaxLength(200); | ||
builder.Property(o => o.Email).HasMaxLength(200); | ||
builder.HasOne<Person>(o => o.Person).WithOne().HasForeignKey<OneLoginUser>(o => o.PersonId); | ||
} | ||
} |
Oops, something went wrong.