diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/Infrastructure/Security/ApiKeyAuthenticationHandler.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/Infrastructure/Security/ApiKeyAuthenticationHandler.cs index 3815818c5..416f8e9d8 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/Infrastructure/Security/ApiKeyAuthenticationHandler.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/Infrastructure/Security/ApiKeyAuthenticationHandler.cs @@ -68,7 +68,7 @@ protected async override Task HandleAuthenticateAsync() return AuthenticateResult.Fail($"API key is expired."); } - var principal = CreatePrincipal(apiKey.ApplicationUserId.ToString(), apiKey.ApplicationUser.Name, apiKey.ApplicationUser.ApiRoles); + var principal = CreatePrincipal(apiKey.ApplicationUserId.ToString(), apiKey.ApplicationUser.Name, apiKey.ApplicationUser.ApiRoles ?? []); var ticket = new AuthenticationTicket(principal, Scheme.Name); LogContext.PushProperty("ClientId", apiKey.ApplicationUser.UserId); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Controllers/OidcController.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Controllers/OidcController.cs index 22e245a0c..3f18a1d92 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Controllers/OidcController.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Controllers/OidcController.cs @@ -35,7 +35,6 @@ public async Task Authorize() { var parameters = Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList(); - var authenticationProperties = new AuthenticationProperties() { RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Filters/NotFoundResourceFilter.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Filters/NotFoundResourceFilter.cs new file mode 100644 index 000000000..b2db1757d --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Filters/NotFoundResourceFilter.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace TeachingRecordSystem.AuthorizeAccess.Infrastructure.Filters; + +public class NotFoundResourceFilter : IResourceFilter +{ + public void OnResourceExecuted(ResourceExecutedContext context) + { + throw new NotImplementedException(); + } + + public void OnResourceExecuting(ResourceExecutingContext context) + { + context.Result = new NotFoundResult(); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Security/OneLoginAuthenticationSchemeProvider.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Security/OneLoginAuthenticationSchemeProvider.cs index e7be7efa1..fba18880c 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Security/OneLoginAuthenticationSchemeProvider.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Infrastructure/Security/OneLoginAuthenticationSchemeProvider.cs @@ -155,6 +155,15 @@ void IConfigureNamedOptions.Configure(string? name, OneLoginOpt options.SignInScheme = AuthenticationSchemes.FormFlowJourney; + options.Events.OnRedirectToIdentityProvider = context => + { + // A large RedirectUri here can make the state parameter so large that One Login rejects the request. + // We have the RedirectUri stashed away on the FormFlow journey any way so we can clear it out. + context.Properties.RedirectUri = null; + + return Task.CompletedTask; + }; + options.Events.OnRedirectToIdentityProviderForSignOut = context => { // The standard sign out process will call Authenticate() on SignInScheme then try to extract the id_token from the Principal. diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml new file mode 100644 index 000000000..08eac25d5 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml @@ -0,0 +1,11 @@ +@page "/oidc-test/sign-out" +@addTagHelper *, Joonasw.AspNetCore.SecurityHeaders +@model TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest.SignOutModel +@{ +} + +
+ Sign out +
+ + diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml.cs new file mode 100644 index 000000000..c90e77748 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignOut.cshtml.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest; + +[Authorize(AuthenticationSchemes = TestAppConfiguration.AuthenticationSchemeName)] +public class SignOutModel : PageModel +{ + public void OnGet() + { + } + + public async Task OnPost() + { + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + return SignOut( + new AuthenticationProperties() + { + RedirectUri = Url.Page("Start") + }, + TestAppConfiguration.AuthenticationSchemeName); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml new file mode 100644 index 000000000..1fd77f6d8 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml @@ -0,0 +1,27 @@ +@page "/oidc-test/signed-in" +@using System.Text.Json +@using TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest +@addTagHelper *, Joonasw.AspNetCore.SecurityHeaders +@model SignedInModel +@{ + var claimsJson = JsonSerializer.Serialize( + User.Claims.ToDictionary(c => c.Type, c => c.Value), + new JsonSerializerOptions() { WriteIndented = true }); +} + +@section Head { + +} + +

+ + Claims + @claimsJson + +

diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml.cs new file mode 100644 index 000000000..9c3f9cbbe --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/SignedIn.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest; + +[Authorize(AuthenticationSchemes = TestAppConfiguration.AuthenticationSchemeName)] +public class SignedInModel : PageModel +{ + public void OnGet() + { + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml new file mode 100644 index 000000000..186a1fd44 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml @@ -0,0 +1,8 @@ +@page "/oidc-test" +@model TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest.StartModel +@{ +} + +
+ Start +
diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml.cs new file mode 100644 index 000000000..d7541707f --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/Start.cshtml.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace TeachingRecordSystem.AuthorizeAccess.Pages.OidcTest; + +public class StartModel : PageModel +{ + public void OnGet() + { + } + + public IActionResult OnPost() => Challenge( + new AuthenticationProperties() + { + RedirectUri = Url.Page("SignedIn") + }, + TestAppConfiguration.AuthenticationSchemeName); +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_Layout.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_Layout.cshtml new file mode 100644 index 000000000..1d05a503c --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_Layout.cshtml @@ -0,0 +1,29 @@ +@{ + Layout = "../Shared/_Layout"; + + ViewBag.ServiceName = "OIDC Sample"; +} + +@section Head { + @RenderSection("Head", required: false) +} + +@if (User.Identity?.IsAuthenticated == true) +{ + @section Nav { + + } +} + +@RenderBody() diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_ViewStart.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_ViewStart.cshtml new file mode 100644 index 000000000..989f3d13e --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/OidcTest/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "./_Layout"; +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/Shared/_Layout.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/Shared/_Layout.cshtml index f093fb75f..d893d176f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/Shared/_Layout.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.AuthorizeAccess/Pages/Shared/_Layout.cshtml @@ -1,9 +1,15 @@ +@inject GovUk.Frontend.AspNetCore.PageTemplateHelper PageTemplateHelper @{ Layout = "_GovUkPageTemplate"; var serviceName = ViewBag.ServiceName ?? "Authorise access to a teaching record"; } +@section Head { + @RenderSection("Head", required: false) + @PageTemplateHelper.GenerateStyleImports() +} + @section Header {