diff --git a/LearningHub.Nhs.UserApi.Services.Interface/IOpenApiHttpClient.cs b/LearningHub.Nhs.UserApi.Services.Interface/IOpenApiHttpClient.cs new file mode 100644 index 0000000..8027c52 --- /dev/null +++ b/LearningHub.Nhs.UserApi.Services.Interface/IOpenApiHttpClient.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.UserApi.Services.Interface +{ + using System.Net.Http; + + /// + /// The IOpenApiHttpClient. + /// + public interface IOpenApiHttpClient + { + /// + /// The get client. + /// + /// + /// The . + /// + HttpClient GetClient(); + } +} diff --git a/LearningHub.Nhs.UserApi.Services.UnitTests/RegistrationServiceTests.cs b/LearningHub.Nhs.UserApi.Services.UnitTests/RegistrationServiceTests.cs index b1da4c2..36d8932 100644 --- a/LearningHub.Nhs.UserApi.Services.UnitTests/RegistrationServiceTests.cs +++ b/LearningHub.Nhs.UserApi.Services.UnitTests/RegistrationServiceTests.cs @@ -51,7 +51,7 @@ public async Task GetRegistrationStatus_ExistingEmail_BlueUser() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var status = await registrationService.GetRegistrationStatus("test@here.com", this.ipAddress); Assert.IsType(status); @@ -79,7 +79,7 @@ public async Task GetRegistrationStatus_ExistingEmail_NonBlueUser() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, userGroupRepositoryMock.Object, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, userGroupRepositoryMock.Object, null, null, null, this.countryLookupRepoMock.Object, null); var status = await registrationService.GetRegistrationStatus("test@here.com", this.ipAddress); Assert.IsType(status); @@ -103,7 +103,7 @@ public async Task GetRegistrationStatus_NonExistingEmail_BlueUser() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var status = await registrationService.GetRegistrationStatus("test@here.com", this.ipAddress); Assert.IsType(status); @@ -127,7 +127,7 @@ public async Task GetRegistrationStatus_NonExistingEmail_NonBlueUser() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var status = await registrationService.GetRegistrationStatus("test@here.com", this.ipAddress); Assert.IsType(status); @@ -145,7 +145,7 @@ public async Task RegisterUser_Invalid_NoEmailOrNames() { var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -177,7 +177,7 @@ public async Task RegisterUser_MissingRegionForEngland() { var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -207,7 +207,7 @@ public async Task RegisterUser_MissingCountryJobRoleSpecialtyAndLocation() { var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, null, null, null, null, null, null, null, null, null, null, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -252,7 +252,7 @@ public async Task RegisterUser_GMCFailure() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -294,7 +294,7 @@ public async Task RegisterUser_GDCFailure() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -336,7 +336,7 @@ public async Task RegisterUser_NMCFailure() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, medicalcouncilServiceMock.Object, null, null, optionsMock.Object, null, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -381,7 +381,7 @@ public async Task RegisterUser_UserAlreadyExists() var optionsMock = new Mock>(); - var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, null, userServiceMock.Object, null, optionsMock.Object, null, medicalCouncilRepositoryMock.Object, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(null, null, jobRoleRepositoryMock.Object, null, null, null, null, null, null, null, null, null, userServiceMock.Object, null, optionsMock.Object, null, medicalCouncilRepositoryMock.Object, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { @@ -456,7 +456,7 @@ public async Task RegisterUser_Success() var emailLogRepositoryMock = new Mock(); emailLogRepositoryMock.Setup(r => r.CreateAsync(It.IsAny(), It.IsAny())); - var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, jobRoleRepositoryMock.Object, userUserGroupRepositoryMock.Object, userEmploymentRepositoryMock.Object, emailTemplateRepositoryMock.Object, emailLogRepositoryMock.Object, systemSettingsRepositoryMock.Object, userPasswordValidationTokenRepositoryMock.Object, tenantRepositoryMock.Object, tenantSmtpRepositoryMock.Object, null, userServiceMock.Object, userHistoryServiceMock.Object, this.GetSettings(), loginWizardServiceMock.Object, null, null, null, null, null, this.countryLookupRepoMock.Object); + var registrationService = new RegistrationService(userRepositoryMock.Object, userGroupTypeInputValidationRepositoryMock.Object, jobRoleRepositoryMock.Object, userUserGroupRepositoryMock.Object, userEmploymentRepositoryMock.Object, emailTemplateRepositoryMock.Object, emailLogRepositoryMock.Object, systemSettingsRepositoryMock.Object, userPasswordValidationTokenRepositoryMock.Object, tenantRepositoryMock.Object, tenantSmtpRepositoryMock.Object, null, userServiceMock.Object, userHistoryServiceMock.Object, this.GetSettings(), loginWizardServiceMock.Object, null, null, null, null, null, this.countryLookupRepoMock.Object, null); var validationResult = await registrationService.RegisterUser( new RegistrationRequestViewModel() { diff --git a/LearningHub.Nhs.UserApi.Services.UnitTests/UserServiceTests.cs b/LearningHub.Nhs.UserApi.Services.UnitTests/UserServiceTests.cs index 7e85e6d..3f0cb64 100644 --- a/LearningHub.Nhs.UserApi.Services.UnitTests/UserServiceTests.cs +++ b/LearningHub.Nhs.UserApi.Services.UnitTests/UserServiceTests.cs @@ -543,6 +543,7 @@ public async Task GetUserRole_Administrator() null, null, null, + null, null); var role = await userService.GetUserRoleAsync(1); @@ -591,6 +592,7 @@ public async Task GetUserRole_BlueUser() null, null, null, + null, null); var role = await userService.GetUserRoleAsync(1); @@ -836,6 +838,7 @@ public async Task GetUserRole_none() null, null, null, + null, null); var role = await userService.GetUserRoleAsync(1); @@ -885,6 +888,7 @@ public async Task UpdateUserSecurityQuestions_Create() null, null, this.NewMapper(), + null, null); await userService.UpdateUserSecurityQuestions(userSecurityQuestions, userId); @@ -936,6 +940,7 @@ public async Task UpdateUserSecurityQuestions_CreateAndUpdate() null, null, this.NewMapper(), + null, null); await userService.UpdateUserSecurityQuestions(userSecurityQuestions, userId); @@ -977,6 +982,7 @@ public async Task InvalidateElfhUserCache_InvalidateSuccess() this.elfhCacheSettingOptions, elfhCacheMock.Object, this.NewMapper(), + null, null); await userService.InvalidateElfhUserCacheAsync(123456, "test.user", CancellationToken.None); diff --git a/LearningHub.Nhs.UserApi.Services/ElfhUserService.cs b/LearningHub.Nhs.UserApi.Services/ElfhUserService.cs index 05fc896..b3fee9e 100644 --- a/LearningHub.Nhs.UserApi.Services/ElfhUserService.cs +++ b/LearningHub.Nhs.UserApi.Services/ElfhUserService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; @@ -14,6 +15,7 @@ using elfhHub.Nhs.Models.Entities; using elfhHub.Nhs.Models.Enums; using LearningHub.Nhs.Models.Common; + using LearningHub.Nhs.Models.GovNotifyMessaging; using LearningHub.Nhs.Models.OpenAthens; using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.UserApi.Repository; @@ -59,6 +61,7 @@ public class ElfhUserService : IElfhUserService private readonly IElfhRedisCache elfhCache; private readonly IMapper mapper; private readonly ILogger logger; + private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. @@ -86,6 +89,7 @@ public class ElfhUserService : IElfhUserService /// The ELFH cache. /// The mapper. /// The logger. + /// The openApiHttpClient. public ElfhUserService( IElfhUserRepository elfhUserRepository, IUserGroupRepository userGroupRepository, @@ -109,7 +113,8 @@ public ElfhUserService( IOptions settings, IElfhRedisCache elfhCache, IMapper mapper, - ILogger logger) + ILogger logger, + IOpenApiHttpClient openApiHttpClient) { this.elfhUserRepository = elfhUserRepository; this.userGroupRepository = userGroupRepository; @@ -134,6 +139,7 @@ public ElfhUserService( this.elfhCache = elfhCache; this.mapper = mapper; this.logger = logger; + this.openApiHttpClient = openApiHttpClient; } /// @@ -663,42 +669,64 @@ public async Task SendAdminPasswordResetEmail(int u return new LearningHubValidationResult(false, $"userId {userId} not found."); } - // Send registration confirmation email - /* This code was copied from the RegistrationService.RegisterUser method and tweaked for this email template. - * The GenerateUserPasswordValidationToken method (and its child methods) are all direct copies of those found in - * the RegistrationService. This is definitely a candidate for turning into a shared component after RR. */ var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryAdminDefault)).IntValue; UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, userId); await this.userPasswordValidationTokenRepository.CreateAsync(userId, userPasswordValidationToken); - string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId)).From; - EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.AdminPasswordValidateEmail, this.settings.Value.LearningHubTenantId); - string bodyText = emailTemplate.Body; - bodyText = bodyText.Replace("[FullName]", (user.FirstName + " " + user.LastName).ToTitleCase()); - bodyText = bodyText.Replace("[FirstName]", user.FirstName.ToTitleCase()); - bodyText = bodyText.Replace("[UserName]", user.UserName); - bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); - bodyText = bodyText.Replace("[TimeLimit]", $"{expiryMinutes / 60} hours"); - bodyText = bodyText.Replace("[TenantUrl]", this.settings.Value.LearningHubUrl); - - Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId); - if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) - { - bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); - } + var personalisation = new Dictionary(); + personalisation["name"] = user.FirstName; + personalisation["username"] = user.UserName; + personalisation["new password"] = userPasswordValidationToken.ValidateUrl; - EmailLog emailLog = new EmailLog() + var emailRequest = new EmailRequest { - EmailTemplateId = emailTemplate.Id, - FromEmailAddress = websiteEmailsFrom, - ToUserId = userId, - ToEmailAddress = user.EmailAddress, - Subject = emailTemplate.Subject, - Body = bodyText, - TenantId = this.settings.Value.LearningHubTenantId, - Priority = 1, + Recipient = user.EmailAddress, + TemplateId = this.settings.Value.GovNotifyTemplates.ForgottenUsernameOrPassword, + Personalisation = personalisation, }; - await this.emailLogRepository.CreateAsync(userId, emailLog); + + var client = this.openApiHttpClient.GetClient(); + + using var reqContent = new StringContent(JsonConvert.SerializeObject(emailRequest), Encoding.UTF8, "application/json"); + + await client.PostAsync("GovNotifyMessage/SendEmail", reqContent).ConfigureAwait(false); + + ////// Send registration confirmation email + /////* This code was copied from the RegistrationService.RegisterUser method and tweaked for this email template. + //// * The GenerateUserPasswordValidationToken method (and its child methods) are all direct copies of those found in + //// * the RegistrationService. This is definitely a candidate for turning into a shared component after RR. */ + ////var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryAdminDefault)).IntValue; + ////UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, userId); + ////await this.userPasswordValidationTokenRepository.CreateAsync(userId, userPasswordValidationToken); + + ////string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId)).From; + ////EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.AdminPasswordValidateEmail, this.settings.Value.LearningHubTenantId); + ////string bodyText = emailTemplate.Body; + ////bodyText = bodyText.Replace("[FullName]", (user.FirstName + " " + user.LastName).ToTitleCase()); + ////bodyText = bodyText.Replace("[FirstName]", user.FirstName.ToTitleCase()); + ////bodyText = bodyText.Replace("[UserName]", user.UserName); + ////bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); + ////bodyText = bodyText.Replace("[TimeLimit]", $"{expiryMinutes / 60} hours"); + ////bodyText = bodyText.Replace("[TenantUrl]", this.settings.Value.LearningHubUrl); + + ////Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId); + ////if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) + ////{ + //// bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); + ////} + + ////EmailLog emailLog = new EmailLog() + ////{ + //// EmailTemplateId = emailTemplate.Id, + //// FromEmailAddress = websiteEmailsFrom, + //// ToUserId = userId, + //// ToEmailAddress = user.EmailAddress, + //// Subject = emailTemplate.Subject, + //// Body = bodyText, + //// TenantId = this.settings.Value.LearningHubTenantId, + //// Priority = 1, + ////}; + ////await this.emailLogRepository.CreateAsync(userId, emailLog); return new LearningHubValidationResult(true); } @@ -712,39 +740,61 @@ public async Task SendForgotPasswordEmail(string emailAddress) return; } - // Copied from the above SendAdminPasswordResetEmail var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryAdminDefault)).IntValue; UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, user.Id); await this.userPasswordValidationTokenRepository.CreateAsync(user.Id, userPasswordValidationToken); - string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId)).From; - EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.AdminPasswordValidateEmail, this.settings.Value.LearningHubTenantId); - string bodyText = emailTemplate.Body; - bodyText = bodyText.Replace("[FullName]", (user.FirstName + " " + user.LastName).ToTitleCase()); - bodyText = bodyText.Replace("[FirstName]", user.FirstName.ToTitleCase()); - bodyText = bodyText.Replace("[UserName]", user.UserName); - bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); - bodyText = bodyText.Replace("[TimeLimit]", (expiryMinutes / 60).ToString() + " hours"); - bodyText = bodyText.Replace("[TenantUrl]", this.settings.Value.LearningHubUrl); - - Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId); - if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) - { - bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); - } + var personalisation = new Dictionary(); + personalisation["name"] = user.FirstName; + personalisation["username"] = user.UserName; + personalisation["new password"] = userPasswordValidationToken.ValidateUrl; - EmailLog emailLog = new EmailLog() + var emailRequest = new EmailRequest { - EmailTemplateId = emailTemplate.Id, - FromEmailAddress = websiteEmailsFrom, - ToUserId = user.Id, - ToEmailAddress = user.EmailAddress, - Subject = emailTemplate.Subject, - Body = bodyText, - TenantId = this.settings.Value.LearningHubTenantId, - Priority = 1, + Recipient = user.EmailAddress, + TemplateId = this.settings.Value.GovNotifyTemplates.ForgottenUsernameOrPassword, + Personalisation = personalisation, }; - await this.emailLogRepository.CreateAsync(user.Id, emailLog); + + var client = this.openApiHttpClient.GetClient(); + + using var reqContent = new StringContent(JsonConvert.SerializeObject(emailRequest), Encoding.UTF8, "application/json"); + + await client.PostAsync("GovNotifyMessage/SendEmail", reqContent).ConfigureAwait(false); + + ////// Copied from the above SendAdminPasswordResetEmail + ////var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryAdminDefault)).IntValue; + ////UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, user.Id); + ////await this.userPasswordValidationTokenRepository.CreateAsync(user.Id, userPasswordValidationToken); + + ////string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId)).From; + ////EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.AdminPasswordValidateEmail, this.settings.Value.LearningHubTenantId); + ////string bodyText = emailTemplate.Body; + ////bodyText = bodyText.Replace("[FullName]", (user.FirstName + " " + user.LastName).ToTitleCase()); + ////bodyText = bodyText.Replace("[FirstName]", user.FirstName.ToTitleCase()); + ////bodyText = bodyText.Replace("[UserName]", user.UserName); + ////bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); + ////bodyText = bodyText.Replace("[TimeLimit]", (expiryMinutes / 60).ToString() + " hours"); + ////bodyText = bodyText.Replace("[TenantUrl]", this.settings.Value.LearningHubUrl); + + ////Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.Value.LearningHubTenantId); + ////if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) + ////{ + //// bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); + ////} + + ////EmailLog emailLog = new EmailLog() + ////{ + //// EmailTemplateId = emailTemplate.Id, + //// FromEmailAddress = websiteEmailsFrom, + //// ToUserId = user.Id, + //// ToEmailAddress = user.EmailAddress, + //// Subject = emailTemplate.Subject, + //// Body = bodyText, + //// TenantId = this.settings.Value.LearningHubTenantId, + //// Priority = 1, + ////}; + ////await this.emailLogRepository.CreateAsync(user.Id, emailLog); } /// diff --git a/LearningHub.Nhs.UserApi.Services/OpenApiConfig.cs b/LearningHub.Nhs.UserApi.Services/OpenApiConfig.cs new file mode 100644 index 0000000..ac3d1cf --- /dev/null +++ b/LearningHub.Nhs.UserApi.Services/OpenApiConfig.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.UserApi.Services +{ + /// + /// The OpenApiConfig. + /// + public class OpenApiConfig + { + /// + /// Gets or sets the OpenApiUrl. + /// + public string OpenApiUrl { get; set; } + + /// + /// Gets or sets the OpenApiKey. + /// + public string OpenApiKey { get; set; } + } +} diff --git a/LearningHub.Nhs.UserApi.Services/OpenApiHttpClient.cs b/LearningHub.Nhs.UserApi.Services/OpenApiHttpClient.cs new file mode 100644 index 0000000..c7b034c --- /dev/null +++ b/LearningHub.Nhs.UserApi.Services/OpenApiHttpClient.cs @@ -0,0 +1,54 @@ +namespace LearningHub.Nhs.UserApi.Services +{ + using System; + using System.Net.Http; + using System.Net.Http.Headers; + using LearningHub.Nhs.UserApi.Services.Interface; + using Microsoft.Extensions.Options; + + /// + /// The OpenApiHttpClient. + /// + public class OpenApiHttpClient : IOpenApiHttpClient + { + /// + /// The Http client. + /// + private readonly HttpClient client; + + /// + /// Initializes a new instance of the class. + /// + /// The config. + /// The client. + public OpenApiHttpClient(IOptions config, HttpClient client) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); + } + + if (config == null) + { + throw new ArgumentNullException(nameof(config)); + } + + client.BaseAddress = new Uri(config.Value.OpenApiUrl); + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Add("X-API-KEY", config.Value.OpenApiKey.ToString()); + this.client = client; + } + + /// + /// The get client. + /// + /// + /// The . + /// + public HttpClient GetClient() + { + return this.client; + } + } +} diff --git a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs index f3dccd3..bb1084f 100644 --- a/LearningHub.Nhs.UserApi.Services/RegistrationService.cs +++ b/LearningHub.Nhs.UserApi.Services/RegistrationService.cs @@ -1,8 +1,10 @@ namespace LearningHub.Nhs.UserApi.Services { using System; + using System.Collections.Generic; using System.Linq; using System.Net; + using System.Net.Http; using System.Net.Mail; using System.Security.Cryptography; using System.Text; @@ -13,6 +15,7 @@ using elfhHub.Nhs.Models.Enums; using elfhHub.Nhs.Models.Validation; using LearningHub.Nhs.Models.Entities.External; + using LearningHub.Nhs.Models.GovNotifyMessaging; using LearningHub.Nhs.Models.Validation; using LearningHub.Nhs.UserApi.Repository; using LearningHub.Nhs.UserApi.Repository.Interface; @@ -21,6 +24,7 @@ using LearningHub.Nhs.UserApi.Services.Models; using LearningHub.Nhs.UserApi.Shared.Configuration; using Microsoft.Extensions.Options; + using Newtonsoft.Json; /// /// The registration service. @@ -50,6 +54,7 @@ public class RegistrationService : IRegistrationService private readonly IEmailLogRepository emailLogRepository; private readonly IUserGroupTypeInputValidationRepository userGroupTypeInputValidationRepository; private readonly Settings settings; + private readonly IOpenApiHttpClient openApiHttpClient; /// /// Initializes a new instance of the class. @@ -76,6 +81,7 @@ public class RegistrationService : IRegistrationService /// The email log repository. /// The userGroupType Input Validation repository. /// The settings. + /// The openApiHttpClient. public RegistrationService( IElfhUserRepository elfhUserRepository, IUserGroupTypeInputValidationRepository userGroupTypeInputValidationRepository, @@ -98,7 +104,8 @@ public RegistrationService( IExternalSystemRepository externalSystemRepository, IUserExternalSystemRepository userExternalSystemRepository, Repository.Interface.LH.IExternalSystemUserRepository externalSystemUserRepository, - IIpCountryLookupRepository ipCountryLookupRepository) + IIpCountryLookupRepository ipCountryLookupRepository, + IOpenApiHttpClient openApiHttpClient) { this.userService = userService; this.medicalCouncilService = medicalCouncilService; @@ -122,6 +129,7 @@ public RegistrationService( this.userGroupTypeInputValidationRepository = userGroupTypeInputValidationRepository; this.ipCountryLookupRepository = ipCountryLookupRepository; this.settings = settings.Value; + this.openApiHttpClient = openApiHttpClient; } /// @@ -341,53 +349,71 @@ public async Task RegisterUser(RegistrationRequestV if (registrationRequest.IsExternalUser == false) { - // Send registration confirmation email - /* This code was duplicated in the elfhHub.Nhs.Services.UserService.SendAdminPasswordResetEmail method, but tweaked for that email template. - * The GenerateUserPasswordValidationToken method (and its child methods) were all directly copied into the UserService. This is - * definitely a candidate for turning into a shared component after RR. */ var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryUserDefault)).IntValue; UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, userId); await this.userPasswordValidationTokenRepository.CreateAsync(userId, userPasswordValidationToken); - string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.LearningHubTenantId)).From; - EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.SuccessEmail_New, this.settings.LearningHubTenantId); - string bodyText = emailTemplate.Body; - bodyText = bodyText.Replace("[FullName]", (newUser.FirstName + " " + newUser.LastName).ToTitleCase()); - bodyText = bodyText.Replace("[FirstName]", newUser.FirstName.ToTitleCase()); - bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); - string resetUrl = this.settings.LearningHubUrl + userPasswordValidationToken.HashedToken; - //// emailTemplate.Body = emailTemplate.Body.Replace("[LHValidateURL]", resetUrl); - - bodyText = bodyText.Replace("[TimeLimit]", GenericHelper.GetPasswordTimeoutString(expiryMinutes)); - bodyText = bodyText.Replace("[ResetPasswordUrl]", userPasswordValidationToken.PasswordResetUrl); - bodyText = bodyText.Replace("[LogInUrl]", userPasswordValidationToken.LogOnUrl); - bodyText = bodyText.Replace("[UserName]", newUser.UserName); - bodyText = bodyText.Replace("[TenantUrl]", this.settings.LearningHubUrl); - - Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.LearningHubTenantId); - bodyText = bodyText.Replace("[TenantUrl]", this.settings.LearningHubUrl); - if (!string.IsNullOrEmpty(tenant.QuickStartGuideUrl)) - { - bodyText = bodyText.Replace("[QuickStartGuideUrl]", tenant.QuickStartGuideUrl); - } - - if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) - { - bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); - } + var personalisation = new Dictionary(); + personalisation["name"] = newUser.FirstName; + personalisation["username"] = newUser.UserName; + personalisation["password"] = userPasswordValidationToken.ValidateUrl; - EmailLog emailLog = new EmailLog() + var emailRequest = new EmailRequest { - EmailTemplateId = emailTemplate.Id, - FromEmailAddress = websiteEmailsFrom, - ToUserId = userId, - ToEmailAddress = registrationRequest.EmailAddress, - Subject = emailTemplate.Subject, - Body = bodyText, - TenantId = this.settings.LearningHubTenantId, - Priority = 1, + Recipient = registrationRequest.EmailAddress, + TemplateId = this.settings.GovNotifyTemplates.NHSLearningHubRegistration, + Personalisation = personalisation, }; - await this.emailLogRepository.CreateAsync(userId, emailLog); + + var client = this.openApiHttpClient.GetClient(); + + using var reqContent = new StringContent(JsonConvert.SerializeObject(emailRequest), Encoding.UTF8, "application/json"); + + await client.PostAsync("GovNotifyMessage/SendEmail", reqContent).ConfigureAwait(false); + + ////var expiryMinutes = (int)(await this.systemSettingRepository.GetByIdAsync((int)SystemSettingEnum.PasswordValidationExpiryUserDefault)).IntValue; + ////UserPasswordValidationTokenExtended userPasswordValidationToken = this.GenerateUserPasswordValidationToken(expiryMinutes, userId); + ////await this.userPasswordValidationTokenRepository.CreateAsync(userId, userPasswordValidationToken); + + ////string websiteEmailsFrom = (await this.tenantSmtpRepository.GetByIdAsync(this.settings.LearningHubTenantId)).From; + ////EmailTemplate emailTemplate = await this.emailTemplateRepository.GetByTypeAndTenantAsync((int)EmailTemplateTypeEnum.SuccessEmail_New, this.settings.LearningHubTenantId); + ////string bodyText = emailTemplate.Body; + ////bodyText = bodyText.Replace("[FullName]", (newUser.FirstName + " " + newUser.LastName).ToTitleCase()); + ////bodyText = bodyText.Replace("[FirstName]", newUser.FirstName.ToTitleCase()); + ////bodyText = bodyText.Replace("[PasswordValidateUrl]", userPasswordValidationToken.ValidateUrl); + ////string resetUrl = this.settings.LearningHubUrl + userPasswordValidationToken.HashedToken; + ////// emailTemplate.Body = emailTemplate.Body.Replace("[LHValidateURL]", resetUrl); + + ////bodyText = bodyText.Replace("[TimeLimit]", GenericHelper.GetPasswordTimeoutString(expiryMinutes)); + ////bodyText = bodyText.Replace("[ResetPasswordUrl]", userPasswordValidationToken.PasswordResetUrl); + ////bodyText = bodyText.Replace("[LogInUrl]", userPasswordValidationToken.LogOnUrl); + ////bodyText = bodyText.Replace("[UserName]", newUser.UserName); + ////bodyText = bodyText.Replace("[TenantUrl]", this.settings.LearningHubUrl); + + ////Tenant tenant = await this.tenantRepository.GetByIdAsync(this.settings.LearningHubTenantId); + ////bodyText = bodyText.Replace("[TenantUrl]", this.settings.LearningHubUrl); + ////if (!string.IsNullOrEmpty(tenant.QuickStartGuideUrl)) + ////{ + //// bodyText = bodyText.Replace("[QuickStartGuideUrl]", tenant.QuickStartGuideUrl); + ////} + + ////if (!string.IsNullOrEmpty(tenant.SupportFormUrl)) + ////{ + //// bodyText = bodyText.Replace("[SupportFormUrl]", tenant.SupportFormUrl); + ////} + + ////EmailLog emailLog = new EmailLog() + ////{ + //// EmailTemplateId = emailTemplate.Id, + //// FromEmailAddress = websiteEmailsFrom, + //// ToUserId = userId, + //// ToEmailAddress = registrationRequest.EmailAddress, + //// Subject = emailTemplate.Subject, + //// Body = bodyText, + //// TenantId = this.settings.LearningHubTenantId, + //// Priority = 1, + ////}; + ////await this.emailLogRepository.CreateAsync(userId, emailLog); } } diff --git a/LearningHub.Nhs.UserApi.Shared/Configuration/GovNotifyTemplates.cs b/LearningHub.Nhs.UserApi.Shared/Configuration/GovNotifyTemplates.cs new file mode 100644 index 0000000..9166666 --- /dev/null +++ b/LearningHub.Nhs.UserApi.Shared/Configuration/GovNotifyTemplates.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.UserApi.Shared.Configuration +{ + /// + /// GovNotifyTemplates. + /// + public class GovNotifyTemplates + { + /// + /// Gets or sets the ForgottenUsernameOrPassword ID. + /// + public string ForgottenUsernameOrPassword { get; set; } + + /// + /// Gets or sets the NHSLearningHubRegistration ID. + /// + public string NHSLearningHubRegistration { get; set; } + } +} diff --git a/LearningHub.Nhs.UserApi.Shared/Configuration/Settings.cs b/LearningHub.Nhs.UserApi.Shared/Configuration/Settings.cs index b2a9c1b..ca4bf53 100644 --- a/LearningHub.Nhs.UserApi.Shared/Configuration/Settings.cs +++ b/LearningHub.Nhs.UserApi.Shared/Configuration/Settings.cs @@ -49,5 +49,10 @@ public class Settings /// Gets or sets the security questions required. /// public int SecurityQuestionsRequired { get; set; } + + /// + /// Gets or sets the GovNotifyTemplates. + /// + public GovNotifyTemplates GovNotifyTemplates { get; set; } } } diff --git a/LearningHub.Nhs.UserApi/Program.cs b/LearningHub.Nhs.UserApi/Program.cs index c82ae49..d560f0e 100644 --- a/LearningHub.Nhs.UserApi/Program.cs +++ b/LearningHub.Nhs.UserApi/Program.cs @@ -22,7 +22,7 @@ builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); builder.Host.UseNLog(); - builder.Services.ConfigureServices(builder.Configuration); + builder.Services.ConfigureServices(builder.Configuration, builder.Environment); var app = builder.Build(); diff --git a/LearningHub.Nhs.UserApi/ServiceCollectionExtension.cs b/LearningHub.Nhs.UserApi/ServiceCollectionExtension.cs index c4bc100..696a582 100644 --- a/LearningHub.Nhs.UserApi/ServiceCollectionExtension.cs +++ b/LearningHub.Nhs.UserApi/ServiceCollectionExtension.cs @@ -9,6 +9,7 @@ using LearningHub.Nhs.UserApi.Services; using LearningHub.Nhs.UserApi.Services.Interface; using LearningHub.Nhs.UserApi.Shared.Configuration; + using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -24,13 +25,14 @@ public static class ServiceCollectionExtension /// /// IServiceCollection. /// IConfiguration. - public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration) + /// IWebHostEnvironment. + public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment env) { services.AddOptions(); services.AddApplicationInsightsTelemetry(); - services.AddMappings(configuration); + services.AddMappings(configuration, env); services.Configure(configuration.GetSection("Settings")); diff --git a/LearningHub.Nhs.UserApi/ServiceMappings.cs b/LearningHub.Nhs.UserApi/ServiceMappings.cs index b7cd803..bbdba5f 100644 --- a/LearningHub.Nhs.UserApi/ServiceMappings.cs +++ b/LearningHub.Nhs.UserApi/ServiceMappings.cs @@ -1,5 +1,6 @@ namespace LearningHub.Nhs.UserApi { + using System.Net.Http; using AutoMapper; using elfhHub.Nhs.Models.Automapper; using IdentityServer4.AccessTokenValidation; @@ -11,9 +12,11 @@ namespace LearningHub.Nhs.UserApi using LearningHub.Nhs.UserApi.Services.Interface; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using ElfhMap = LearningHub.Nhs.UserApi.Repository.ElfhMap; using LHMap = LearningHub.Nhs.UserApi.Repository.LHMap; @@ -28,13 +31,14 @@ public static class ServiceMappings /// /// The services. /// The configuration. - public static void AddMappings(this IServiceCollection services, IConfiguration configuration) + /// The IWebHostEnvironment. + public static void AddMappings(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment env) { services.AddLearningHubDBMappings(configuration); services.AddElfhDBMappings(configuration); - services.AddServices(); + services.AddServices(configuration, env); services.AddAuthentication(configuration); @@ -169,7 +173,7 @@ private static void AddLearningHubDBMappings(this IServiceCollection services, I services.AddScoped(); } - private static void AddServices(this IServiceCollection services) + private static void AddServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment env) { services.AddScoped(); services.AddScoped(); @@ -195,6 +199,22 @@ private static void AddServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + + services.Configure(configuration.GetSection("OpenApiConfig")); + if (env.IsDevelopment()) + { + services.AddHttpClient() + .ConfigurePrimaryHttpMessageHandler( + () => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, + }); + } + else + { + services.AddHttpClient(); + } } private static void AddAuthentication(this IServiceCollection services, IConfiguration configuration) diff --git a/LearningHub.Nhs.UserApi/appsettings.json b/LearningHub.Nhs.UserApi/appsettings.json index 00f0cc4..f7fce96 100644 --- a/LearningHub.Nhs.UserApi/appsettings.json +++ b/LearningHub.Nhs.UserApi/appsettings.json @@ -36,6 +36,14 @@ "ElfhUserLoadByUserIdKey": "", "ElfhUserLoadByUserNameKey": "" }, - "SecurityQuestionsRequired": 2 + "SecurityQuestionsRequired": 2, + "GovNotifyTemplates": { + "ForgottenUsernameOrPassword": "", + "NHSLearningHubRegistration": "" + } + }, + "OpenApiConfig": { + "OpenApiUrl": "", + "OpenApiKey": "" } } \ No newline at end of file