From c0f4cd20977bb6bdefab14927dfb598bf70e3d30 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 12 Jul 2023 16:37:18 -0400 Subject: [PATCH] add a UserManager to simplify user creation, improve response validation in ServiceBase, allow Section component to support parameter changes --- Oqtane.Client/Modules/Controls/Section.razor | 13 +- Oqtane.Client/Services/ServiceBase.cs | 22 ++-- Oqtane.Server/Controllers/UserController.cs | 121 ++++-------------- .../OqtaneServiceCollectionExtensions.cs | 21 +-- .../Infrastructure/Interfaces/IUserManager.cs | 10 ++ Oqtane.Server/Infrastructure/UserManager.cs | 109 ++++++++++++++++ Oqtane.Shared/Models/User.cs | 6 + 7 files changed, 178 insertions(+), 124 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/Interfaces/IUserManager.cs create mode 100644 Oqtane.Server/Infrastructure/UserManager.cs diff --git a/Oqtane.Client/Modules/Controls/Section.razor b/Oqtane.Client/Modules/Controls/Section.razor index 928f1edc..22737d6c 100644 --- a/Oqtane.Client/Modules/Controls/Section.razor +++ b/Oqtane.Client/Modules/Controls/Section.razor @@ -40,19 +40,10 @@ [Parameter] public string Expanded { get; set; } // optional - will default to false if not provided - protected override void OnInitialized() + protected override void OnParametersSet() { - _heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name; + _heading = !string.IsNullOrEmpty(Heading) ? Localize(nameof(Heading), Heading) : Localize(nameof(Name), Name); _expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded.ToLower() : "false"; if (_expanded == "true") { _show = "show"; } } - - protected override void OnParametersSet() - { - base.OnParametersSet(); - - _heading = !string.IsNullOrEmpty(Heading) - ? Localize(nameof(Heading), Heading) - : Localize(nameof(Name), Name); - } } diff --git a/Oqtane.Client/Services/ServiceBase.cs b/Oqtane.Client/Services/ServiceBase.cs index 00b2412a..5da7762c 100644 --- a/Oqtane.Client/Services/ServiceBase.cs +++ b/Oqtane.Client/Services/ServiceBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Json; +using System.Reflection.Metadata.Ecma335; using System.Threading; using System.Threading.Tasks; using Oqtane.Models; @@ -105,7 +106,7 @@ namespace Oqtane.Services protected async Task GetAsync(string uri) { var response = await GetHttpClient().GetAsync(uri); - CheckResponse(response); + CheckResponse(response, uri); } protected async Task GetStringAsync(string uri) @@ -139,7 +140,7 @@ namespace Oqtane.Services protected async Task GetJsonAsync(string uri) { var response = await GetHttpClient().GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); - if (CheckResponse(response) && ValidateJsonContent(response.Content)) + if (CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { return await response.Content.ReadFromJsonAsync(); } @@ -150,7 +151,7 @@ namespace Oqtane.Services protected async Task PutAsync(string uri) { var response = await GetHttpClient().PutAsync(uri, null); - CheckResponse(response); + CheckResponse(response, uri); } protected async Task PutJsonAsync(string uri, T value) @@ -161,7 +162,7 @@ namespace Oqtane.Services protected async Task PutJsonAsync(string uri, TValue value) { var response = await GetHttpClient().PutAsJsonAsync(uri, value); - if (CheckResponse(response) && ValidateJsonContent(response.Content)) + if (CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); return result; @@ -172,7 +173,7 @@ namespace Oqtane.Services protected async Task PostAsync(string uri) { var response = await GetHttpClient().PostAsync(uri, null); - CheckResponse(response); + CheckResponse(response, uri); } protected async Task PostJsonAsync(string uri, T value) @@ -183,7 +184,7 @@ namespace Oqtane.Services protected async Task PostJsonAsync(string uri, TValue value) { var response = await GetHttpClient().PostAsJsonAsync(uri, value); - if (CheckResponse(response) && ValidateJsonContent(response.Content)) + if (CheckResponse(response, uri) && ValidateJsonContent(response.Content)) { var result = await response.Content.ReadFromJsonAsync(); return result; @@ -195,11 +196,16 @@ namespace Oqtane.Services protected async Task DeleteAsync(string uri) { var response = await GetHttpClient().DeleteAsync(uri); - CheckResponse(response); + CheckResponse(response, uri); } - private bool CheckResponse(HttpResponseMessage response) + private bool CheckResponse(HttpResponseMessage response, string uri) { + if (!response.RequestMessage.RequestUri.AbsolutePath.StartsWith("/api/")) + { + Console.WriteLine($"Request: {uri} Not Mapped To A Controller Method"); + return false; + } if (response.IsSuccessStatusCode) return true; if (response.StatusCode != HttpStatusCode.NoContent && response.StatusCode != HttpStatusCode.NotFound) { diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 1055012a..d2581ec0 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -29,13 +29,14 @@ namespace Oqtane.Controllers private readonly ITenantManager _tenantManager; private readonly INotificationRepository _notifications; private readonly IFolderRepository _folders; + private readonly IUserManager _userManager; private readonly ISiteRepository _sites; private readonly IUserPermissions _userPermissions; private readonly IJwtManager _jwtManager; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; - public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger) + public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger) { _users = users; _userRoles = userRoles; @@ -44,6 +45,7 @@ namespace Oqtane.Controllers _tenantManager = tenantManager; _notifications = notifications; _folders = folders; + _userManager = userManager; _sites = sites; _userPermissions = userPermissions; _jwtManager = jwtManager; @@ -141,8 +143,28 @@ namespace Oqtane.Controllers { if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId) { - var User = await CreateUser(user); - return User; + bool allowregistration; + if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin)) + { + user.EmailConfirmed = true; + allowregistration = true; + } + else + { + user.EmailConfirmed = false; + allowregistration = _sites.GetSite(user.SiteId).AllowRegistration; + } + + if (allowregistration) + { + user = await _userManager.AddUser(user); + } + else + { + _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "User Registration Is Not Enabled For Site. User Was Not Added {User}", user); + } + + return user; } else { @@ -153,99 +175,6 @@ namespace Oqtane.Controllers } } - private async Task CreateUser(User user) - { - User newUser = null; - - bool verified; - bool allowregistration; - if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin)) - { - verified = true; - allowregistration = true; - } - else - { - verified = false; - allowregistration = _sites.GetSite(user.SiteId).AllowRegistration; - } - - if (allowregistration) - { - bool succeeded; - string errors = ""; - IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); - if (identityuser == null) - { - identityuser = new IdentityUser(); - identityuser.UserName = user.Username; - identityuser.Email = user.Email; - identityuser.EmailConfirmed = verified; - var result = await _identityUserManager.CreateAsync(identityuser, user.Password); - succeeded = result.Succeeded; - if (!succeeded) - { - errors = string.Join(", ", result.Errors.Select(e => e.Description)); - } - } - else - { - var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); - succeeded = result.Succeeded; - if (!succeeded) - { - errors = "Password Not Valid For User"; - } - verified = succeeded; - } - - if (succeeded) - { - user.LastLoginOn = null; - user.LastIPAddress = ""; - newUser = _users.AddUser(user); - _syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, newUser.UserId, SyncEventActions.Create); - } - else - { - _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {Username} - {Errors}", user.Username, errors); - } - - if (newUser != null) - { - if (!verified) - { - string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); - string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, newUser, "User Account Verification", body); - _notifications.AddNotification(notification); - } - else - { - string url = HttpContext.Request.Scheme + "://" + _tenantManager.GetAlias().Name; - string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You. Please Use The Following Link To Access The Site:\n\n" + url + "\n\nThank You!"; - var notification = new Notification(user.SiteId, newUser, "User Account Notification", body); - _notifications.AddNotification(notification); - } - - newUser.Password = ""; // remove sensitive information - _logger.Log(user.SiteId, LogLevel.Information, this, LogFunction.Create, "User Added {User}", newUser); - } - else - { - user.Password = ""; // remove sensitive information - _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {User}", user); - } - } - else - { - _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "User Registration Is Not Enabled For Site. User Was Not Added {User}", user); - } - - return newUser; - } - // PUT api//5 [HttpPut("{id}")] [Authorize] diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index a7239af2..e8daf17b 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -73,13 +73,7 @@ namespace Microsoft.Extensions.DependencyInjection internal static IServiceCollection AddOqtaneTransientServices(this IServiceCollection services) { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - + // repositories services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -95,7 +89,6 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -104,11 +97,21 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + // managers + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + // obsolete - replaced by ITenantManager services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/Interfaces/IUserManager.cs b/Oqtane.Server/Infrastructure/Interfaces/IUserManager.cs new file mode 100644 index 00000000..4c2f184f --- /dev/null +++ b/Oqtane.Server/Infrastructure/Interfaces/IUserManager.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Oqtane.Models; + +namespace Oqtane.Infrastructure +{ + public interface IUserManager + { + Task AddUser(User user); + } +} diff --git a/Oqtane.Server/Infrastructure/UserManager.cs b/Oqtane.Server/Infrastructure/UserManager.cs new file mode 100644 index 00000000..a7a84607 --- /dev/null +++ b/Oqtane.Server/Infrastructure/UserManager.cs @@ -0,0 +1,109 @@ +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Oqtane.Enums; +using Oqtane.Models; +using Oqtane.Repository; +using Oqtane.Shared; + +namespace Oqtane.Infrastructure +{ + public class UserManager : IUserManager + { + private readonly IUserRepository _users; + private readonly UserManager _identityUserManager; + private readonly SignInManager _identitySignInManager; + private readonly ITenantManager _tenantManager; + private readonly INotificationRepository _notifications; + private readonly IFolderRepository _folders; + private readonly ISyncManager _syncManager; + private readonly ILogManager _logger; + + public UserManager(IUserRepository users, UserManager identityUserManager, SignInManager identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ILogManager logger) + { + _users = users; + _identityUserManager = identityUserManager; + _identitySignInManager = identitySignInManager; + _tenantManager = tenantManager; + _notifications = notifications; + _folders = folders; + _syncManager = syncManager; + _logger = logger; + } + + public async Task AddUser(User user) + { + User User = null; + var alias = _tenantManager.GetAlias(); + bool succeeded = false; + string errors = ""; + + IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); + if (identityuser == null) + { + identityuser = new IdentityUser(); + identityuser.UserName = user.Username; + identityuser.Email = user.Email; + identityuser.EmailConfirmed = user.EmailConfirmed; + var result = await _identityUserManager.CreateAsync(identityuser, user.Password); + succeeded = result.Succeeded; + if (!succeeded) + { + errors = string.Join(", ", result.Errors.Select(e => e.Description)); + } + } + else + { + var result = await _identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); + succeeded = result.Succeeded; + if (!succeeded) + { + errors = "Password Not Valid For User"; + } + user.EmailConfirmed = succeeded; + } + + if (succeeded) + { + user.LastLoginOn = null; + user.LastIPAddress = ""; + User = _users.AddUser(user); + _syncManager.AddSyncEvent(alias.TenantId, EntityNames.User, User.UserId, SyncEventActions.Create); + } + else + { + _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {Username} - {Errors}", user.Username, errors); + } + + if (User != null) + { + if (!user.EmailConfirmed) + { + string token = await _identityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); + string url = alias.Protocol + "://" + alias.Name + "/login?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string body = "Dear " + user.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, User, "User Account Verification", body); + _notifications.AddNotification(notification); + } + else + { + string url = alias.Protocol + "://" + alias.Name; + string body = "Dear " + user.DisplayName + ",\n\nA User Account Has Been Successfully Created For You. Please Use The Following Link To Access The Site:\n\n" + url + "\n\nThank You!"; + var notification = new Notification(user.SiteId, User, "User Account Notification", body); + _notifications.AddNotification(notification); + } + + User.Password = ""; // remove sensitive information + _logger.Log(user.SiteId, LogLevel.Information, this, LogFunction.Create, "User Added {User}", User); + } + else + { + user.Password = ""; // remove sensitive information + _logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Create, "Unable To Add User {User}", user); + } + + return User; + } + } +} diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index 4fe450dc..751b6e01 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -99,5 +99,11 @@ namespace Oqtane.Models { get => "Users\\" + UserId.ToString() + "\\"; } + + /// + /// Information if this user's email address is confirmed (set during user creation) + /// + [NotMapped] + public bool EmailConfirmed { get; set; } } }