diff --git a/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs b/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs index 54dab61..4b4113d 100644 --- a/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs +++ b/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs @@ -56,5 +56,10 @@ public class WebSettings /// Gets or sets the SupportFeedbackForm. /// public string SupportFeedbackForm { get; set; } + + /// + /// Gets or sets a value indicating whether IsPasswordUpdate. + /// + public bool IsPasswordUpdate { get; set; } } } diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index ff7b352..123fe1a 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -20,15 +20,11 @@ using LearningHub.Nhs.Auth.Models.Account; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Common; - using LearningHub.Nhs.Models.Entities.Reporting; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using NHSUKViewComponents.Web.ViewModels; /// /// Account Controller operations. @@ -72,7 +68,7 @@ public AccountController( this.authConfig = authConfig?.Value; this.webSettings = webSettings; this.logger = logger; - } + } /// /// Shows the Login page. @@ -214,9 +210,9 @@ await this.UserService.AddLogonToUserHistory( this.ModelState.AddModelError(string.Empty, loginResult.ErrorMessage); } - showFormWithError: +showFormWithError: - // something went wrong, show form with error +// something went wrong, show form with error var vm = await this.BuildLoginViewModelAsync(model); if ((vm.ClientId == "learninghubwebclient") || (vm.ClientId == "learninghubadmin")) { @@ -268,6 +264,9 @@ public async Task Logout(LogoutInputModel model) // delete local authentication cookie await this.HttpContext.SignOutAsync(); + // Delete the authentication cookie to ensure it is invalidated + this.HttpContext.Response.Cookies.Delete(".AspNetCore.Identity.Application"); + // raise the logout event await this.Events.RaiseAsync(new UserLogoutSuccessEvent(this.User.GetSubjectId(), this.User.GetDisplayName())); @@ -296,7 +295,15 @@ public async Task Logout(LogoutInputModel model) return this.SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); } - return this.View("LoggedOut", vm); + if (this.webSettings.IsPasswordUpdate) + { + var redirectUri = $"{this.webSettings.LearningHubWebClient}Home/ChangePasswordAcknowledgement"; + return this.Redirect(redirectUri); + } + else + { + return this.View("LoggedOut", vm); + } } /// diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/HomeController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/HomeController.cs index 7855fea..aa0937f 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/HomeController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/HomeController.cs @@ -80,6 +80,27 @@ public async Task Error() return this.View("Error"); } + /// + /// IsPasswordUpdateMethod. + /// + /// The Logout. + /// The . + [HttpGet] + public IActionResult SetIsPasswordUpdate(bool isLogout) + { + if (isLogout) + { + this.webSettings.IsPasswordUpdate = false; + } + else + { + this.webSettings.IsPasswordUpdate = true; + } + + var redirectUri = $"{this.webSettings.LearningHubWebClient}Home/UserLogout"; + return this.Redirect(redirectUri); + } + /// /// Shows the HealthCheck response. /// diff --git a/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs b/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs new file mode 100644 index 0000000..1bfa669 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs @@ -0,0 +1,104 @@ +namespace LearningHub.Nhs.Auth.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + + /// + /// Defines the . + /// + public class InMemoryTicketStore : ITicketStore + { + private readonly ConcurrentDictionary cache; + + /// + /// Initializes a new instance of the class. + /// The InMemoryTicketStore. + /// + /// the cache. + public InMemoryTicketStore(ConcurrentDictionary cache) + { + this.cache = cache; + } + + /// + /// The StoreAsync. + /// + /// The ticket. + /// The key. + public async Task StoreAsync(AuthenticationTicket ticket) + { + var ticketUserId = ticket.Principal.Claims.Where(c => c.Type == "sub") + .FirstOrDefault() + .Value; + var matchingAuthTicket = this.cache.Values.FirstOrDefault( + t => t.Principal.Claims.FirstOrDefault( + c => c.Type == "sub" + && c.Value == ticketUserId) != null); + if (matchingAuthTicket != null) + { + var cacheKey = this.cache.Where( + entry => entry.Value == matchingAuthTicket) + .Select(entry => entry.Key) + .FirstOrDefault(); + this.cache.TryRemove( + cacheKey, + out _); + } + + var key = Guid + .NewGuid() + .ToString(); + await this.RenewAsync( + key, + ticket); + return key; + } + + /// + /// The RenewAsync. + /// + /// The key. + /// The ticket. + /// The Task. + public Task RenewAsync( + string key, + AuthenticationTicket ticket) + { + this.cache.AddOrUpdate( + key, + ticket, + (_, _) => ticket); + return Task.CompletedTask; + } + + /// + /// The RetrieveAsync. + /// + /// The Key. + /// The Task. + public Task RetrieveAsync(string key) + { + this.cache.TryGetValue( + key, + out var ticket); + return Task.FromResult(ticket); + } + + /// + /// The RemoveAsync. + /// + /// The key. + /// The Task. + public Task RemoveAsync(string key) + { + this.cache.TryRemove( + key, + out _); + return Task.CompletedTask; + } + } + } diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 6aaf2f5..0a08288 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -1,10 +1,12 @@ namespace LearningHub.Nhs.Auth { using System; + using System.Collections.Concurrent; using System.Security.Cryptography.X509Certificates; using Azure.Identity; using IdentityServer4; using LearningHub.Nhs.Auth.Configuration; + using LearningHub.Nhs.Auth.Helpers; using LearningHub.Nhs.Auth.Middleware; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Enums; @@ -70,7 +72,13 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }).AddCookie().AddOpenIdConnect( + }) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.AccessDeniedPath = "/Home/AccessDenied"; + options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); + }) + .AddOpenIdConnect( "oidc_oa", options => { diff --git a/Auth/LearningHub.Nhs.Auth/appsettings.json b/Auth/LearningHub.Nhs.Auth/appsettings.json index f746802..c54a92d 100644 --- a/Auth/LearningHub.Nhs.Auth/appsettings.json +++ b/Auth/LearningHub.Nhs.Auth/appsettings.json @@ -39,9 +39,8 @@ "ElfhHub": "", "Rcr": "", "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", - "SupportFeedbackForm": "https://forms.office.com/e/C8tteweEhG" - - + "SupportFeedbackForm": "https://forms.office.com/e/C8tteweEhG", + "IsPasswordUpdate": "false" }, "AllowOpenAthensDebug": false, "OaLhClients": { diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index e89e70b..8a6e0aa 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -10,6 +10,7 @@ var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger(); +var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"; try { logger.Debug("Log Started"); @@ -36,6 +37,17 @@ c.SwaggerEndpoint($"/swagger/{app.Configuration["Swagger:Title"]}/swagger.json", app.Configuration["Swagger:Version"]); }); + app.Use(async (context, next) => + { + context.Response.Headers.Add("content-security-policy", csp); + context.Response.Headers.Add("Referrer-Policy", "no-referrer"); + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + context.Response.Headers.Add("X-XSS-protection", "0"); + await next(); + }); + app.UseMiddleware(); app.UseEndpoints(endpoints => endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"));