From 02977620655f9be00081cfab28d3e4938be4c3c1 Mon Sep 17 00:00:00 2001 From: thomasduft Date: Wed, 26 Jan 2022 10:31:43 +0000 Subject: [PATCH] added AccountService and removed EF dependency in OpentIddict-UI Identity Api project --- README.md | 2 +- notes.txt | 18 +- samples/Server/ConfigureServices.cs | 2 +- .../Account/AccountApiService.cs | 116 +++--------- .../DependencyInjection.cs | 6 +- .../{RoleService.cs => RoleApiService.cs} | 6 +- .../Role/RoleController.cs | 4 +- .../tomware.OpenIddict.UI.Identity.Api.csproj | 1 - .../DTOs/ChangePasswordParam.cs | 9 + .../DTOs/RegisterUserParam.cs | 9 + .../DTOs/UserInfo.cs | 27 +++ .../DTOs/UserParam.cs | 4 + .../DependencyInjection.cs | 19 +- .../Interfaces/IAccountService.cs | 16 ++ .../Interfaces/IUserCreationService.cs | 10 ++ .../Services}/UserCreationStrategyService.cs | 12 +- ...tomware.OpenIddict.UI.Identity.Core.csproj | 1 + .../DependencyInjection.cs | 17 +- .../Services/AccountService.cs | 166 ++++++++++++++++++ tests/Unit/RoleControllerTest.cs | 6 +- 20 files changed, 317 insertions(+), 134 deletions(-) rename src/identity/OpenIddict.UI.Identity.Api/Role/{RoleService.cs => RoleApiService.cs} (93%) create mode 100644 src/identity/OpenIddict.UI.Identity.Core/DTOs/ChangePasswordParam.cs create mode 100644 src/identity/OpenIddict.UI.Identity.Core/DTOs/RegisterUserParam.cs create mode 100644 src/identity/OpenIddict.UI.Identity.Core/DTOs/UserInfo.cs create mode 100644 src/identity/OpenIddict.UI.Identity.Core/DTOs/UserParam.cs create mode 100644 src/identity/OpenIddict.UI.Identity.Core/Interfaces/IAccountService.cs create mode 100644 src/identity/OpenIddict.UI.Identity.Core/Interfaces/IUserCreationService.cs rename src/identity/{OpenIddict.UI.Identity.Api/Account => OpenIddict.UI.Identity.Core/Services}/UserCreationStrategyService.cs (75%) create mode 100644 src/identity/OpenIddict.UI.Identity.Infrastructure/Services/AccountService.cs diff --git a/README.md b/README.md index 09cee4b..d7945a9 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ services.AddOpenIddict() } }) // Register the EF based UI Store for the ASP.NET Identity related entities. - .AddUIIdentityStore(options => + .AddUIIdentityStore(options => { options.OpenIddictUIIdentityContext = builder => builder.UseSqlite(Configuration.GetConnectionString("DefaultConnection"), diff --git a/notes.txt b/notes.txt index a96bf2b..89d361a 100644 --- a/notes.txt +++ b/notes.txt @@ -1,14 +1,7 @@ TODO: -- go full razor pages - - https://github.com/openiddict/openiddict-samples/tree/dev/samples/Contruum/Contruum.Server - - -- migrate to .net Program.cs file - - refactor DB-migrations in sample server - - -- load options.Permissions from database instead of configuring it +- for next release 1.5.0 + - mention small API change when registering .AddUIIdentityStore the TApplicationUser is not mandatory! - client refactoring @@ -18,6 +11,13 @@ TODO: - check server stylings not removed +- load options.Permissions from database instead of configuring it + + +- go full razor pages + - https://github.com/openiddict/openiddict-samples/tree/dev/samples/Contruum/Contruum.Server + + - organize APIs a bit more by feature - i.e. identity -> AccountController - AccountController diff --git a/samples/Server/ConfigureServices.cs b/samples/Server/ConfigureServices.cs index 28e57b2..ac651b6 100644 --- a/samples/Server/ConfigureServices.cs +++ b/samples/Server/ConfigureServices.cs @@ -172,7 +172,7 @@ string environmentName }; }) // Register the EF based UI Store for the ASP.NET Identity related entities. - .AddUIIdentityStore(options => + .AddUIIdentityStore(options => { options.OpenIddictUIIdentityContext = builder => builder.UseSqlite(configuration.GetConnectionString("DefaultConnection"), diff --git a/src/identity/OpenIddict.UI.Identity.Api/Account/AccountApiService.cs b/src/identity/OpenIddict.UI.Identity.Api/Account/AccountApiService.cs index 7aee249..185965e 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/Account/AccountApiService.cs +++ b/src/identity/OpenIddict.UI.Identity.Api/Account/AccountApiService.cs @@ -1,11 +1,12 @@ using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; +using tomware.OpenIddict.UI.Identity.Core; +using tomware.OpenIddict.UI.Suite.Core; + namespace tomware.OpenIddict.UI.Identity.Api { public interface IAccountApiService @@ -21,50 +22,36 @@ public interface IAccountApiService public class AccountApiService : IAccountApiService where TIdentityUser : IdentityUser, new() { - // TODO: move UserManager down to Infrastructure - // using Microsoft.EntityFrameworkCore is wrong in the Api layer! - // remove package dependency - private readonly UserManager _manager; - private readonly IUserCreationStrategy _userCreationStrategy; + private readonly IAccountService _accountService; public AccountApiService( - UserManager manager, - IUserCreationStrategy userCreationStrategy + IAccountService accountService ) { - _manager = manager - ?? throw new ArgumentNullException(nameof(manager)); - _userCreationStrategy = userCreationStrategy - ?? throw new ArgumentNullException(nameof(userCreationStrategy)); + _accountService = accountService + ?? throw new ArgumentNullException(nameof(accountService)); } public async Task RegisterAsync( RegisterUserViewModel model ) { - var identiyUser = _userCreationStrategy.CreateUser(model); + var param = SimpleMapper.From(model); - return await _manager.CreateAsync(identiyUser, model.Password); + return await _accountService.RegisterAsync(param); } public async Task ChangePasswordAsync(ChangePasswordViewModel model) { - var user = await _manager.FindByNameAsync(model.UserName); + var param = SimpleMapper.From(model); - return await _manager.ChangePasswordAsync( - user, - model.CurrentPassword, - model.NewPassword - ); + return await _accountService.ChangePasswordAsync(param); } public async Task> GetUsersAsync() { // TODO: Paging ??? - var items = await _manager.Users - .OrderBy(u => u.UserName) - .AsNoTracking() - .ToListAsync(); + var items = await _accountService.GetUsersAsync(); return items.Select(u => new UserViewModel { @@ -77,11 +64,7 @@ public async Task> GetUsersAsync() public async Task GetUserAsync(string id) { - var user = await _manager.FindByIdAsync(id); - var roles = await _manager.GetRolesAsync(user); - var claims = await _manager.GetClaimsAsync(user); - - var isLockedOut = await _manager.IsLockedOutAsync(user); + var user = await _accountService.GetUserAsync(id); return new UserViewModel { @@ -89,13 +72,13 @@ public async Task GetUserAsync(string id) UserName = user.UserName, Email = user.Email, LockoutEnabled = user.LockoutEnabled, - IsLockedOut = isLockedOut, - Claims = new List(claims.ToList().Select(x => new ClaimViewModel + IsLockedOut = user.IsLockedOut, + Claims = new List(user.Claims.Select(x => new ClaimViewModel { Type = x.Type, Value = x.Value })), - Roles = roles.ToList() + Roles = user.Roles }; } @@ -105,74 +88,21 @@ public async Task UpdateAsync(UserViewModel model) if (string.IsNullOrWhiteSpace(model.Id)) throw new InvalidOperationException(nameof(model.Id)); - var user = await _manager.FindByIdAsync(model.Id); - user.UserName = model.UserName; - user.Email = model.Email; - user.LockoutEnabled = model.LockoutEnabled; - - var result = await _manager.UpdateAsync(user); - if (!result.Succeeded) - { - return result; - } - - result = await AssignClaimsAsync( - user, - model.Claims.Select(x => new Claim(x.Type, x.Value)).ToList() - ); - if (!result.Succeeded) - { - return result; - } - - result = await AssignRolesAsync(user, model.Roles); - if (!result.Succeeded) + var param = SimpleMapper.From(model); + param.Claims = new List(model.Claims.Select(c => new ClaimInfo { - return result; - } + Type = c.Type, + Value = c.Value + })); - return result; + return await _accountService.UpdateAsync(param); } public async Task DeleteAsync(string id) { if (string.IsNullOrWhiteSpace(id)) throw new InvalidOperationException(nameof(id)); - var user = await _manager.FindByIdAsync(id); - - return await _manager.DeleteAsync(user); - } - - private async Task AssignClaimsAsync( - TIdentityUser user, - IEnumerable claims - ) - { - // removing all claims - var existingClaims = await _manager.GetClaimsAsync(user); - await _manager.RemoveClaimsAsync(user, existingClaims); - - // assigning claims - return await _manager.AddClaimsAsync( - user, - claims - ); - } - - private async Task AssignRolesAsync( - TIdentityUser user, - IEnumerable roles - ) - { - // removing all roles - var existingRoles = await _manager.GetRolesAsync(user); - await _manager.RemoveFromRolesAsync(user, existingRoles); - - // assigning roles - return await _manager.AddToRolesAsync( - user, - roles - ); + return await _accountService.DeleteAsync(id); } } } diff --git a/src/identity/OpenIddict.UI.Identity.Api/DependencyInjection.cs b/src/identity/OpenIddict.UI.Identity.Api/DependencyInjection.cs index 81f1927..b812f2e 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/DependencyInjection.cs +++ b/src/identity/OpenIddict.UI.Identity.Api/DependencyInjection.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Authorization; using tomware.OpenIddict.UI.Suite.Api; +using tomware.OpenIddict.UI.Identity.Core; namespace tomware.OpenIddict.UI.Identity.Api { @@ -35,7 +36,7 @@ this OpenIddictBuilder builder ) where TApplicationUser : IdentityUser, new() { builder.Services - .AddTransient, UserNameUserCreationStrategy>(); + .AddUserNameUserCreationStrategy(); return builder; } @@ -44,9 +45,8 @@ private static IServiceCollection AddApiServices( this IServiceCollection services ) where TApplicationUser : IdentityUser, new() { - services.AddTransient, EmailUserCreationStrategy>(); services.AddTransient>(); - services.AddTransient(); + services.AddTransient(); services.AddTransient(); return services; diff --git a/src/identity/OpenIddict.UI.Identity.Api/Role/RoleService.cs b/src/identity/OpenIddict.UI.Identity.Api/Role/RoleApiService.cs similarity index 93% rename from src/identity/OpenIddict.UI.Identity.Api/Role/RoleService.cs rename to src/identity/OpenIddict.UI.Identity.Api/Role/RoleApiService.cs index 1102724..29e9b75 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/Role/RoleService.cs +++ b/src/identity/OpenIddict.UI.Identity.Api/Role/RoleApiService.cs @@ -6,7 +6,7 @@ namespace tomware.OpenIddict.UI.Identity.Api { - public interface IRoleService + public interface IRoleApiService { Task> GetRolesAsync(); @@ -19,11 +19,11 @@ public interface IRoleService Task DeleteAsync(string id); } - public class RoleService : IRoleService + public class RoleApiService : IRoleApiService { private readonly RoleManager _manager; - public RoleService(RoleManager manager) + public RoleApiService(RoleManager manager) { _manager = manager; } diff --git a/src/identity/OpenIddict.UI.Identity.Api/Role/RoleController.cs b/src/identity/OpenIddict.UI.Identity.Api/Role/RoleController.cs index 9731602..6ec9872 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/Role/RoleController.cs +++ b/src/identity/OpenIddict.UI.Identity.Api/Role/RoleController.cs @@ -8,9 +8,9 @@ namespace tomware.OpenIddict.UI.Identity.Api [Route("roles")] public class RoleController : IdentityApiController { - private readonly IRoleService _service; + private readonly IRoleApiService _service; - public RoleController(IRoleService service) + public RoleController(IRoleApiService service) { _service = service; } diff --git a/src/identity/OpenIddict.UI.Identity.Api/tomware.OpenIddict.UI.Identity.Api.csproj b/src/identity/OpenIddict.UI.Identity.Api/tomware.OpenIddict.UI.Identity.Api.csproj index a06a3be..8fe7362 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/tomware.OpenIddict.UI.Identity.Api.csproj +++ b/src/identity/OpenIddict.UI.Identity.Api/tomware.OpenIddict.UI.Identity.Api.csproj @@ -19,7 +19,6 @@ - diff --git a/src/identity/OpenIddict.UI.Identity.Core/DTOs/ChangePasswordParam.cs b/src/identity/OpenIddict.UI.Identity.Core/DTOs/ChangePasswordParam.cs new file mode 100644 index 0000000..ea87d1c --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/DTOs/ChangePasswordParam.cs @@ -0,0 +1,9 @@ +namespace tomware.OpenIddict.UI.Identity.Core +{ + public class ChangePasswordParam + { + public string CurrentPassword { get; set; } + public string NewPassword { get; set; } + public string UserName { get; set; } + } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Core/DTOs/RegisterUserParam.cs b/src/identity/OpenIddict.UI.Identity.Core/DTOs/RegisterUserParam.cs new file mode 100644 index 0000000..72180c8 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/DTOs/RegisterUserParam.cs @@ -0,0 +1,9 @@ +namespace tomware.OpenIddict.UI.Identity.Core +{ + public class RegisterUserParam + { + public string UserName { get; set; } + public string Email { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserInfo.cs b/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserInfo.cs new file mode 100644 index 0000000..9c68730 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserInfo.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace tomware.OpenIddict.UI.Identity.Core +{ + public class ClaimInfo + { + public string Type { get; set; } + public string Value { get; set; } + } + + public class UserInfo + { + public string Id { get; set; } + + public string UserName { get; set; } + + public string Email { get; set; } + + public bool LockoutEnabled { get; set; } + + public bool IsLockedOut { get; set; } + + public List Claims { get; set; } = new List(); + + public List Roles { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserParam.cs b/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserParam.cs new file mode 100644 index 0000000..54333c5 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/DTOs/UserParam.cs @@ -0,0 +1,4 @@ +namespace tomware.OpenIddict.UI.Identity.Core +{ + public class UserParam : UserInfo { } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Core/DependencyInjection.cs b/src/identity/OpenIddict.UI.Identity.Core/DependencyInjection.cs index 3ab59c5..b9a2256 100644 --- a/src/identity/OpenIddict.UI.Identity.Core/DependencyInjection.cs +++ b/src/identity/OpenIddict.UI.Identity.Core/DependencyInjection.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; namespace tomware.OpenIddict.UI.Identity.Core @@ -6,11 +7,25 @@ namespace tomware.OpenIddict.UI.Identity.Core [ExcludeFromCodeCoverage] public static class OpenIddictUIIdentityCoreServicesExtensions { - public static IServiceCollection AddOpenIddictUIIdentityCoreServices( + public static IServiceCollection AddOpenIddictUIIdentityCoreServices( this IServiceCollection services - ) + ) where TApplicationUser : IdentityUser, new() { services.AddTransient(); + services.AddTransient, EmailUserCreationStrategy>(); + + return services; + } + + /// + /// Registers the UserName to UserName UserCreationStrategy. + /// + public static IServiceCollection AddUserNameUserCreationStrategy( + this IServiceCollection services + ) where TApplicationUser : IdentityUser, new() + { + services + .AddTransient, UserNameUserCreationStrategy>(); return services; } diff --git a/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IAccountService.cs b/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IAccountService.cs new file mode 100644 index 0000000..80bee08 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IAccountService.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Identity; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace tomware.OpenIddict.UI.Identity.Core +{ + public interface IAccountService + { + Task RegisterAsync(RegisterUserParam model); + Task ChangePasswordAsync(ChangePasswordParam model); + Task> GetUsersAsync(); + Task GetUserAsync(string id); + Task UpdateAsync(UserParam model); + Task DeleteAsync(string id); + } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IUserCreationService.cs b/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IUserCreationService.cs new file mode 100644 index 0000000..523a8e2 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Core/Interfaces/IUserCreationService.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Identity; + +namespace tomware.OpenIddict.UI.Identity.Core +{ + public interface IUserCreationStrategy + where TIdentityUser : IdentityUser + { + TIdentityUser CreateUser(RegisterUserParam model); + } +} \ No newline at end of file diff --git a/src/identity/OpenIddict.UI.Identity.Api/Account/UserCreationStrategyService.cs b/src/identity/OpenIddict.UI.Identity.Core/Services/UserCreationStrategyService.cs similarity index 75% rename from src/identity/OpenIddict.UI.Identity.Api/Account/UserCreationStrategyService.cs rename to src/identity/OpenIddict.UI.Identity.Core/Services/UserCreationStrategyService.cs index 811827e..fd1de87 100644 --- a/src/identity/OpenIddict.UI.Identity.Api/Account/UserCreationStrategyService.cs +++ b/src/identity/OpenIddict.UI.Identity.Core/Services/UserCreationStrategyService.cs @@ -1,13 +1,7 @@ using Microsoft.AspNetCore.Identity; -namespace tomware.OpenIddict.UI.Identity.Api +namespace tomware.OpenIddict.UI.Identity.Core { - public interface IUserCreationStrategy - where TIdentityUser : IdentityUser - { - TIdentityUser CreateUser(RegisterUserViewModel model); - } - /// /// Maps the Email property to the IdentityUser.UserName property. /// Note: If you use the ASP.NET Core Identity UI this will be the default. @@ -17,7 +11,7 @@ public class EmailUserCreationStrategy : IUserCreationStrategy where TIdentityUser : IdentityUser, new() { - public TIdentityUser CreateUser(RegisterUserViewModel model) + public TIdentityUser CreateUser(RegisterUserParam model) { return new TIdentityUser { @@ -35,7 +29,7 @@ public class UserNameUserCreationStrategy : IUserCreationStrategy where TIdentityUser : IdentityUser, new() { - public TIdentityUser CreateUser(RegisterUserViewModel model) + public TIdentityUser CreateUser(RegisterUserParam model) { return new TIdentityUser { diff --git a/src/identity/OpenIddict.UI.Identity.Core/tomware.OpenIddict.UI.Identity.Core.csproj b/src/identity/OpenIddict.UI.Identity.Core/tomware.OpenIddict.UI.Identity.Core.csproj index c8a0d34..00250f4 100644 --- a/src/identity/OpenIddict.UI.Identity.Core/tomware.OpenIddict.UI.Identity.Core.csproj +++ b/src/identity/OpenIddict.UI.Identity.Core/tomware.OpenIddict.UI.Identity.Core.csproj @@ -10,6 +10,7 @@ + diff --git a/src/identity/OpenIddict.UI.Identity.Infrastructure/DependencyInjection.cs b/src/identity/OpenIddict.UI.Identity.Infrastructure/DependencyInjection.cs index 4bcbfa6..c16272b 100644 --- a/src/identity/OpenIddict.UI.Identity.Infrastructure/DependencyInjection.cs +++ b/src/identity/OpenIddict.UI.Identity.Infrastructure/DependencyInjection.cs @@ -5,18 +5,19 @@ using tomware.OpenIddict.UI.Suite.Core; using tomware.OpenIddict.UI.Identity.Core; +using Microsoft.AspNetCore.Identity; namespace tomware.OpenIddict.UI.Identity.Infrastructure { [ExcludeFromCodeCoverage] public static class OpenIddictUIIdentityInfrastructureServicesExtensions { - public static OpenIddictBuilder AddUIIdentityStore( + public static OpenIddictBuilder AddUIIdentityStore( this OpenIddictBuilder builder, Action storeOptionsAction = null - ) + ) where TApplicationUser : IdentityUser, new() { - builder.Services.AddInfrastructureServices(); + builder.Services.AddInfrastructureServices(); builder.Services .AddOpenIddictUIIdentityStore(storeOptionsAction); @@ -24,15 +25,17 @@ public static OpenIddictBuilder AddUIIdentityStore( return builder; } - private static IServiceCollection AddInfrastructureServices( + private static IServiceCollection AddInfrastructureServices( this IServiceCollection services - ) + ) where TApplicationUser : IdentityUser, new() { - services.AddOpenIddictUIIdentityCoreServices(); + // core services + services.AddOpenIddictUIIdentityCoreServices(); + // own services services.AddScoped(typeof(IAsyncRepository<,>), typeof(EfRepository<,>)); - services.AddTransient>(); + services.AddTransient>(); return services; } diff --git a/src/identity/OpenIddict.UI.Identity.Infrastructure/Services/AccountService.cs b/src/identity/OpenIddict.UI.Identity.Infrastructure/Services/AccountService.cs new file mode 100644 index 0000000..362dad6 --- /dev/null +++ b/src/identity/OpenIddict.UI.Identity.Infrastructure/Services/AccountService.cs @@ -0,0 +1,166 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using tomware.OpenIddict.UI.Identity.Core; + +namespace tomware.OpenIddict.UI.Identity.Infrastructure +{ + public class AccountService : IAccountService + where TIdentityUser : IdentityUser, new() + { + private readonly UserManager _manager; + private readonly IUserCreationStrategy _userCreationStrategy; + + public AccountService( + UserManager manager, + IUserCreationStrategy userCreationStrategy + ) + { + _manager = manager + ?? throw new ArgumentNullException(nameof(manager)); + _userCreationStrategy = userCreationStrategy + ?? throw new ArgumentNullException(nameof(userCreationStrategy)); + } + + public async Task RegisterAsync( + RegisterUserParam model + ) + { + var identiyUser = _userCreationStrategy.CreateUser(model); + + return await _manager.CreateAsync(identiyUser, model.Password); + } + + public async Task ChangePasswordAsync(ChangePasswordParam model) + { + var user = await _manager.FindByNameAsync(model.UserName); + + return await _manager.ChangePasswordAsync( + user, + model.CurrentPassword, + model.NewPassword + ); + } + + public async Task> GetUsersAsync() + { + // TODO: Paging ??? + var items = await _manager.Users + .OrderBy(u => u.UserName) + .AsNoTracking() + .ToListAsync(); + + return items.Select(u => new UserInfo + { + Id = u.Id, + UserName = u.UserName, + Email = u.Email, + LockoutEnabled = u.LockoutEnabled + }); + } + + public async Task GetUserAsync(string id) + { + var user = await _manager.FindByIdAsync(id); + var roles = await _manager.GetRolesAsync(user); + var claims = await _manager.GetClaimsAsync(user); + + var isLockedOut = await _manager.IsLockedOutAsync(user); + + return new UserInfo + { + Id = user.Id, + UserName = user.UserName, + Email = user.Email, + LockoutEnabled = user.LockoutEnabled, + IsLockedOut = isLockedOut, + Claims = new List(claims.ToList().Select(x => new ClaimInfo + { + Type = x.Type, + Value = x.Value + })), + Roles = roles.ToList() + }; + } + + public async Task UpdateAsync(UserParam model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + if (string.IsNullOrWhiteSpace(model.Id)) + throw new InvalidOperationException(nameof(model.Id)); + + var user = await _manager.FindByIdAsync(model.Id); + user.UserName = model.UserName; + user.Email = model.Email; + user.LockoutEnabled = model.LockoutEnabled; + + var result = await _manager.UpdateAsync(user); + if (!result.Succeeded) + { + return result; + } + + result = await AssignClaimsAsync( + user, + model.Claims.Select(x => new Claim(x.Type, x.Value)).ToList() + ); + if (!result.Succeeded) + { + return result; + } + + result = await AssignRolesAsync(user, model.Roles); + if (!result.Succeeded) + { + return result; + } + + return result; + } + + public async Task DeleteAsync(string id) + { + if (string.IsNullOrWhiteSpace(id)) throw new InvalidOperationException(nameof(id)); + + var user = await _manager.FindByIdAsync(id); + + return await _manager.DeleteAsync(user); + } + + private async Task AssignClaimsAsync( + TIdentityUser user, + IEnumerable claims + ) + { + // removing all claims + var existingClaims = await _manager.GetClaimsAsync(user); + await _manager.RemoveClaimsAsync(user, existingClaims); + + // assigning claims + return await _manager.AddClaimsAsync( + user, + claims + ); + } + + private async Task AssignRolesAsync( + TIdentityUser user, + IEnumerable roles + ) + { + // removing all roles + var existingRoles = await _manager.GetRolesAsync(user); + await _manager.RemoveFromRolesAsync(user, existingRoles); + + // assigning roles + return await _manager.AddToRolesAsync( + user, + roles + ); + } + } +} \ No newline at end of file diff --git a/tests/Unit/RoleControllerTest.cs b/tests/Unit/RoleControllerTest.cs index ce98b43..90e2961 100644 --- a/tests/Unit/RoleControllerTest.cs +++ b/tests/Unit/RoleControllerTest.cs @@ -26,7 +26,7 @@ public async Task GetAsync_WithNullId_ReturnsBadRequest() public async Task GetAsync_WithNotExistingId_ReturnsNotFound() { // Arrange - var serviceMock = new Mock(); + var serviceMock = new Mock(); var controller = GetController(serviceMock); var id = "id"; @@ -126,9 +126,9 @@ public async Task DeleteAsync_WithNullModel_ReturnsBadRequest() Assert.IsType(result); } - private static RoleController GetController(Mock service = null) + private static RoleController GetController(Mock service = null) { - service ??= new Mock(); + service ??= new Mock(); return new RoleController(service.Object); }