From 4ccfe6e13387791c92efb036147b2021268d666d Mon Sep 17 00:00:00 2001 From: binon Date: Fri, 14 Feb 2025 10:59:10 +0000 Subject: [PATCH 01/40] Configuring OpenID Connect Authentication with moodle user id --- .../Configuration/ServiceMappings.cs | 10 ++ .../Interfaces/IMoodleApiService.cs | 17 +++ .../Interfaces/IMoodleHttpClient.cs | 23 ++++ .../Models/MoodleUserResponseViewModel.cs | 121 ++++++++++++++++++ .../Services/MoodleApiService.cs | 64 +++++++++ .../Services/MoodleHttpClient.cs | 87 +++++++++++++ .../UserServices/LearningHubProfileService.cs | 14 +- Auth/LearningHub.Nhs.Auth/appsettings.json | 5 + 8 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleApiService.cs create mode 100644 Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleHttpClient.cs create mode 100644 Auth/LearningHub.Nhs.Auth/Models/MoodleUserResponseViewModel.cs create mode 100644 Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs create mode 100644 Auth/LearningHub.Nhs.Auth/Services/MoodleHttpClient.cs diff --git a/Auth/LearningHub.Nhs.Auth/Configuration/ServiceMappings.cs b/Auth/LearningHub.Nhs.Auth/Configuration/ServiceMappings.cs index 064aa10..79a6731 100644 --- a/Auth/LearningHub.Nhs.Auth/Configuration/ServiceMappings.cs +++ b/Auth/LearningHub.Nhs.Auth/Configuration/ServiceMappings.cs @@ -40,12 +40,22 @@ public static void AddServiceMappings(this IServiceCollection services, IConfigu ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); + + services.AddHttpClient() + .ConfigurePrimaryHttpMessageHandler( + () => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }); } else { services.AddHttpClient(); + services.AddHttpClient(); } + services.AddScoped(); services.AddDistributedMemoryCache(); services.AddScoped(); services.AddTransient(); diff --git a/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleApiService.cs b/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleApiService.cs new file mode 100644 index 0000000..81a3adc --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleApiService.cs @@ -0,0 +1,17 @@ +namespace LearningHub.Nhs.Auth.Interfaces +{ + using System.Threading.Tasks; + + /// + /// IMoodleApiService. + /// + public interface IMoodleApiService + { + /// + /// GetResourcesAsync. + /// + /// The current User Id. + /// A representing the result of the asynchronous operation. + Task GetMoodleUserIdByUsernameAsync(int currentUserId); + } +} diff --git a/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleHttpClient.cs b/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleHttpClient.cs new file mode 100644 index 0000000..1d711b8 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleHttpClient.cs @@ -0,0 +1,23 @@ +namespace LearningHub.Nhs.Auth.Interfaces +{ + using System.Net.Http; + using System.Threading.Tasks; + + /// + /// The Moodle Http Client interface. + /// + public interface IMoodleHttpClient + { + /// + /// The get cient async. + /// + /// The . + Task GetClient(); + + /// + /// GetDefaultParameters. + /// + /// defaultParameters. + string GetDefaultParameters(); + } +} diff --git a/Auth/LearningHub.Nhs.Auth/Models/MoodleUserResponseViewModel.cs b/Auth/LearningHub.Nhs.Auth/Models/MoodleUserResponseViewModel.cs new file mode 100644 index 0000000..fac745f --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Models/MoodleUserResponseViewModel.cs @@ -0,0 +1,121 @@ +namespace LearningHub.Nhs.Auth.Models +{ + using System.Collections.Generic; + + /// + /// MoodleUserResponseViewModel. + /// + public class MoodleUserResponseViewModel + { + /// + /// Gets or sets the list of users. + /// + public List Users { get; set; } + + /// + /// Gets or sets the warnings. + /// + public List Warnings { get; set; } + + /// + /// MoodleUser. + /// + public class MoodleUser + { + /// + /// Gets or sets the user ID. + /// + public int Id { get; set; } + + /// + /// Gets or sets the username. + /// + public string Username { get; set; } + + /// + /// Gets or sets the first name. + /// + public string FirstName { get; set; } + + /// + /// Gets or sets the last name. + /// + public string LastName { get; set; } + + /// + /// Gets or sets the full name. + /// + public string FullName { get; set; } + + /// + /// Gets or sets the email. + /// + public string Email { get; set; } + + /// + /// Gets or sets the department. + /// + public string Department { get; set; } + + /// + /// Gets or sets the first access timestamp. + /// + public long FirstAccess { get; set; } + + /// + /// Gets or sets the last access timestamp. + /// + public long LastAccess { get; set; } + + /// + /// Gets or sets the authentication method. + /// + public string Auth { get; set; } + + /// + /// Gets or sets a value indicating whether the user is suspended. + /// + public bool Suspended { get; set; } + + /// + /// Gets or sets a value indicating whether the user is confirmed. + /// + public bool Confirmed { get; set; } + + /// + /// Gets or sets the language. + /// + public string Lang { get; set; } + + /// + /// Gets or sets the theme. + /// + public string Theme { get; set; } + + /// + /// Gets or sets the timezone. + /// + public string Timezone { get; set; } + + /// + /// Gets or sets the mail format. + /// + public int MailFormat { get; set; } + + /// + /// Gets or sets the forum tracking preference. + /// + public int TrackForums { get; set; } + + /// + /// Gets or sets the small profile image URL. + /// + public string ProfileImageUrlSmall { get; set; } + + /// + /// Gets or sets the profile image URL. + /// + public string ProfileImageUrl { get; set; } + } + } +} diff --git a/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs b/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs new file mode 100644 index 0000000..3ca059c --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs @@ -0,0 +1,64 @@ +namespace LearningHub.Nhs.Auth.Services +{ + using System; + using System.Net.Http; + using System.Threading.Tasks; + using LearningHub.Nhs.Auth.Interfaces; + using LearningHub.Nhs.Auth.Models; + using Newtonsoft.Json; + + /// + /// MoodleApiService. + /// + public class MoodleApiService : IMoodleApiService + { + private readonly IMoodleHttpClient moodleHttpClient; + + /// + /// Initializes a new instance of the class. + /// + /// moodleHttpClient. + public MoodleApiService(IMoodleHttpClient moodleHttpClient) + { + this.moodleHttpClient = moodleHttpClient; + } + + /// + /// GetMoodleUserIdByUsernameAsync. + /// + /// current User Id. + /// UserId from Moodle. + public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) + { + int moodleUserId = 0; + string additionalParameters = $"&criteria[0][key]=username&criteria[0][value]={currentUserId}"; + string defaultParameters = this.moodleHttpClient.GetDefaultParameters(); + + var client = await this.moodleHttpClient.GetClient(); + + string url = $"&wsfunction=core_user_get_users{additionalParameters}"; + + HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url); + if (response.IsSuccessStatusCode) + { + var result = response.Content.ReadAsStringAsync().Result; + var viewmodel = JsonConvert.DeserializeObject(result); + + foreach (var user in viewmodel.Users) + { + if (user.Username == currentUserId.ToString()) + { + moodleUserId = user.Id; + } + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || + response.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return moodleUserId; + } + } +} diff --git a/Auth/LearningHub.Nhs.Auth/Services/MoodleHttpClient.cs b/Auth/LearningHub.Nhs.Auth/Services/MoodleHttpClient.cs new file mode 100644 index 0000000..5538136 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Services/MoodleHttpClient.cs @@ -0,0 +1,87 @@ +namespace LearningHub.Nhs.Auth.Services +{ + using System; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using LearningHub.Nhs.Auth.Interfaces; + using Microsoft.Extensions.Configuration; + + /// + /// The moodle http client. + /// + public class MoodleHttpClient : IMoodleHttpClient, IDisposable + { + private readonly HttpClient httpClient = new (); + private bool initialised = false; + private string moodleAPIBaseUrl; + private string moodleAPIMoodleWSRestFormat; + private string moodleAPIWSToken; + + /// + /// Initializes a new instance of the class. + /// + /// httpClient. + /// config. + public MoodleHttpClient(HttpClient httpClient, IConfiguration config) + { + this.httpClient = httpClient; + this.moodleAPIBaseUrl = config["MoodleAPIConfig:BaseUrl"]; + this.moodleAPIMoodleWSRestFormat = config["MoodleAPIConfig:MoodleWSRestFormat"]; + this.moodleAPIWSToken = config["MoodleAPIConfig:WSToken"]; + } + + /// + /// The Get Client method. + /// + /// The . + public async Task GetClient() + { + this.Initialise(this.moodleAPIBaseUrl); + return this.httpClient; + } + + /// + /// GetDefaultParameters. + /// + /// defaultParameters. + public string GetDefaultParameters() + { + string defaultParameters = $"wstoken={this.moodleAPIWSToken}" + + $"&moodlewsrestformat={this.moodleAPIMoodleWSRestFormat}"; + + return defaultParameters; + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// The dispoase. + /// + /// disposing. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + this.httpClient.Dispose(); + } + } + + private void Initialise(string httpClientUrl) + { + if (this.initialised == false) + { + this.httpClient.BaseAddress = new Uri(httpClientUrl); + this.httpClient.DefaultRequestHeaders.Accept.Clear(); + this.httpClient.DefaultRequestHeaders.Accept.Add( + new MediaTypeWithQualityHeaderValue("application/json")); + this.initialised = true; + } + } + } +} diff --git a/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs b/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs index 5a3a602..36b41b0 100644 --- a/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs +++ b/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs @@ -20,12 +20,16 @@ public class LearningHubProfileService : IProfileService /// /// The user service. /// + /// + /// The moodle api service. + /// /// /// The logger. /// - public LearningHubProfileService(IUserService userService, ILogger logger) + public LearningHubProfileService(IUserService userService, IMoodleApiService moodleApiService, ILogger logger) { this.UserService = userService; + this.MoodleApiService = moodleApiService; this.Logger = logger; } @@ -39,6 +43,11 @@ public LearningHubProfileService(IUserService userService, ILogger protected IUserService UserService { get; } + /// + /// Gets the moodle api service. + /// + protected IMoodleApiService MoodleApiService { get; } + /// /// The get profile data async. /// @@ -53,6 +62,8 @@ public async Task GetProfileDataAsync(ProfileDataRequestContext context) if (context != null) { var user = await this.UserService.GetBasicUserByUserIdAsync(context.Subject.GetSubjectId()); + var moodleUser = await this.MoodleApiService.GetMoodleUserIdByUsernameAsync(user.Id); + var roleName = await this.UserService.GetUserRoleAsync(user.Id); var claims = new List @@ -63,6 +74,7 @@ public async Task GetProfileDataAsync(ProfileDataRequestContext context) new Claim("family_name", user.LastName), new Claim("role", roleName), new Claim("elfh_userName", user.UserName), + new Claim("preferred_username", moodleUser.ToString()), }; if (context.Subject.HasClaim("openAthensUser", "true")) diff --git a/Auth/LearningHub.Nhs.Auth/appsettings.json b/Auth/LearningHub.Nhs.Auth/appsettings.json index 1565fa4..f6476a7 100644 --- a/Auth/LearningHub.Nhs.Auth/appsettings.json +++ b/Auth/LearningHub.Nhs.Auth/appsettings.json @@ -51,6 +51,11 @@ "3584C984-028C-4002-AA4B-58AF665AEBDD": "", "LearningHubOAClient": "" }, + "MoodleAPIConfig": { + "BaseUrl": "", + "MoodleWSRestFormat": "json", + "WSToken": "" + }, "OaScopes": [ "" ], "AuthKey": "", "AuthOrigin": "", From d784fda8475f699bb5fff678f27b5d3767179405 Mon Sep 17 00:00:00 2001 From: Colin Beeby Date: Tue, 25 Feb 2025 15:21:38 +0000 Subject: [PATCH 02/40] Added moodle client configuration to appsettings.json --- Auth/LearningHub.Nhs.Auth/appsettings.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Auth/LearningHub.Nhs.Auth/appsettings.json b/Auth/LearningHub.Nhs.Auth/appsettings.json index 1565fa4..c1aa5d7 100644 --- a/Auth/LearningHub.Nhs.Auth/appsettings.json +++ b/Auth/LearningHub.Nhs.Auth/appsettings.json @@ -181,6 +181,23 @@ "RequireConsent": false, "RequirePkce": true, "AllowOfflineAccess": true + }, + "moodle": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "authorization_code" ], + "RedirectUris": [ "/login/oidc/" ], + "PostLogoutUris": [ "/login/logout.php" ], + "AllowedScopes": [ "openid", "profile", "learninghubapi", "userapi", "roles", "learningcredentialsapi" ], + "BackChannelLogoutSessionRequired": true, + "BackChannelLogoutUri": "/login/logout.php", + "FrontChannelLogoutSessionRequired": true, + "FrontChannelLogoutUri": "/login/logout.php", + "UpdateAccessTokenClaimsOnRefresh": true, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": true } }, "IdsClients": { From f8b8d3d61dbaeae6be751dfc45dce8dc27e08d9f Mon Sep 17 00:00:00 2001 From: Colin Beeby Date: Thu, 27 Feb 2025 10:05:49 +0000 Subject: [PATCH 03/40] Corrected RedirectUris for moodle client --- Auth/LearningHub.Nhs.Auth/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Auth/LearningHub.Nhs.Auth/appsettings.json b/Auth/LearningHub.Nhs.Auth/appsettings.json index c1aa5d7..f746802 100644 --- a/Auth/LearningHub.Nhs.Auth/appsettings.json +++ b/Auth/LearningHub.Nhs.Auth/appsettings.json @@ -187,7 +187,7 @@ "ClientName": "", "ClientSecret": "", "AllowedGrantTypes": [ "authorization_code" ], - "RedirectUris": [ "/login/oidc/" ], + "RedirectUris": [ "/auth/oidc/" ], "PostLogoutUris": [ "/login/logout.php" ], "AllowedScopes": [ "openid", "profile", "learninghubapi", "userapi", "roles", "learningcredentialsapi" ], "BackChannelLogoutSessionRequired": true, From ccf2104d6ba5003a60b81b5eec92ca7219b2a5d7 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Fri, 28 Feb 2025 11:21:01 +0000 Subject: [PATCH 04/40] TD-3732: Missing Security Headers --- Auth/LearningHub.Nhs.Auth/Program.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Auth/LearningHub.Nhs.Auth/Program.cs b/Auth/LearningHub.Nhs.Auth/Program.cs index 04759f5..910cb0b 100644 --- a/Auth/LearningHub.Nhs.Auth/Program.cs +++ b/Auth/LearningHub.Nhs.Auth/Program.cs @@ -56,6 +56,20 @@ await next(); }); +app.Use(async (context, next) => +{ + // Add security headers + context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); + context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); + context.Response.Headers.Add("X-Frame-Options", "DENY"); + context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';"); + context.Response.Headers.Add("Referrer-Policy", "no-referrer-when-downgrade"); + context.Response.Headers.Add("Feature-Policy", "geolocation 'self'; microphone 'none'; camera 'none'"); + + await next(); +}); + if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); From 11526c1a09db81675acf8f0cceab1c3ed38c0235 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 5 Mar 2025 11:30:45 +0000 Subject: [PATCH 05/40] TD-3731: Password Change Does Not Invalidate Current Session --- .../Configuration/WebSettings.cs | 5 ++++ .../Controllers/AccountController.cs | 23 ++++++++++++------- .../Controllers/HomeController.cs | 21 +++++++++++++++++ Auth/LearningHub.Nhs.Auth/appsettings.json | 5 ++-- 4 files changed, 43 insertions(+), 11 deletions(-) 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/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": { From f898a6f05f4088fae75b607427f46127361ac8aa Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 5 Mar 2025 14:36:28 +0000 Subject: [PATCH 06/40] TD-3733:Information Disclosures --- Auth/LearningHub.Nhs.Auth/Program.cs | 14 -------------- LearningHub.Nhs.UserApi/Program.cs | 12 ++++++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Program.cs b/Auth/LearningHub.Nhs.Auth/Program.cs index 910cb0b..04759f5 100644 --- a/Auth/LearningHub.Nhs.Auth/Program.cs +++ b/Auth/LearningHub.Nhs.Auth/Program.cs @@ -56,20 +56,6 @@ await next(); }); -app.Use(async (context, next) => -{ - // Add security headers - context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); - context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); - context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); - context.Response.Headers.Add("X-Frame-Options", "DENY"); - context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'; object-src 'none';"); - context.Response.Headers.Add("Referrer-Policy", "no-referrer-when-downgrade"); - context.Response.Headers.Add("Feature-Policy", "geolocation 'self'; microphone 'none'; camera 'none'"); - - await next(); -}); - if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); 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?}")); From 76c7bdae563f4b50e76732de6035eccf74c4852f Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 10 Mar 2025 17:15:53 +0000 Subject: [PATCH 07/40] TD-5406: Errors are not Getting Logged in Learning Hub Prod logging Database --- Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj | 2 +- LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj index 15c031f..01041c3 100644 --- a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj +++ b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj @@ -113,7 +113,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj index 4c32a0f..50f8813 100644 --- a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj +++ b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 0474fd75d21879446f2db0347c94a8f3b01baeae Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 13 Mar 2025 14:26:09 +0000 Subject: [PATCH 08/40] TD-3743: Concurrent Sessions Allowed --- .../Helpers/InMemoryTicketStore.cs | 104 ++++++++++++++++++ .../ServiceCollectionExtension.cs | 10 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs 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 => { From 455d035a26407ef03b404c8619418388c13bea8d Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 17 Mar 2025 09:30:22 +0000 Subject: [PATCH 09/40] PR to test error logging in test environment --- Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index ff7b352..ecb36c0 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -82,6 +82,12 @@ public AccountController( [HttpGet] public async Task Login(string returnUrl) { + returnUrl = null; + if (string.IsNullOrWhiteSpace(returnUrl)) + { + throw new Exception("ClientId or origin are empty."); + } + // Use internal login page // build a model so we know what to show on the login page var vm = await this.BuildLoginViewModelAsync(returnUrl); From 8eca96089b9f1eaed085c9949bc8ebdbc2877395 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 17 Mar 2025 09:57:39 +0000 Subject: [PATCH 10/40] Reverted the test code --- Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index ecb36c0..ff7b352 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -82,12 +82,6 @@ public AccountController( [HttpGet] public async Task Login(string returnUrl) { - returnUrl = null; - if (string.IsNullOrWhiteSpace(returnUrl)) - { - throw new Exception("ClientId or origin are empty."); - } - // Use internal login page // build a model so we know what to show on the login page var vm = await this.BuildLoginViewModelAsync(returnUrl); From 6200dee0121dd9c792a9959ff38158fae7b38427 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Tue, 18 Mar 2025 15:25:55 +0000 Subject: [PATCH 11/40] CSP updated --- LearningHub.Nhs.UserApi/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 8a6e0aa..24463ca 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -39,7 +39,7 @@ app.Use(async (context, next) => { - context.Response.Headers.Add("content-security-policy", csp); + ////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"); From d6deb8738ad05f2c09b64f9d01c5f7d469246c96 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Tue, 18 Mar 2025 15:55:55 +0000 Subject: [PATCH 12/40] Reverted CSP fixes --- LearningHub.Nhs.UserApi/Program.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 24463ca..f0a8346 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -37,16 +37,16 @@ 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.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(); From 969b770f15f616aae71a3a446b6cd39ef864dbe2 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 11:08:06 +0000 Subject: [PATCH 13/40] Reverted in memory store --- Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 0a08288..6268047 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -73,11 +73,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => - { - options.AccessDeniedPath = "/Home/AccessDenied"; - options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); - }) + .AddCookie() .AddOpenIdConnect( "oidc_oa", options => From cb03bf16db35cf6d967273b7c5204c15a77097c3 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 11:11:09 +0000 Subject: [PATCH 14/40] reverted --- LearningHub.Nhs.UserApi/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index f0a8346..1a6246f 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -10,7 +10,6 @@ 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"); From 72b8cc07af0a4c71fb62d796342dbf1ac9c620ad Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 12:53:27 +0000 Subject: [PATCH 15/40] Test --- Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs index 0dd0fc4..4953b75 100644 --- a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs +++ b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs @@ -33,7 +33,7 @@ public override void OnResultExecuting(ResultExecutingContext context) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy ////var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"; + var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; //// also consider adding upgrade-insecure-requests once you have HTTPS in place for production ////csp += "upgrade-insecure-requests;"; //// also an example if you need client images to be displayed from twitter From 294deb73fa47c5f2adc473fbbb6b382ce463b2be Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 14:39:04 +0000 Subject: [PATCH 16/40] Revert "reverted" This reverts commit cb03bf16db35cf6d967273b7c5204c15a77097c3. --- LearningHub.Nhs.UserApi/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 1a6246f..f0a8346 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"); From 11714d9d591daafacf312f597536af9df96015c8 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 14:40:54 +0000 Subject: [PATCH 17/40] Revert "Test" This reverts commit 72b8cc07af0a4c71fb62d796342dbf1ac9c620ad. --- Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs index 4953b75..0dd0fc4 100644 --- a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs +++ b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs @@ -33,7 +33,7 @@ public override void OnResultExecuting(ResultExecutingContext context) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy ////var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; + var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"; //// also consider adding upgrade-insecure-requests once you have HTTPS in place for production ////csp += "upgrade-insecure-requests;"; //// also an example if you need client images to be displayed from twitter From a6fe18a3f57377e177c13b71506672789905688d Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 16:55:54 +0000 Subject: [PATCH 18/40] Added in store memmory ticket store --- Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 6268047..632c087 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -73,7 +73,11 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddCookie() + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.AccessDeniedPath = "/Home/AccessDenied"; + options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); + }) .AddOpenIdConnect( "oidc_oa", options => From 79a4bbc14348156914f2fea2767ec7287b5a5276 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 17:14:33 +0000 Subject: [PATCH 19/40] Reverted the chnages --- Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 632c087..6268047 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -73,11 +73,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => - { - options.AccessDeniedPath = "/Home/AccessDenied"; - options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); - }) + .AddCookie() .AddOpenIdConnect( "oidc_oa", options => From fb344cc8a92123edcb8a277c86e4db2cd8c9173f Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Wed, 19 Mar 2025 17:55:07 +0000 Subject: [PATCH 20/40] CSP code added --- LearningHub.Nhs.UserApi/Program.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index f0a8346..8a6e0aa 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -37,16 +37,16 @@ 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.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(); From 2103eea4da0176b418ba849b941011cd2d0f737d Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 20 Mar 2025 10:08:28 +0000 Subject: [PATCH 21/40] Removed Sandbox --- Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs | 2 +- LearningHub.Nhs.UserApi/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs index 0dd0fc4..4953b75 100644 --- a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs +++ b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs @@ -33,7 +33,7 @@ public override void OnResultExecuting(ResultExecutingContext context) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy ////var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"; + var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; //// also consider adding upgrade-insecure-requests once you have HTTPS in place for production ////csp += "upgrade-insecure-requests;"; //// also an example if you need client images to be displayed from twitter diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 8a6e0aa..15fad9d 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -10,7 +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';"; +var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; try { logger.Debug("Log Started"); From b7c7090389954bf0c1e6f00f52167bcc17fee1ee Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 20 Mar 2025 12:12:38 +0000 Subject: [PATCH 22/40] Added in memory ticket store --- Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 6268047..632c087 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -73,7 +73,11 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddCookie() + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.AccessDeniedPath = "/Home/AccessDenied"; + options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); + }) .AddOpenIdConnect( "oidc_oa", options => From 610f1a5bb583b97e9e1a63839489c4c071d3656b Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 20 Mar 2025 14:06:57 +0000 Subject: [PATCH 23/40] Reverted in memory ticket store from RC --- .../Filters/SecurityHeadersAttribute.cs | 2 +- Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs | 6 +----- LearningHub.Nhs.UserApi/Program.cs | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs index 4953b75..0dd0fc4 100644 --- a/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs +++ b/Auth/LearningHub.Nhs.Auth/Filters/SecurityHeadersAttribute.cs @@ -33,7 +33,7 @@ public override void OnResultExecuting(ResultExecutingContext context) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy ////var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; + var csp = "object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts allow-popups; base-uri 'self';"; //// also consider adding upgrade-insecure-requests once you have HTTPS in place for production ////csp += "upgrade-insecure-requests;"; //// also an example if you need client images to be displayed from twitter diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 632c087..6268047 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -73,11 +73,7 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) - .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => - { - options.AccessDeniedPath = "/Home/AccessDenied"; - options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); - }) + .AddCookie() .AddOpenIdConnect( "oidc_oa", options => diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 15fad9d..8a6e0aa 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -10,7 +10,7 @@ var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger(); -var csp = "object-src 'none'; frame-ancestors 'none'; base-uri 'self';"; +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"); From 2d3f7b18efcb439764bcee702045d89e908bbf35 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Fri, 4 Apr 2025 11:12:22 +0100 Subject: [PATCH 24/40] TD-3734: Concurrent Sessions Allowed --- .../LearningHub.Nhs.Auth.Tests.csproj | 2 +- .../Controllers/AccountController.cs | 83 ++++++++++++++----- .../Interfaces/IUserService.cs | 7 ++ .../LearningHub.Nhs.Auth.csproj | 2 +- .../Services/UserService.cs | 24 ++++++ .../IUserHistoryRepository.cs | 7 ++ ...ub.Nhs.UserApi.Repository.Interface.csproj | 2 +- .../LearningHub.Nhs.UserApi.Repository.csproj | 2 +- .../UserHistoryRepository.cs | 24 +++++- .../IUserHistoryService.cs | 7 ++ ...gHub.Nhs.UserAPI.Services.Interface.csproj | 2 +- ...gHub.Nhs.UserApi.Services.UnitTests.csproj | 2 +- .../LearningHub.Nhs.UserApi.Services.csproj | 2 +- .../UserHistoryService.cs | 13 +++ .../LearningHub.Nhs.UserApi.Shared.csproj | 2 +- .../LearningHub.Nhs.UserApi.UnitTests.csproj | 2 +- .../Controllers/UserHistoryController.cs | 13 +++ .../LearningHub.Nhs.UserApi.csproj | 2 +- 18 files changed, 168 insertions(+), 30 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj b/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj index e52462d..ec6d6ad 100644 --- a/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj +++ b/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj @@ -9,7 +9,7 @@ - + diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index 123fe1a..847b5a7 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; + using Azure.Core; using elfhHub.Nhs.Models.Common; using elfhHub.Nhs.Models.Enums; using IdentityModel; @@ -22,9 +23,11 @@ using LearningHub.Nhs.Models.Common; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using UAParser; /// /// Account Controller operations. @@ -163,34 +166,76 @@ await this.interaction.GrantConsentAsync( if (loginResult.IsAuthenticated) { - await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); - - if (context != null) + var uaParser = Parser.GetDefault(); + var clientInfo = uaParser.Parse(this.Request.Headers["User-Agent"]); + var result = await this.UserService.CheckUserHasAnActiveSessionAsync(userId); + if (result.Items.Count == 0 || result.Items[0].BrowserName == clientInfo.UA.Family) { - if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) + await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); + + if (context != null) { - // if the client is PKCE then we assume it's native, so this change in how to - // return the response is for better UX for the end user. - return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); + if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) + { + // if the client is PKCE then we assume it's native, so this change in how to + // return the response is for better UX for the end user. + return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return this.Redirect(model.ReturnUrl); } - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return this.Redirect(model.ReturnUrl); - } - - // request for a local page - if (this.Url.IsLocalUrl(model.ReturnUrl)) - { - return this.Redirect(model.ReturnUrl); + // request for a local page + if (this.Url.IsLocalUrl(model.ReturnUrl)) + { + return this.Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return this.Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } } - else if (string.IsNullOrEmpty(model.ReturnUrl)) + else if (result.Items[0].BrowserName == clientInfo.UA.Family) { - return this.Redirect("~/"); + await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); + + if (context != null) + { + if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) + { + // if the client is PKCE then we assume it's native, so this change in how to + // return the response is for better UX for the end user. + return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return this.Redirect(model.ReturnUrl); + } + + // request for a local page + if (this.Url.IsLocalUrl(model.ReturnUrl)) + { + return this.Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return this.Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } } else { - // user might have clicked on a malicious link - should be logged - throw new Exception("invalid return URL"); + this.ModelState.AddModelError(string.Empty, "already active session"); } } else if (userId > 0) diff --git a/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs b/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs index 3a97b47..e4ae21c 100644 --- a/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs +++ b/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs @@ -114,6 +114,13 @@ public interface IUserService /// Task StoreUserHistoryAsync(UserHistoryViewModel userHistory); + /// + /// check user has an laredy active session. + /// + /// The userId. + /// The . + Task> CheckUserHasAnActiveSessionAsync(int userId); + /// /// The store user history async. /// diff --git a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj index 01041c3..2933aee 100644 --- a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj +++ b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj @@ -101,7 +101,7 @@ - + diff --git a/Auth/LearningHub.Nhs.Auth/Services/UserService.cs b/Auth/LearningHub.Nhs.Auth/Services/UserService.cs index 2ba58b7..98246ec 100644 --- a/Auth/LearningHub.Nhs.Auth/Services/UserService.cs +++ b/Auth/LearningHub.Nhs.Auth/Services/UserService.cs @@ -243,5 +243,29 @@ public async Task StoreUserHistoryAsync(UserHistoryViewModel userHistory) } } } + + /// + public async Task> CheckUserHasAnActiveSessionAsync(int userId) + { + PagedResultSet userHistoryViewModel = new PagedResultSet(); + + var client = this.UserApiHttpClient.GetClient(); + var request = $"UserHistory/CheckUserHasActiveSession/{userId}"; + var response = await client.GetAsync(request).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + userHistoryViewModel = JsonConvert.DeserializeObject>(result); + } + else if (response.StatusCode == HttpStatusCode.Unauthorized + || + response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("AccessDenied"); + } + + return userHistoryViewModel; + } } } diff --git a/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs b/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs index e72aebf..8684ba8 100644 --- a/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs @@ -59,5 +59,12 @@ public interface IUserHistoryRepository /// The . /// Task GetPagedByUserIdAsync(int userId, int startPage, int pageSize); + + /// + /// Check user has an active login session. + /// + /// The userId. + /// The . + Task CheckUserHasActiveSessionAsync(int userId); } } \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj b/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj index fc69964..29a3a90 100644 --- a/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj +++ b/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj @@ -8,7 +8,7 @@ - + all diff --git a/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj b/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj index 4644295..5921669 100644 --- a/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj +++ b/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj @@ -8,7 +8,7 @@ - + diff --git a/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs b/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs index 673a3ae..3f0386b 100644 --- a/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs @@ -9,6 +9,7 @@ using LearningHub.Nhs.UserApi.Repository.Interface; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; + using Newtonsoft.Json.Linq; /// /// The user history repository. @@ -66,11 +67,13 @@ public async Task CreateAsync(int userId, int tenantId, UserHistoryViewModel use new SqlParameter("@LoginIP", SqlDbType.VarChar) { Value = userHistoryVM.LoginIP ?? (object)DBNull.Value }, new SqlParameter("@LoginSuccessFul", SqlDbType.Bit) { Value = userHistoryVM.LoginSuccessFul ?? (object)DBNull.Value }, new SqlParameter("@TenantId", SqlDbType.Int) { Value = tenantId }, + new SqlParameter("@SessionId", SqlDbType.VarChar) { Value = (userHistoryVM.UserHistoryTypeId == 0 && userHistoryVM.Detail == "User logged on. Source of auth: LearningHub.Nhs.Auth Account\\Login") ? userHistoryVM.SessionId : (object)DBNull.Value }, + new SqlParameter("@IsActive", SqlDbType.Bit) { Value = (userHistoryVM.UserHistoryTypeId == 0 && userHistoryVM.Detail == "User logged on. Source of auth: LearningHub.Nhs.Auth Account\\Login") ? userHistoryVM.IsActive : (object)DBNull.Value }, new SqlParameter("@AmendUserId", SqlDbType.Int) { Value = userId }, new SqlParameter("@AmendDate", SqlDbType.DateTimeOffset) { Value = DateTimeOffset.Now }, }; - string sql = "proc_UserHistoryInsert @UserId, @UserHistoryTypeId, @Detail, @UserAgent, @BrowserName, @BrowserVersion, @UrlReferer, @LoginIP, @LoginSuccessFul, @TenantId, @AmendUserId, @AmendDate"; + string sql = "proc_UserHistoryInsert @UserId, @UserHistoryTypeId, @Detail, @UserAgent, @BrowserName, @BrowserVersion, @UrlReferer, @LoginIP, @LoginSuccessFul, @TenantId, @SessionId, @IsActive, @AmendUserId, @AmendDate"; await this.DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); } @@ -98,5 +101,24 @@ public async Task GetPagedByUserIdAsync(int userId return retVal; } + + /// + public async Task CheckUserHasActiveSessionAsync(int userId) + { + try + { + var retVal = new UserHistoryStoredProcResults(); + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; + + var result = await this.DbContext.Set().FromSqlRaw( + "dbo.proc_ActiveLearningHubUserbyId @p0", param0).AsNoTracking().ToListWithNoLockAsync(); + retVal.Results = result; + return retVal; + } + catch (Exception ex) + { + return null; + } + } } } \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs b/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs index 7ed5e01..9c6954b 100644 --- a/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs +++ b/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs @@ -53,5 +53,12 @@ public interface IUserHistoryService /// The . /// Task> GetUserHistoryPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); + + /// + /// Check user has an active login session. + /// + /// The userId. + /// The . + Task> CheckUserHasActiveSessionAsync(int userId); } } diff --git a/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj b/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj index fc69964..29a3a90 100644 --- a/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj +++ b/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj @@ -8,7 +8,7 @@ - + all diff --git a/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj b/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj index 78785b5..a61a661 100644 --- a/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj +++ b/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj b/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj index 0b91349..670491b 100644 --- a/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj +++ b/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj @@ -8,7 +8,7 @@ - + diff --git a/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs b/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs index c65377c..e6ef86c 100644 --- a/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs +++ b/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs @@ -1,5 +1,6 @@ namespace LearningHub.Nhs.UserApi.Services { + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -56,6 +57,8 @@ public async Task CreateAsync(UserHistoryViewModel if (retVal.IsValid) { + userHistoryVM.SessionId = Guid.NewGuid().ToString(); + userHistoryVM.IsActive = true; await this.userHistoryRepository.CreateAsync(userHistoryVM.UserId, this.settings.LearningHubTenantId, userHistoryVM); } @@ -99,6 +102,16 @@ public async Task> GetUserHistoryPageAsync( return result; } + /// + public async Task> CheckUserHasActiveSessionAsync(int userId) + { + PagedResultSet result = new PagedResultSet(); + var userHistory = await this.userHistoryRepository.CheckUserHasActiveSessionAsync(userId); + userHistory.Results.ForEach(x => x.UserAgent = this.ParseUserAgentString(x.UserAgent)); + result.Items = this.mapper.Map>(userHistory.Results); + return result; + } + private string ParseUserAgentString(string userAgent) { string retVal = string.Empty; diff --git a/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj b/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj index 149be25..ecfe311 100644 --- a/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj +++ b/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj @@ -8,7 +8,7 @@ - + all diff --git a/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj b/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj index fda3b53..5c215c5 100644 --- a/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj +++ b/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs b/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs index f86d769..d1436d4 100644 --- a/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs +++ b/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs @@ -100,6 +100,19 @@ public async Task GetUserHistoryPageAsync(int page, int pageSize, return this.Ok(pagedResultSet); } + /// + /// Check the user has an active login session. + /// + /// The UserId. + /// The . + [HttpGet] + [Route("CheckUserHasActiveSession/{userId}")] + public async Task CheckUserHasActiveSessionAsync(int userId) + { + PagedResultSet pagedResultSet = await this.userHistoryService.CheckUserHasActiveSessionAsync(userId); + return this.Ok(pagedResultSet); + } + /// /// Create a UserHistory. /// diff --git a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj index 50f8813..c707f5a 100644 --- a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj +++ b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj @@ -23,7 +23,7 @@ - + From 58ea79f9c3de350e57ed2a7432bc5db45fb6e56b Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Fri, 4 Apr 2025 11:36:04 +0100 Subject: [PATCH 25/40] Concurrent Sessions Allowed --- .../Controllers/AccountController.cs | 34 +------------------ .../Views/Account/AlreadyActiveSession.cshtml | 15 ++++++++ 2 files changed, 16 insertions(+), 33 deletions(-) create mode 100644 Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index 847b5a7..58f804d 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -201,41 +201,9 @@ await this.interaction.GrantConsentAsync( throw new Exception("invalid return URL"); } } - else if (result.Items[0].BrowserName == clientInfo.UA.Family) - { - await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); - - if (context != null) - { - if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) - { - // if the client is PKCE then we assume it's native, so this change in how to - // return the response is for better UX for the end user. - return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); - } - - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return this.Redirect(model.ReturnUrl); - } - - // request for a local page - if (this.Url.IsLocalUrl(model.ReturnUrl)) - { - return this.Redirect(model.ReturnUrl); - } - else if (string.IsNullOrEmpty(model.ReturnUrl)) - { - return this.Redirect("~/"); - } - else - { - // user might have clicked on a malicious link - should be logged - throw new Exception("invalid return URL"); - } - } else { - this.ModelState.AddModelError(string.Empty, "already active session"); + return this.View("AlreadyActiveSession"); } } else if (userId > 0) diff --git a/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml b/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml new file mode 100644 index 0000000..1f5d3b6 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml @@ -0,0 +1,15 @@ +@{ + ViewData["Title"] = "Already active session"; +} +
+
+
+
+

@ViewData["Title"]

+

You are already logged in from another browser. Please continue using the same browser or close the existing session and try again with a new one.

+

If you have any questions, please contact the support team.

+

@DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss")

+
+
+
+
\ No newline at end of file From e39319a5006e42543c98b6cbe059ad1f19241fa7 Mon Sep 17 00:00:00 2001 From: Binon Date: Mon, 7 Apr 2025 15:14:11 +0100 Subject: [PATCH 26/40] user null check --- Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs b/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs index 3ca059c..8edb52e 100644 --- a/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs +++ b/Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs @@ -44,11 +44,14 @@ public async Task GetMoodleUserIdByUsernameAsync(int currentUserId) var result = response.Content.ReadAsStringAsync().Result; var viewmodel = JsonConvert.DeserializeObject(result); - foreach (var user in viewmodel.Users) + if (viewmodel?.Users != null) { - if (user.Username == currentUserId.ToString()) + foreach (var user in viewmodel.Users) { - moodleUserId = user.Id; + if (user.Username == currentUserId.ToString()) + { + moodleUserId = user.Id; + } } } } From 429e9a44bd512a93c2a45b861025821957cfc808 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 7 Apr 2025 17:16:32 +0100 Subject: [PATCH 27/40] TD-5407: Implemented a fix to avoid SQL exception on LinkExistingUserToSso method --- .../LH/IExternalSystemUserRepository.cs | 8 +++++++ .../LH/ExternalSystemUserRepository.cs | 22 +++++++++++++++++++ .../RegistrationService.cs | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/LearningHub.Nhs.UserApi.Repository.Interface/LH/IExternalSystemUserRepository.cs b/LearningHub.Nhs.UserApi.Repository.Interface/LH/IExternalSystemUserRepository.cs index d84ded7..6f76875 100644 --- a/LearningHub.Nhs.UserApi.Repository.Interface/LH/IExternalSystemUserRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository.Interface/LH/IExternalSystemUserRepository.cs @@ -1,6 +1,7 @@ namespace LearningHub.Nhs.UserApi.Repository.Interface.LH { using System.Threading.Tasks; + using elfhHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.External; /// @@ -15,5 +16,12 @@ public interface IExternalSystemUserRepository : IGenericLHRepositoryThe external system id. /// The . Task GetByIdAsync(int userId, int externalSystemId); + + /// + /// Create External system user. + /// + /// The userExternalSystem. + /// The . + Task CreateExternalSystemUserAsync(ExternalSystemUser userExternalSystem); } } diff --git a/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs b/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs index 0e03129..c02b97d 100644 --- a/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs @@ -1,10 +1,15 @@ namespace LearningHub.Nhs.UserApi.Repository.LH { + using System; + using System.Collections.Generic; + using System.Data; using System.Linq; using System.Threading.Tasks; + using elfhHub.Nhs.Models.Entities; using LearningHub.Nhs.Models.Entities.External; using LearningHub.Nhs.UserApi.Repository; using LearningHub.Nhs.UserApi.Repository.Interface.LH; + using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; /// @@ -30,5 +35,22 @@ public async Task GetByIdAsync(int userId, int externalSyste .AsNoTracking() .FirstOrDefaultWithNoLockAsync(); } + + /// + public async Task CreateExternalSystemUserAsync(ExternalSystemUser userExternalSystem) + { + try + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userExternalSystem.UserId }; + var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = userExternalSystem.ExternalSystemId }; + var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = userExternalSystem.UserId }; + var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = this.TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; + await this.DbContext.Database.ExecuteSqlRawAsync("[external].ExternalSystemUserCreate @p0, @p1, @p2, @p3", param0, param1, param2, param3); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } } } diff --git a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs index ea3d67b..7dccbaf 100644 --- a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs +++ b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs @@ -185,7 +185,7 @@ public async Task LinkExistingUserToSso(int userId, int externalSystemId) ExternalSystemId = externalSystemId, }; - await this.externalSystemUserRepository.CreateAsync(userId, userExternalSystem); + await this.externalSystemUserRepository.CreateExternalSystemUserAsync(userExternalSystem); } /// From ecf118fbb6b33815aad8f665734cc2b6547fbb98 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Tue, 8 Apr 2025 11:23:54 +0100 Subject: [PATCH 28/40] Corrected the datatype --- .../LH/ExternalSystemUserRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs b/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs index c02b97d..bc00c49 100644 --- a/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository/LH/ExternalSystemUserRepository.cs @@ -42,8 +42,8 @@ public async Task CreateExternalSystemUserAsync(ExternalSystemUser userExternalS try { var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userExternalSystem.UserId }; - var param1 = new SqlParameter("@p1", SqlDbType.VarChar) { Value = userExternalSystem.ExternalSystemId }; - var param2 = new SqlParameter("@p2", SqlDbType.VarChar) { Value = userExternalSystem.UserId }; + var param1 = new SqlParameter("@p1", SqlDbType.Int) { Value = userExternalSystem.ExternalSystemId }; + var param2 = new SqlParameter("@p2", SqlDbType.Int) { Value = userExternalSystem.UserId }; var param3 = new SqlParameter("@p3", SqlDbType.Int) { Value = this.TimezoneOffsetManager.UserTimezoneOffset ?? (object)DBNull.Value }; await this.DbContext.Database.ExecuteSqlRawAsync("[external].ExternalSystemUserCreate @p0, @p1, @p2, @p3", param0, param1, param2, param3); } From 0b6e8691a59508fbb4e2607291037955156bf7e7 Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Tue, 8 Apr 2025 14:15:41 +0100 Subject: [PATCH 29/40] Modified EF call with SP call --- LearningHub.Nhs.UserApi.Services/RegistrationService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs index 7dccbaf..f3dccd3 100644 --- a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs +++ b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs @@ -336,7 +336,7 @@ public async Task RegisterUser(RegistrationRequestV ExternalSystemId = registrationRequest.ExternalSystemId.Value, }; - await this.externalSystemUserRepository.CreateAsync(userId, userExternalSystem); + await this.externalSystemUserRepository.CreateExternalSystemUserAsync(userExternalSystem); } if (registrationRequest.IsExternalUser == false) From 09ea76daaa79600d8eaaa08313d0ca70adae337b Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 11 Apr 2025 08:38:07 +0100 Subject: [PATCH 30/40] added new appsetting value - EnableMoodle --- .../Configuration/WebSettings.cs | 7 +- .../UserServices/LearningHubProfileService.cs | 18 +- Auth/LearningHub.Nhs.Auth/appsettings.json | 753 +++++++++--------- 3 files changed, 397 insertions(+), 381 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs b/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs index 4b4113d..0634fe6 100644 --- a/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs +++ b/Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs @@ -61,5 +61,10 @@ public class WebSettings /// Gets or sets a value indicating whether IsPasswordUpdate. /// public bool IsPasswordUpdate { get; set; } - } + + /// + /// Gets or sets a value indicating whether gets or sets a value to Enable Moodle. + /// + public bool EnableMoodle { get; set; } + } } diff --git a/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs b/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs index 36b41b0..9175d60 100644 --- a/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs +++ b/Auth/LearningHub.Nhs.Auth/UserServices/LearningHubProfileService.cs @@ -6,6 +6,7 @@ using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; + using LearningHub.Nhs.Auth.Configuration; using LearningHub.Nhs.Auth.Interfaces; using Microsoft.Extensions.Logging; @@ -14,6 +15,8 @@ /// public class LearningHubProfileService : IProfileService { + private readonly WebSettings webSettings; + /// /// Initializes a new instance of the class. /// @@ -26,11 +29,15 @@ public class LearningHubProfileService : IProfileService /// /// The logger. /// - public LearningHubProfileService(IUserService userService, IMoodleApiService moodleApiService, ILogger logger) + /// + /// The webSettings. + /// + public LearningHubProfileService(IUserService userService, IMoodleApiService moodleApiService, ILogger logger, WebSettings webSettings) { this.UserService = userService; this.MoodleApiService = moodleApiService; this.Logger = logger; + this.webSettings = webSettings; } /// @@ -62,8 +69,6 @@ public async Task GetProfileDataAsync(ProfileDataRequestContext context) if (context != null) { var user = await this.UserService.GetBasicUserByUserIdAsync(context.Subject.GetSubjectId()); - var moodleUser = await this.MoodleApiService.GetMoodleUserIdByUsernameAsync(user.Id); - var roleName = await this.UserService.GetUserRoleAsync(user.Id); var claims = new List @@ -74,9 +79,14 @@ public async Task GetProfileDataAsync(ProfileDataRequestContext context) new Claim("family_name", user.LastName), new Claim("role", roleName), new Claim("elfh_userName", user.UserName), - new Claim("preferred_username", moodleUser.ToString()), }; + if (this.webSettings.EnableMoodle) + { + var moodleUser = await this.MoodleApiService.GetMoodleUserIdByUsernameAsync(user.Id); + claims.Add(new Claim("preferred_username", moodleUser.ToString())); + } + if (context.Subject.HasClaim("openAthensUser", "true")) { claims.Add(new Claim("openAthensUser", "true")); diff --git a/Auth/LearningHub.Nhs.Auth/appsettings.json b/Auth/LearningHub.Nhs.Auth/appsettings.json index 236dada..f4daca9 100644 --- a/Auth/LearningHub.Nhs.Auth/appsettings.json +++ b/Auth/LearningHub.Nhs.Auth/appsettings.json @@ -1,383 +1,384 @@ { - "Environment": "", - "ApplicationInsights": { - "InstrumentationKey": "" - }, - "AzureDataProtection": { - "StorageConnectionString": "", - "StorageContainerName": "learning-hub-id4", - "VaultKeyIdentifier": "", - "TenantId": "", - "ClientId": "", - "ClientSecret": "" - }, - "OpenAthensConfig": { - "Authority": "", - "ClientId": "", - "ClientSecret": "", - "RedirectUri": "" - }, - "Logging": { - "LogLevel": { - "Default": "Trace", - "Microsoft": "Trace" - } - }, - "ConnectionStrings": { - "NLogDb": "", - "Redis": "" - }, - "AllowedHosts": "*", - "WebSettings": { - "BuildNumber": "NotSet", - "UserApiUrl": "", - "X509Certificate2Thumbprint": "", - "LearningHubAdminUrl": "", - "elfhClientMvcUrl": "", - "LearningHubWebClient": "", - "AuthClientIdentityKey": "", - "ElfhHub": "", - "Rcr": "", - "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", - "SupportFeedbackForm": "https://forms.office.com/e/C8tteweEhG", - "IsPasswordUpdate": "false" - }, - "AllowOpenAthensDebug": false, - "OaLhClients": { - "4A49B728-487A-49DC-92EE-B8A848AE13F5": "", - "97859211-1DBF-4CCA-B9EA-37940A6442D8": "", - "B28B1A31-45C1-484B-8E83-9D57CC1C4C7B": "", - "3584C984-028C-4002-AA4B-58AF665AEBDD": "", - "LearningHubOAClient": "" - }, - "MoodleAPIConfig": { - "BaseUrl": "", - "MoodleWSRestFormat": "json", - "WSToken": "" - }, - "OaScopes": [ "" ], - "AuthKey": "", - "AuthOrigin": "", - "IdentityServerCertThumbprint": "", - "LearningHubAuthConfig": { - "LearningHubClientSecrets": { - "LearningHubWebClient": "" + "Environment": "", + "ApplicationInsights": { + "InstrumentationKey": "" }, - "AuthClientIdentityKey": "", - "AuthTimeout": 20, - "AuthClients": { - "learninghubadmin": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": false, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": false, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": false - }, - "learninghubwebclient": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": false, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": false, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": false - }, - "migrationTool": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "", "" ], - "RedirectUris": [], - "PostLogoutUris": [], - "AllowedScopes": [ "", "", "", "" ], - "BackChannelLogoutSessionRequired": false, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": false, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": false - }, - "learninghubwebclient_local": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": false, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": false, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": false - }, - "learninghubadmin_local": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": false, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": false, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": false - }, - "userprofileclient": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": true, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": true, - "RequireConsent": false, - "RequirePkce": true, - "AllowOfflineAccess": true - }, - "learninghubopenapi": { - "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": true, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": true, - "RequireConsent": false, - "RequirePkce": true, - "AllowOfflineAccess": true - }, - "digitallearningsolutions": { - "BaseUrl": "", - "ClientName": "", + "AzureDataProtection": { + "StorageConnectionString": "", + "StorageContainerName": "learning-hub-id4", + "VaultKeyIdentifier": "", + "TenantId": "", + "ClientId": "", + "ClientSecret": "" + }, + "OpenAthensConfig": { + "Authority": "", + "ClientId": "", "ClientSecret": "", - "AllowedGrantTypes": [ "" ], - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AllowedScopes": [ "", "", "", "", "" ], - "BackChannelLogoutSessionRequired": true, - "BackChannelLogoutUri": "", - "UpdateAccessTokenClaimsOnRefresh": true, - "RequireConsent": false, - "RequirePkce": true, - "AllowOfflineAccess": true - }, - "moodle": { + "RedirectUri": "" + }, + "Logging": { + "LogLevel": { + "Default": "Trace", + "Microsoft": "Trace" + } + }, + "ConnectionStrings": { + "NLogDb": "", + "Redis": "" + }, + "AllowedHosts": "*", + "WebSettings": { + "BuildNumber": "NotSet", + "UserApiUrl": "", + "X509Certificate2Thumbprint": "", + "LearningHubAdminUrl": "", + "elfhClientMvcUrl": "", + "LearningHubWebClient": "", + "AuthClientIdentityKey": "", + "ElfhHub": "", + "Rcr": "", + "SupportForm": "https://support.learninghub.nhs.uk/support/tickets/new", + "SupportFeedbackForm": "https://forms.office.com/e/C8tteweEhG", + "IsPasswordUpdate": "false", + "EnableMoodle": "false" + }, + "AllowOpenAthensDebug": false, + "OaLhClients": { + "4A49B728-487A-49DC-92EE-B8A848AE13F5": "", + "97859211-1DBF-4CCA-B9EA-37940A6442D8": "", + "B28B1A31-45C1-484B-8E83-9D57CC1C4C7B": "", + "3584C984-028C-4002-AA4B-58AF665AEBDD": "", + "LearningHubOAClient": "" + }, + "MoodleAPIConfig": { "BaseUrl": "", - "ClientName": "", - "ClientSecret": "", - "AllowedGrantTypes": [ "authorization_code" ], - "RedirectUris": [ "/auth/oidc/" ], - "PostLogoutUris": [ "/login/logout.php" ], - "AllowedScopes": [ "openid", "profile", "learninghubapi", "userapi", "roles", "learningcredentialsapi" ], - "BackChannelLogoutSessionRequired": true, - "BackChannelLogoutUri": "/login/logout.php", - "FrontChannelLogoutSessionRequired": true, - "FrontChannelLogoutUri": "/login/logout.php", - "UpdateAccessTokenClaimsOnRefresh": true, - "RequireConsent": false, - "RequirePkce": false, - "AllowOfflineAccess": true - } + "MoodleWSRestFormat": "json", + "WSToken": "" }, - "IdsClients": { - "eLfH": { - "ClientDescription": "e-Learning for Healthcare Hub", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "", "" ], - "Scopes": [ "openathens" ], - "AuthMainTitle": "e-Learning for Healthcare Authentication", - "ClientLogoSrc": "/images/client-logos/elfh.svg", - "ClientLogoAltText": "e-Learning for Healthcare", - "ClientCssClass": "elfh-hub", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "eIn": { - "ClientDescription": "eIntegrity", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "e-Integrity Authentication", - "ClientLogoSrc": "/images/client-logos/eInt.svg", - "ClientLogoAltText": "eIntegrity", - "ClientCssClass": "eIntegrity", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "UseRegister": false, - "RegisterAccountRelativeUrl": "", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "etft": { - "ClientDescription": "ETFT Educator Hub", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "Educator Hub Authentication", - "ClientLogoSrc": "/images/client-logos/etft-educator-hub.png", - "ClientLogoAltText": "ETFT Educator Hub", - "ClientCssClass": "etft", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "minded": { - "ClientDescription": "MindEd", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "MindEd Authentication", - "ClientLogoSrc": "/images/client-logos/minded.svg", - "ClientLogoAltText": "MindEd", - "ClientCssClass": "minded", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "dismat": { - "ClientDescription": "Disability Matters", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "Disability Matters Authentication", - "ClientLogoSrc": "/images/client-logos/disability-matters.png", - "ClientLogoAltText": "Disability Matters", - "ClientCssClass": "disability-matters", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "popwell": { - "ClientDescription": "Population Wellbeing Portal", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "Population Wellbeing Authentication", - "ClientLogoSrc": "/images/client-logos/population-wellbeing.png", - "ClientLogoAltText": "Population Wellbeing Portal", - "ClientCssClass": "population-wellbeing", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "volpass": { - "ClientDescription": "Volunteer Passport", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "VLP Authentication", - "ClientLogoSrc": "/images/client-logos/volpass.png", - "ClientLogoAltText": "Volunteers Passport", - "ClientCssClass": "vlp", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "carers": { - "ClientDescription": "Supporting Unpaid Carers", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [ "" ], - "PostLogoutUris": [ "" ], - "AuthMainTitle": "Supporting Unpaid Carers Authentication", - "ClientLogoSrc": "/images/client-logos/carers.svg", - "ClientLogoAltText": "Supporting Unpaid Carers", - "ClientCssClass": "carers", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false - }, - "learninghubadmin": { - "ClientDescription": "Learning Hub Admin App", - "AuthSecret": "", - "ClientUrl": "", - "RedirectUris": [], - "PostLogoutUris": [], - "AuthMainTitle": "Learning Hub Authentication", - "ClientLogoSrc": "/images/client-logos/learningHubAdmin.svg", - "ClientLogoUrl": "https://www.nhs.uk/", - "ClientLogoAltText": "Learning Hub Administration", - "ClientCssClass": "learning-hub", - "UseForgottenPassword": false, - "UseRegister": false, - "UseSupport": false, - "AllowRememberLogin": false, - "LayoutPath": "/Views/Shared/LearningHub/_Layout.cshtml" - }, - "learninghubwebclient": { - "ClientDescription": "Learning Hub", - "AuthSecret": "", - "ClientUrl": "", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "registration/create-an-account", - "RedirectUris": [], - "PostLogoutUris": [], - "AuthMainTitle": "Learning Hub", - "ClientLogoSrc": "/images/client-logos/nhs-learning-hub-beta.png", - "ClientLogoUrl": "https://www.nhs.uk/", - "ClientLogoAltText": "Learning Hub", - "ClientCssClass": "learning-hub", - "UseForgottenPassword": true, - "UseRegister": true, - "UseSupport": false, - "AllowRememberLogin": false, - "LayoutPath": "/Views/Shared/LearningHub/_Layout.cshtml" - }, - "digitallearningsolutions": { - "ClientDescription": "Learning Hub", - "ClientUrl": "", - "RedirectUris": [], - "PostLogoutUris": [], - "AuthMainTitle": "Digital Learning Solutions Authentication", - "ClientLogoSrc": "/images/client-logos/dls.svg", - "ClientLogoAltText": "Digital Learning Solutions", - "ClientCssClass": "learning-hub", - "ForgottenPasswordRelativeUrl": "forgotten-password", - "RegisterAccountRelativeUrl": "register", - "SupportUrl": "https://support.e-lfh.org.uk/", - "AllowRememberLogin": false, - "UseRegister": false - } + "OaScopes": [ "" ], + "AuthKey": "", + "AuthOrigin": "", + "IdentityServerCertThumbprint": "", + "LearningHubAuthConfig": { + "LearningHubClientSecrets": { + "LearningHubWebClient": "" + }, + "AuthClientIdentityKey": "", + "AuthTimeout": 20, + "AuthClients": { + "learninghubadmin": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": false, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": false, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": false + }, + "learninghubwebclient": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": false, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": false, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": false + }, + "migrationTool": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "", "" ], + "RedirectUris": [], + "PostLogoutUris": [], + "AllowedScopes": [ "", "", "", "" ], + "BackChannelLogoutSessionRequired": false, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": false, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": false + }, + "learninghubwebclient_local": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": false, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": false, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": false + }, + "learninghubadmin_local": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": false, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": false, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": false + }, + "userprofileclient": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": true, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": true, + "RequireConsent": false, + "RequirePkce": true, + "AllowOfflineAccess": true + }, + "learninghubopenapi": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": true, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": true, + "RequireConsent": false, + "RequirePkce": true, + "AllowOfflineAccess": true + }, + "digitallearningsolutions": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "" ], + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AllowedScopes": [ "", "", "", "", "" ], + "BackChannelLogoutSessionRequired": true, + "BackChannelLogoutUri": "", + "UpdateAccessTokenClaimsOnRefresh": true, + "RequireConsent": false, + "RequirePkce": true, + "AllowOfflineAccess": true + }, + "moodle": { + "BaseUrl": "", + "ClientName": "", + "ClientSecret": "", + "AllowedGrantTypes": [ "authorization_code" ], + "RedirectUris": [ "/auth/oidc/" ], + "PostLogoutUris": [ "/login/logout.php" ], + "AllowedScopes": [ "openid", "profile", "learninghubapi", "userapi", "roles", "learningcredentialsapi" ], + "BackChannelLogoutSessionRequired": true, + "BackChannelLogoutUri": "/login/logout.php", + "FrontChannelLogoutSessionRequired": true, + "FrontChannelLogoutUri": "/login/logout.php", + "UpdateAccessTokenClaimsOnRefresh": true, + "RequireConsent": false, + "RequirePkce": false, + "AllowOfflineAccess": true + } + }, + "IdsClients": { + "eLfH": { + "ClientDescription": "e-Learning for Healthcare Hub", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "", "" ], + "Scopes": [ "openathens" ], + "AuthMainTitle": "e-Learning for Healthcare Authentication", + "ClientLogoSrc": "/images/client-logos/elfh.svg", + "ClientLogoAltText": "e-Learning for Healthcare", + "ClientCssClass": "elfh-hub", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "eIn": { + "ClientDescription": "eIntegrity", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "e-Integrity Authentication", + "ClientLogoSrc": "/images/client-logos/eInt.svg", + "ClientLogoAltText": "eIntegrity", + "ClientCssClass": "eIntegrity", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "UseRegister": false, + "RegisterAccountRelativeUrl": "", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "etft": { + "ClientDescription": "ETFT Educator Hub", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "Educator Hub Authentication", + "ClientLogoSrc": "/images/client-logos/etft-educator-hub.png", + "ClientLogoAltText": "ETFT Educator Hub", + "ClientCssClass": "etft", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "minded": { + "ClientDescription": "MindEd", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "MindEd Authentication", + "ClientLogoSrc": "/images/client-logos/minded.svg", + "ClientLogoAltText": "MindEd", + "ClientCssClass": "minded", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "dismat": { + "ClientDescription": "Disability Matters", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "Disability Matters Authentication", + "ClientLogoSrc": "/images/client-logos/disability-matters.png", + "ClientLogoAltText": "Disability Matters", + "ClientCssClass": "disability-matters", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "popwell": { + "ClientDescription": "Population Wellbeing Portal", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "Population Wellbeing Authentication", + "ClientLogoSrc": "/images/client-logos/population-wellbeing.png", + "ClientLogoAltText": "Population Wellbeing Portal", + "ClientCssClass": "population-wellbeing", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "volpass": { + "ClientDescription": "Volunteer Passport", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "VLP Authentication", + "ClientLogoSrc": "/images/client-logos/volpass.png", + "ClientLogoAltText": "Volunteers Passport", + "ClientCssClass": "vlp", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "carers": { + "ClientDescription": "Supporting Unpaid Carers", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [ "" ], + "PostLogoutUris": [ "" ], + "AuthMainTitle": "Supporting Unpaid Carers Authentication", + "ClientLogoSrc": "/images/client-logos/carers.svg", + "ClientLogoAltText": "Supporting Unpaid Carers", + "ClientCssClass": "carers", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false + }, + "learninghubadmin": { + "ClientDescription": "Learning Hub Admin App", + "AuthSecret": "", + "ClientUrl": "", + "RedirectUris": [], + "PostLogoutUris": [], + "AuthMainTitle": "Learning Hub Authentication", + "ClientLogoSrc": "/images/client-logos/learningHubAdmin.svg", + "ClientLogoUrl": "https://www.nhs.uk/", + "ClientLogoAltText": "Learning Hub Administration", + "ClientCssClass": "learning-hub", + "UseForgottenPassword": false, + "UseRegister": false, + "UseSupport": false, + "AllowRememberLogin": false, + "LayoutPath": "/Views/Shared/LearningHub/_Layout.cshtml" + }, + "learninghubwebclient": { + "ClientDescription": "Learning Hub", + "AuthSecret": "", + "ClientUrl": "", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "registration/create-an-account", + "RedirectUris": [], + "PostLogoutUris": [], + "AuthMainTitle": "Learning Hub", + "ClientLogoSrc": "/images/client-logos/nhs-learning-hub-beta.png", + "ClientLogoUrl": "https://www.nhs.uk/", + "ClientLogoAltText": "Learning Hub", + "ClientCssClass": "learning-hub", + "UseForgottenPassword": true, + "UseRegister": true, + "UseSupport": false, + "AllowRememberLogin": false, + "LayoutPath": "/Views/Shared/LearningHub/_Layout.cshtml" + }, + "digitallearningsolutions": { + "ClientDescription": "Learning Hub", + "ClientUrl": "", + "RedirectUris": [], + "PostLogoutUris": [], + "AuthMainTitle": "Digital Learning Solutions Authentication", + "ClientLogoSrc": "/images/client-logos/dls.svg", + "ClientLogoAltText": "Digital Learning Solutions", + "ClientCssClass": "learning-hub", + "ForgottenPasswordRelativeUrl": "forgotten-password", + "RegisterAccountRelativeUrl": "register", + "SupportUrl": "https://support.e-lfh.org.uk/", + "AllowRememberLogin": false, + "UseRegister": false + } + } } - } } \ No newline at end of file From 1baf49a7ceeccb2b8568179b608ae618d0ee34df Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Mon, 14 Apr 2025 17:15:06 +0100 Subject: [PATCH 31/40] Fixed the style cop issue with the user api (Blank line required) --- LearningHub.Nhs.UserApi/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index 8a6e0aa..c82ae49 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -11,6 +11,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"); From 67624380b2410c162536587570de3ec8c7aa865c Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 24 Apr 2025 10:48:48 +0100 Subject: [PATCH 32/40] TD-5492: Upgrade .NET Framework Version For Database Project --- Hee.UserProfile.Database/Hee.UserProfile.Database.sqlproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hee.UserProfile.Database/Hee.UserProfile.Database.sqlproj b/Hee.UserProfile.Database/Hee.UserProfile.Database.sqlproj index b8cf3b5..df6c02e 100644 --- a/Hee.UserProfile.Database/Hee.UserProfile.Database.sqlproj +++ b/Hee.UserProfile.Database/Hee.UserProfile.Database.sqlproj @@ -16,7 +16,7 @@ 1033, CI BySchemaAndSchemaType True - v4.5.2 + v4.8 CS Properties False From 041b19c6dd1cb37a0597da2d758f11ce6c2b7b54 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 12 May 2025 15:35:29 +0100 Subject: [PATCH 33/40] TD-5538: IUncluded web.config in the solution --- Auth/LearningHub.Nhs.Auth/web.config | 21 +++++++++++++++++++++ LearningHub.Nhs.UserApi/web.config | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 Auth/LearningHub.Nhs.Auth/web.config create mode 100644 LearningHub.Nhs.UserApi/web.config diff --git a/Auth/LearningHub.Nhs.Auth/web.config b/Auth/LearningHub.Nhs.Auth/web.config new file mode 100644 index 0000000..379d167 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi/web.config b/LearningHub.Nhs.UserApi/web.config new file mode 100644 index 0000000..9720653 --- /dev/null +++ b/LearningHub.Nhs.UserApi/web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fb0c8633ed4810fb6e45baa622072026bf3f2aa5 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 12 May 2025 15:36:46 +0100 Subject: [PATCH 34/40] TD-5538: IUncluded web.config in the solution --- .../Views/Account/AlreadyActiveSession.cshtml | 2 +- Auth/LearningHub.Nhs.Auth/web.config | 34 +++++++++---------- LearningHub.Nhs.UserApi/web.config | 34 +++++++++---------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml b/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml index 1f5d3b6..a0e908c 100644 --- a/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml +++ b/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml @@ -1,5 +1,5 @@ @{ - ViewData["Title"] = "Already active session"; + ViewData["Title"] = "Session already active"; }
diff --git a/Auth/LearningHub.Nhs.Auth/web.config b/Auth/LearningHub.Nhs.Auth/web.config index 379d167..0cd2434 100644 --- a/Auth/LearningHub.Nhs.Auth/web.config +++ b/Auth/LearningHub.Nhs.Auth/web.config @@ -1,21 +1,21 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi/web.config b/LearningHub.Nhs.UserApi/web.config index 9720653..44bd371 100644 --- a/LearningHub.Nhs.UserApi/web.config +++ b/LearningHub.Nhs.UserApi/web.config @@ -1,21 +1,21 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file From 60e679a133a2a1127c428bfc81cb9ad225a54acb Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Mon, 12 May 2025 15:42:31 +0100 Subject: [PATCH 35/40] corrected --- LearningHub.Nhs.UserApi/web.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningHub.Nhs.UserApi/web.config b/LearningHub.Nhs.UserApi/web.config index 44bd371..f1d8e42 100644 --- a/LearningHub.Nhs.UserApi/web.config +++ b/LearningHub.Nhs.UserApi/web.config @@ -14,7 +14,7 @@ - + From 3dd19cf9c89a1f009a6f07e1186daa549318bfed Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Thu, 15 May 2025 16:42:44 +0100 Subject: [PATCH 36/40] TD-4982-Blocked all IdentityServer4 logs from being processed by later rules --- Auth/LearningHub.Nhs.Auth/NLog.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Auth/LearningHub.Nhs.Auth/NLog.config b/Auth/LearningHub.Nhs.Auth/NLog.config index eb67b60..a59da11 100644 --- a/Auth/LearningHub.Nhs.Auth/NLog.config +++ b/Auth/LearningHub.Nhs.Auth/NLog.config @@ -39,6 +39,10 @@ + + + + From 0f43fcaa22fc0bcf8208b69d7983810385e0ca40 Mon Sep 17 00:00:00 2001 From: swapnamol-abraham Date: Wed, 28 May 2025 14:08:33 +0100 Subject: [PATCH 37/40] TD-3734: Reverting the changes --- .../Controllers/AccountController.cs | 55 +++++++------------ .../Interfaces/IUserService.cs | 7 --- .../Services/UserService.cs | 24 -------- .../Views/Account/AlreadyActiveSession.cshtml | 15 ----- .../IUserHistoryRepository.cs | 7 --- .../UserHistoryRepository.cs | 24 +------- .../IUserHistoryService.cs | 7 --- .../UserHistoryService.cs | 13 ----- .../Controllers/UserHistoryController.cs | 13 ----- 9 files changed, 22 insertions(+), 143 deletions(-) delete mode 100644 Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml diff --git a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs index 58f804d..123fe1a 100644 --- a/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs +++ b/Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using System.Threading.Tasks; - using Azure.Core; using elfhHub.Nhs.Models.Common; using elfhHub.Nhs.Models.Enums; using IdentityModel; @@ -23,11 +22,9 @@ using LearningHub.Nhs.Models.Common; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; - using UAParser; /// /// Account Controller operations. @@ -166,44 +163,34 @@ await this.interaction.GrantConsentAsync( if (loginResult.IsAuthenticated) { - var uaParser = Parser.GetDefault(); - var clientInfo = uaParser.Parse(this.Request.Headers["User-Agent"]); - var result = await this.UserService.CheckUserHasAnActiveSessionAsync(userId); - if (result.Items.Count == 0 || result.Items[0].BrowserName == clientInfo.UA.Family) - { - await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); + await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]); - if (context != null) + if (context != null) + { + if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) { - if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId)) - { - // if the client is PKCE then we assume it's native, so this change in how to - // return the response is for better UX for the end user. - return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); - } - - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return this.Redirect(model.ReturnUrl); + // if the client is PKCE then we assume it's native, so this change in how to + // return the response is for better UX for the end user. + return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); } - // request for a local page - if (this.Url.IsLocalUrl(model.ReturnUrl)) - { - return this.Redirect(model.ReturnUrl); - } - else if (string.IsNullOrEmpty(model.ReturnUrl)) - { - return this.Redirect("~/"); - } - else - { - // user might have clicked on a malicious link - should be logged - throw new Exception("invalid return URL"); - } + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return this.Redirect(model.ReturnUrl); + } + + // request for a local page + if (this.Url.IsLocalUrl(model.ReturnUrl)) + { + return this.Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return this.Redirect("~/"); } else { - return this.View("AlreadyActiveSession"); + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); } } else if (userId > 0) diff --git a/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs b/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs index e4ae21c..3a97b47 100644 --- a/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs +++ b/Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs @@ -114,13 +114,6 @@ public interface IUserService /// Task StoreUserHistoryAsync(UserHistoryViewModel userHistory); - /// - /// check user has an laredy active session. - /// - /// The userId. - /// The . - Task> CheckUserHasAnActiveSessionAsync(int userId); - /// /// The store user history async. /// diff --git a/Auth/LearningHub.Nhs.Auth/Services/UserService.cs b/Auth/LearningHub.Nhs.Auth/Services/UserService.cs index 98246ec..2ba58b7 100644 --- a/Auth/LearningHub.Nhs.Auth/Services/UserService.cs +++ b/Auth/LearningHub.Nhs.Auth/Services/UserService.cs @@ -243,29 +243,5 @@ public async Task StoreUserHistoryAsync(UserHistoryViewModel userHistory) } } } - - /// - public async Task> CheckUserHasAnActiveSessionAsync(int userId) - { - PagedResultSet userHistoryViewModel = new PagedResultSet(); - - var client = this.UserApiHttpClient.GetClient(); - var request = $"UserHistory/CheckUserHasActiveSession/{userId}"; - var response = await client.GetAsync(request).ConfigureAwait(false); - - if (response.IsSuccessStatusCode) - { - var result = await response.Content.ReadAsStringAsync(); - userHistoryViewModel = JsonConvert.DeserializeObject>(result); - } - else if (response.StatusCode == HttpStatusCode.Unauthorized - || - response.StatusCode == HttpStatusCode.Forbidden) - { - throw new Exception("AccessDenied"); - } - - return userHistoryViewModel; - } } } diff --git a/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml b/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml deleted file mode 100644 index a0e908c..0000000 --- a/Auth/LearningHub.Nhs.Auth/Views/Account/AlreadyActiveSession.cshtml +++ /dev/null @@ -1,15 +0,0 @@ -@{ - ViewData["Title"] = "Session already active"; -} -
-
-
-
-

@ViewData["Title"]

-

You are already logged in from another browser. Please continue using the same browser or close the existing session and try again with a new one.

-

If you have any questions, please contact the support team.

-

@DateTimeOffset.Now.ToString("d MMMM yyyy HH:mm:ss")

-
-
-
-
\ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs b/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs index 8684ba8..e72aebf 100644 --- a/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository.Interface/IUserHistoryRepository.cs @@ -59,12 +59,5 @@ public interface IUserHistoryRepository /// The . /// Task GetPagedByUserIdAsync(int userId, int startPage, int pageSize); - - /// - /// Check user has an active login session. - /// - /// The userId. - /// The . - Task CheckUserHasActiveSessionAsync(int userId); } } \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs b/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs index 3f0386b..673a3ae 100644 --- a/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs +++ b/LearningHub.Nhs.UserApi.Repository/UserHistoryRepository.cs @@ -9,7 +9,6 @@ using LearningHub.Nhs.UserApi.Repository.Interface; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; - using Newtonsoft.Json.Linq; /// /// The user history repository. @@ -67,13 +66,11 @@ public async Task CreateAsync(int userId, int tenantId, UserHistoryViewModel use new SqlParameter("@LoginIP", SqlDbType.VarChar) { Value = userHistoryVM.LoginIP ?? (object)DBNull.Value }, new SqlParameter("@LoginSuccessFul", SqlDbType.Bit) { Value = userHistoryVM.LoginSuccessFul ?? (object)DBNull.Value }, new SqlParameter("@TenantId", SqlDbType.Int) { Value = tenantId }, - new SqlParameter("@SessionId", SqlDbType.VarChar) { Value = (userHistoryVM.UserHistoryTypeId == 0 && userHistoryVM.Detail == "User logged on. Source of auth: LearningHub.Nhs.Auth Account\\Login") ? userHistoryVM.SessionId : (object)DBNull.Value }, - new SqlParameter("@IsActive", SqlDbType.Bit) { Value = (userHistoryVM.UserHistoryTypeId == 0 && userHistoryVM.Detail == "User logged on. Source of auth: LearningHub.Nhs.Auth Account\\Login") ? userHistoryVM.IsActive : (object)DBNull.Value }, new SqlParameter("@AmendUserId", SqlDbType.Int) { Value = userId }, new SqlParameter("@AmendDate", SqlDbType.DateTimeOffset) { Value = DateTimeOffset.Now }, }; - string sql = "proc_UserHistoryInsert @UserId, @UserHistoryTypeId, @Detail, @UserAgent, @BrowserName, @BrowserVersion, @UrlReferer, @LoginIP, @LoginSuccessFul, @TenantId, @SessionId, @IsActive, @AmendUserId, @AmendDate"; + string sql = "proc_UserHistoryInsert @UserId, @UserHistoryTypeId, @Detail, @UserAgent, @BrowserName, @BrowserVersion, @UrlReferer, @LoginIP, @LoginSuccessFul, @TenantId, @AmendUserId, @AmendDate"; await this.DbContext.Database.ExecuteSqlRawAsync(sql, sqlParams); } @@ -101,24 +98,5 @@ public async Task GetPagedByUserIdAsync(int userId return retVal; } - - /// - public async Task CheckUserHasActiveSessionAsync(int userId) - { - try - { - var retVal = new UserHistoryStoredProcResults(); - var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = userId }; - - var result = await this.DbContext.Set().FromSqlRaw( - "dbo.proc_ActiveLearningHubUserbyId @p0", param0).AsNoTracking().ToListWithNoLockAsync(); - retVal.Results = result; - return retVal; - } - catch (Exception ex) - { - return null; - } - } } } \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs b/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs index 9c6954b..7ed5e01 100644 --- a/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs +++ b/LearningHub.Nhs.UserApi.Services.Interface/IUserHistoryService.cs @@ -53,12 +53,5 @@ public interface IUserHistoryService /// The . /// Task> GetUserHistoryPageAsync(int page, int pageSize, string sortColumn = "", string sortDirection = "", string presetFilter = "", string filter = ""); - - /// - /// Check user has an active login session. - /// - /// The userId. - /// The . - Task> CheckUserHasActiveSessionAsync(int userId); } } diff --git a/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs b/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs index e6ef86c..c65377c 100644 --- a/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs +++ b/LearningHub.Nhs.UserApi.Services/UserHistoryService.cs @@ -1,6 +1,5 @@ namespace LearningHub.Nhs.UserApi.Services { - using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -57,8 +56,6 @@ public async Task CreateAsync(UserHistoryViewModel if (retVal.IsValid) { - userHistoryVM.SessionId = Guid.NewGuid().ToString(); - userHistoryVM.IsActive = true; await this.userHistoryRepository.CreateAsync(userHistoryVM.UserId, this.settings.LearningHubTenantId, userHistoryVM); } @@ -102,16 +99,6 @@ public async Task> GetUserHistoryPageAsync( return result; } - /// - public async Task> CheckUserHasActiveSessionAsync(int userId) - { - PagedResultSet result = new PagedResultSet(); - var userHistory = await this.userHistoryRepository.CheckUserHasActiveSessionAsync(userId); - userHistory.Results.ForEach(x => x.UserAgent = this.ParseUserAgentString(x.UserAgent)); - result.Items = this.mapper.Map>(userHistory.Results); - return result; - } - private string ParseUserAgentString(string userAgent) { string retVal = string.Empty; diff --git a/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs b/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs index d1436d4..f86d769 100644 --- a/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs +++ b/LearningHub.Nhs.UserApi/Controllers/UserHistoryController.cs @@ -100,19 +100,6 @@ public async Task GetUserHistoryPageAsync(int page, int pageSize, return this.Ok(pagedResultSet); } - /// - /// Check the user has an active login session. - /// - /// The UserId. - /// The . - [HttpGet] - [Route("CheckUserHasActiveSession/{userId}")] - public async Task CheckUserHasActiveSessionAsync(int userId) - { - PagedResultSet pagedResultSet = await this.userHistoryService.CheckUserHasActiveSessionAsync(userId); - return this.Ok(pagedResultSet); - } - /// /// Create a UserHistory. /// From 9d3aee13c1c99358c66c9d0eeb0d83c3ea73e882 Mon Sep 17 00:00:00 2001 From: Binon Date: Tue, 3 Jun 2025 15:56:53 +0100 Subject: [PATCH 38/40] Enabled central package management --- .../LearningHub.Nhs.Auth.Tests.csproj | 27 +- .../LearningHub.Nhs.Auth.csproj | 278 +++++++++--------- Directory.Build.props | 2 +- Directory.Packages.props | 49 +++ ...ub.Nhs.UserApi.Repository.Interface.csproj | 16 +- .../LearningHub.Nhs.UserApi.Repository.csproj | 20 +- ...gHub.Nhs.UserAPI.Services.Interface.csproj | 16 +- ...gHub.Nhs.UserApi.Services.UnitTests.csproj | 34 +-- .../LearningHub.Nhs.UserApi.Services.csproj | 21 +- .../LearningHub.Nhs.UserApi.Shared.csproj | 15 +- .../LearningHub.Nhs.UserApi.UnitTests.csproj | 27 +- .../LearningHub.Nhs.UserApi.csproj | 40 ++- 12 files changed, 273 insertions(+), 272 deletions(-) create mode 100644 Directory.Packages.props diff --git a/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj b/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj index ec6d6ad..4544d37 100644 --- a/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj +++ b/Auth/LearningHub.Nhs.Auth.Tests/LearningHub.Nhs.Auth.Tests.csproj @@ -1,32 +1,27 @@ - - + net8.0 true false - x64 + x64 - - - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + \ No newline at end of file diff --git a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj index 2933aee..8fe3ee6 100644 --- a/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj +++ b/Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj @@ -1,145 +1,133 @@ - - - - net8.0 - 00EF27C2-ECB6-4E37-A6B6-58E4E6189D0E - true - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - Never - - - - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - Always - - - Always - - - - - - - - - - - - - - + + + net8.0 + 00EF27C2-ECB6-4E37-A6B6-58E4E6189D0E + true + x64 + + + + + + + + + + + + + + + + + + + + + + + Never + + + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + true + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + Always + + + Always + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 041b430..d153dde 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ $(SolutionDir)StyleCop.ruleset - + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..c94d0a7 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,49 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj b/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj index 29a3a90..60f0b5d 100644 --- a/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj +++ b/LearningHub.Nhs.UserApi.Repository.Interface/LearningHub.Nhs.UserApi.Repository.Interface.csproj @@ -1,23 +1,19 @@ - - + net8.0 true false - x64 + x64 - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj b/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj index 5921669..7d254b8 100644 --- a/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj +++ b/LearningHub.Nhs.UserApi.Repository/LearningHub.Nhs.UserApi.Repository.csproj @@ -1,25 +1,21 @@ - - + net8.0 true false - x64 + x64 - - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj b/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj index 29a3a90..60f0b5d 100644 --- a/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj +++ b/LearningHub.Nhs.UserApi.Services.Interface/LearningHub.Nhs.UserAPI.Services.Interface.csproj @@ -1,23 +1,19 @@ - - + net8.0 true false - x64 + x64 - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj b/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj index a61a661..2bcf2e7 100644 --- a/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj +++ b/LearningHub.Nhs.UserApi.Services.UnitTests/LearningHub.Nhs.UserApi.Services.UnitTests.csproj @@ -1,37 +1,33 @@ - - + net8.0 - true + true enable enable - x64 + x64 false - - - - - - - - - - - + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj b/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj index 670491b..a81d32a 100644 --- a/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj +++ b/LearningHub.Nhs.UserApi.Services/LearningHub.Nhs.UserApi.Services.csproj @@ -1,27 +1,24 @@ - - + net8.0 true false - x64 + x64 - - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj b/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj index ecfe311..04b48b4 100644 --- a/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj +++ b/LearningHub.Nhs.UserApi.Shared/LearningHub.Nhs.UserApi.Shared.csproj @@ -1,19 +1,16 @@ - - + net8.0 true false - x64 + x64 - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj b/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj index 5c215c5..4eb125a 100644 --- a/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj +++ b/LearningHub.Nhs.UserApi.UnitTests/LearningHub.Nhs.UserApi.UnitTests.csproj @@ -1,37 +1,32 @@ - - + net8.0 - true + true enable enable - x64 + x64 false - - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + \ No newline at end of file diff --git a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj index c707f5a..f98d757 100644 --- a/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj +++ b/LearningHub.Nhs.UserApi/LearningHub.Nhs.UserApi.csproj @@ -1,43 +1,37 @@ - - + net8.0 InProcess true - x64 + x64 - LearningHub.Nhs.UserApi.xml - - Always - - - - - - - - - - + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - @@ -45,12 +39,14 @@ - Never - - + + + + + \ No newline at end of file From 03ea146cdddf4e47b7f53a6d2f47d45a8573d4bd Mon Sep 17 00:00:00 2001 From: Binon Date: Fri, 6 Jun 2025 15:25:10 +0100 Subject: [PATCH 39/40] Added nuget.config file --- LearningHub.Nhs.UserApi.sln | 2 ++ nuget.config | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 nuget.config diff --git a/LearningHub.Nhs.UserApi.sln b/LearningHub.Nhs.UserApi.sln index adb65c7..9775d9c 100644 --- a/LearningHub.Nhs.UserApi.sln +++ b/LearningHub.Nhs.UserApi.sln @@ -6,6 +6,8 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{683CA47A-9041-4CB9-B436-CD20BD40EB34}" ProjectSection(SolutionItems) = preProject Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + nuget.config = nuget.config StyleCop.json = StyleCop.json StyleCop.ruleset = StyleCop.ruleset EndProjectSection diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..4b5a055 --- /dev/null +++ b/nuget.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file From 8e3adf09c4daeef5a2143b20b4ef7ee2d33e9763 Mon Sep 17 00:00:00 2001 From: binon Date: Thu, 12 Jun 2025 10:34:52 +0100 Subject: [PATCH 40/40] Update continuous-integration-workflow.yml --- .github/workflows/continuous-integration-workflow.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index baa6759..3293c61 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -18,7 +18,9 @@ jobs: dotnet-version: 8.0.x - name: Add Azure artifact - run: dotnet nuget add source 'https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json' --name 'LearningHubFeed' --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text + run: | + dotnet nuget remove source LearningHubFeed || true + dotnet nuget add source 'https://pkgs.dev.azure.com/e-LfH/_packaging/LearningHubFeed/nuget/v3/index.json' --name 'LearningHubFeed' --username 'kevin.whittaker' --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text - name: Use NuGet 5.8 uses: nuget/setup-nuget@v1