From 8da0108013c4422b7bc277d36796265fc4276da2 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 27 May 2017 12:51:32 +0200 Subject: [PATCH] Dependencies reduced --- .../Users/MongoRoleStore.cs | 93 +++++ .../Users/MongoUserEntity.cs | 50 --- .../Users/MongoUserStore.cs | 323 ++++++++++++++++++ .../Users/WrappedIdentityRole.cs | 17 + .../Users/WrappedIdentityUser.cs | 41 +++ src/Squidex.Read/Squidex.Read.csproj | 2 + src/Squidex.Read/Users/IRole.cs | 15 + src/Squidex.Read/Users/IRoleFactory.cs | 15 + src/Squidex.Read/Users/IUser.cs | 32 ++ src/Squidex.Read/Users/IUserFactory.cs | 15 + .../{IUserEntity.cs => IUserResolver.cs} | 16 +- .../Users/Repositories/IUserRepository.cs | 30 -- src/Squidex.Read/Users/UserExtensions.cs | 42 +++ .../Users/UserManagerExtensions.cs} | 95 ++---- src/Squidex.Write/Apps/AppCommandHandler.cs | 12 +- .../Config/Domain/StoreMongoDbModule.cs | 32 +- .../Config/Identity/IdentityServices.cs | 6 +- src/Squidex/Config/Identity/IdentityUsage.cs | 14 +- .../Api/Apps/AppLanguagesController.cs | 4 +- .../Api/Assets/AssetsController.cs | 8 +- .../Api/Schemas/SchemasController.cs | 8 +- .../Api/Users/UserManagementController.cs | 33 +- .../Controllers/Api/Users/UsersController.cs | 19 +- .../ContentApi/ContentsController.cs | 10 +- .../UI/Account/AccountController.cs | 29 +- .../UI/Account/ExternalProvider.cs | 1 + .../Apps/AppCommandHandlerTests.cs | 13 +- 27 files changed, 724 insertions(+), 251 deletions(-) create mode 100644 src/Squidex.Read.MongoDb/Users/MongoRoleStore.cs delete mode 100644 src/Squidex.Read.MongoDb/Users/MongoUserEntity.cs create mode 100644 src/Squidex.Read.MongoDb/Users/MongoUserStore.cs create mode 100644 src/Squidex.Read.MongoDb/Users/WrappedIdentityRole.cs create mode 100644 src/Squidex.Read.MongoDb/Users/WrappedIdentityUser.cs create mode 100644 src/Squidex.Read/Users/IRole.cs create mode 100644 src/Squidex.Read/Users/IRoleFactory.cs create mode 100644 src/Squidex.Read/Users/IUser.cs create mode 100644 src/Squidex.Read/Users/IUserFactory.cs rename src/Squidex.Read/Users/{IUserEntity.cs => IUserResolver.cs} (63%) delete mode 100644 src/Squidex.Read/Users/Repositories/IUserRepository.cs create mode 100644 src/Squidex.Read/Users/UserExtensions.cs rename src/{Squidex.Read.MongoDb/Users/MongoUserRepository.cs => Squidex.Read/Users/UserManagerExtensions.cs} (53%) diff --git a/src/Squidex.Read.MongoDb/Users/MongoRoleStore.cs b/src/Squidex.Read.MongoDb/Users/MongoRoleStore.cs new file mode 100644 index 000000000..964029eb9 --- /dev/null +++ b/src/Squidex.Read.MongoDb/Users/MongoRoleStore.cs @@ -0,0 +1,93 @@ +// ========================================================================== +// MongoRoleStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.MongoDB; +using MongoDB.Driver; +using Squidex.Read.Users; + +namespace Squidex.Read.MongoDb.Users +{ + public sealed class MongoRoleStore : + IRoleStore, + IRoleFactory + { + private readonly RoleStore innerStore; + + public MongoRoleStore(IMongoDatabase database) + { + var rolesCollection = database.GetCollection("Identity_Roles"); + + IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection); + + innerStore = new RoleStore(rolesCollection); + } + + public void Dispose() + { + innerStore.Dispose(); + } + + public IRole Create(string name) + { + return new WrappedIdentityRole { Name = name }; + } + + public async Task FindByIdAsync(string roleId, CancellationToken cancellationToken) + { + return await innerStore.FindByIdAsync(roleId, cancellationToken); + } + + public async Task FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken) + { + return await innerStore.FindByNameAsync(normalizedRoleName, cancellationToken); + } + + public Task CreateAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.CreateAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task UpdateAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.UpdateAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task DeleteAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.DeleteAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task GetRoleIdAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetRoleIdAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task GetRoleNameAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetRoleNameAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task SetRoleNameAsync(IRole role, string roleName, CancellationToken cancellationToken) + { + return innerStore.SetRoleNameAsync((WrappedIdentityRole)role, roleName, cancellationToken); + } + + public Task GetNormalizedRoleNameAsync(IRole role, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedRoleNameAsync((WrappedIdentityRole)role, cancellationToken); + } + + public Task SetNormalizedRoleNameAsync(IRole role, string normalizedName, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedRoleNameAsync((WrappedIdentityRole)role, normalizedName, cancellationToken); + } + } +} diff --git a/src/Squidex.Read.MongoDb/Users/MongoUserEntity.cs b/src/Squidex.Read.MongoDb/Users/MongoUserEntity.cs deleted file mode 100644 index f5f7336fb..000000000 --- a/src/Squidex.Read.MongoDb/Users/MongoUserEntity.cs +++ /dev/null @@ -1,50 +0,0 @@ -// ========================================================================== -// MongoUserEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Microsoft.AspNetCore.Identity.MongoDB; -using Squidex.Core.Identity; -using Squidex.Read.Users; - -namespace Squidex.Read.MongoDb.Users -{ - public class MongoUserEntity : IUserEntity - { - private readonly IdentityUser inner; - - public string Id - { - get { return inner.Id; } - } - - public string Email - { - get { return inner.Email; } - } - - public string DisplayName - { - get { return inner.Claims.Find(x => x.Type == SquidexClaimTypes.SquidexDisplayName)?.Value; } - } - - public string PictureUrl - { - get { return inner.Claims.Find(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value; } - } - - public bool IsLocked - { - get { return inner.LockoutEndDateUtc != null && inner.LockoutEndDateUtc.Value > DateTime.UtcNow; } - } - - public MongoUserEntity(IdentityUser inner) - { - this.inner = inner; - } - } -} diff --git a/src/Squidex.Read.MongoDb/Users/MongoUserStore.cs b/src/Squidex.Read.MongoDb/Users/MongoUserStore.cs new file mode 100644 index 000000000..b988d8faf --- /dev/null +++ b/src/Squidex.Read.MongoDb/Users/MongoUserStore.cs @@ -0,0 +1,323 @@ +// ========================================================================== +// MongoUserStore.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.MongoDB; +using MongoDB.Driver; +using Squidex.Read.Users; + +namespace Squidex.Read.MongoDb.Users +{ + public sealed class MongoUserStore : + IUserPasswordStore, + IUserRoleStore, + IUserLoginStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserClaimStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserLockoutStore, + IUserAuthenticationTokenStore, + IUserFactory, + IQueryableUserStore + { + private readonly UserStore innerStore; + + public MongoUserStore(IMongoDatabase database) + { + var usersCollection = database.GetCollection("Identity_Users"); + + IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection); + IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection); + + innerStore = new UserStore(usersCollection); + } + + public void Dispose() + { + innerStore.Dispose(); + } + + public IQueryable Users + { + get { return innerStore.Users; } + } + + public IUser Create(string email) + { + return new WrappedIdentityUser { Email = email, UserName = email }; + } + + public async Task FindByIdAsync(string userId, CancellationToken cancellationToken) + { + return await innerStore.FindByIdAsync(userId, cancellationToken); + } + + public async Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken) + { + return await innerStore.FindByEmailAsync(normalizedEmail, cancellationToken); + } + + public async Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken) + { + return await innerStore.FindByNameAsync(normalizedUserName, cancellationToken); + } + + public async Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return await innerStore.FindByLoginAsync(loginProvider, providerKey, cancellationToken); + } + + public async Task> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken) + { + return (await innerStore.GetUsersForClaimAsync(claim, cancellationToken)).OfType().ToList(); + } + + public async Task> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken) + { + return (await innerStore.GetUsersInRoleAsync(roleName, cancellationToken)).OfType().ToList(); + } + + public Task CreateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.CreateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task UpdateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.UpdateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task DeleteAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.DeleteAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetUserIdAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetUserIdAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetUserNameAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetUserNameAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetUserNameAsync(IUser user, string userName, CancellationToken cancellationToken) + { + return innerStore.SetUserNameAsync((WrappedIdentityUser)user, userName, cancellationToken); + } + + public Task GetNormalizedUserNameAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedUserNameAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetNormalizedUserNameAsync(IUser user, string normalizedName, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedUserNameAsync((WrappedIdentityUser)user, normalizedName, cancellationToken); + } + + public Task GetPasswordHashAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPasswordHashAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPasswordHashAsync(IUser user, string passwordHash, CancellationToken cancellationToken) + { + return innerStore.SetPasswordHashAsync((WrappedIdentityUser)user, passwordHash, cancellationToken); + } + + public Task HasPasswordAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.HasPasswordAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.AddToRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task RemoveFromRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.RemoveFromRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task> GetRolesAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetRolesAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task IsInRoleAsync(IUser user, string roleName, CancellationToken cancellationToken) + { + return innerStore.IsInRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken); + } + + public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken) + { + return innerStore.AddLoginAsync((WrappedIdentityUser)user, login, cancellationToken); + } + + public Task RemoveLoginAsync(IUser user, string loginProvider, string providerKey, CancellationToken cancellationToken) + { + return innerStore.RemoveLoginAsync((WrappedIdentityUser)user, loginProvider, providerKey, cancellationToken); + } + + public Task> GetLoginsAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLoginsAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetSecurityStampAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetSecurityStampAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetSecurityStampAsync(IUser user, string stamp, CancellationToken cancellationToken) + { + return innerStore.SetSecurityStampAsync((WrappedIdentityUser)user, stamp, cancellationToken); + } + + public Task GetEmailAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetEmailAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetEmailAsync(IUser user, string email, CancellationToken cancellationToken) + { + return innerStore.SetEmailAsync((WrappedIdentityUser)user, email, cancellationToken); + } + + public Task GetEmailConfirmedAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetEmailConfirmedAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetEmailConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken) + { + return innerStore.SetEmailConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken); + } + + public Task GetNormalizedEmailAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetNormalizedEmailAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetNormalizedEmailAsync(IUser user, string normalizedEmail, CancellationToken cancellationToken) + { + return innerStore.SetNormalizedEmailAsync((WrappedIdentityUser)user, normalizedEmail, cancellationToken); + } + + public Task> GetClaimsAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetClaimsAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task AddClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken) + { + return innerStore.AddClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken); + } + + public Task ReplaceClaimAsync(IUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken) + { + return innerStore.ReplaceClaimAsync((WrappedIdentityUser)user, claim, newClaim, cancellationToken); + } + + public Task RemoveClaimsAsync(IUser user, IEnumerable claims, CancellationToken cancellationToken) + { + return innerStore.RemoveClaimsAsync((WrappedIdentityUser)user, claims, cancellationToken); + } + + public Task GetPhoneNumberAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPhoneNumberAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPhoneNumberAsync(IUser user, string phoneNumber, CancellationToken cancellationToken) + { + return innerStore.SetPhoneNumberAsync((WrappedIdentityUser)user, phoneNumber, cancellationToken); + } + + public Task GetPhoneNumberConfirmedAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetPhoneNumberConfirmedAsync(IUser user, bool confirmed, CancellationToken cancellationToken) + { + return innerStore.SetPhoneNumberConfirmedAsync((WrappedIdentityUser)user, confirmed, cancellationToken); + } + + public Task GetTwoFactorEnabledAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetTwoFactorEnabledAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetTwoFactorEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken) + { + return innerStore.SetTwoFactorEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken); + } + + public Task GetLockoutEndDateAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLockoutEndDateAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetLockoutEndDateAsync(IUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken) + { + return innerStore.SetLockoutEndDateAsync((WrappedIdentityUser)user, lockoutEnd, cancellationToken); + } + + public Task GetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task IncrementAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.IncrementAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task ResetAccessFailedCountAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.ResetAccessFailedCountAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task GetLockoutEnabledAsync(IUser user, CancellationToken cancellationToken) + { + return innerStore.GetLockoutEnabledAsync((WrappedIdentityUser)user, cancellationToken); + } + + public Task SetLockoutEnabledAsync(IUser user, bool enabled, CancellationToken cancellationToken) + { + return innerStore.SetLockoutEnabledAsync((WrappedIdentityUser)user, enabled, cancellationToken); + } + + public Task SetTokenAsync(IUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + return innerStore.SetTokenAsync((WrappedIdentityUser)user, loginProvider, name, value, cancellationToken); + } + + public Task RemoveTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + return innerStore.RemoveTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken); + } + + public Task GetTokenAsync(IUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + return innerStore.GetTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken); + } + } +} diff --git a/src/Squidex.Read.MongoDb/Users/WrappedIdentityRole.cs b/src/Squidex.Read.MongoDb/Users/WrappedIdentityRole.cs new file mode 100644 index 000000000..f040aa3a8 --- /dev/null +++ b/src/Squidex.Read.MongoDb/Users/WrappedIdentityRole.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// WrappedIdentityRole.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Microsoft.AspNetCore.Identity.MongoDB; +using Squidex.Read.Users; + +namespace Squidex.Read.MongoDb.Users +{ + public sealed class WrappedIdentityRole : IdentityRole, IRole + { + } +} diff --git a/src/Squidex.Read.MongoDb/Users/WrappedIdentityUser.cs b/src/Squidex.Read.MongoDb/Users/WrappedIdentityUser.cs new file mode 100644 index 000000000..e1528dc50 --- /dev/null +++ b/src/Squidex.Read.MongoDb/Users/WrappedIdentityUser.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// WrappedIdentityUser.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Identity.MongoDB; +using Squidex.Read.Users; + +namespace Squidex.Read.MongoDb.Users +{ + public sealed class WrappedIdentityUser : IdentityUser, IUser + { + public bool IsLocked + { + get { return LockoutEndDateUtc != null && LockoutEndDateUtc.Value > DateTime.UtcNow; } + } + + IReadOnlyList IUser.Claims + { + get { return Claims.Select(x => new Claim(x.Type, x.Value)).ToList(); } + } + + public void UpdateEmail(string email) + { + Email = UserName = email; + } + + public void SetClaim(string type, string value) + { + Claims.RemoveAll(x => string.Equals(x.Type, type, StringComparison.OrdinalIgnoreCase)); + Claims.Add(new IdentityUserClaim { Type = type, Value = value }); + } + } +} diff --git a/src/Squidex.Read/Squidex.Read.csproj b/src/Squidex.Read/Squidex.Read.csproj index 0efb70fad..447361e27 100644 --- a/src/Squidex.Read/Squidex.Read.csproj +++ b/src/Squidex.Read/Squidex.Read.csproj @@ -13,7 +13,9 @@ + + diff --git a/src/Squidex.Read/Users/IRole.cs b/src/Squidex.Read/Users/IRole.cs new file mode 100644 index 000000000..e96bcb3f6 --- /dev/null +++ b/src/Squidex.Read/Users/IRole.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IRole.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Read.Users +{ + public interface IRole + { + string Name { get; } + } +} diff --git a/src/Squidex.Read/Users/IRoleFactory.cs b/src/Squidex.Read/Users/IRoleFactory.cs new file mode 100644 index 000000000..ed41209d5 --- /dev/null +++ b/src/Squidex.Read/Users/IRoleFactory.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IRoleFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Read.Users +{ + public interface IRoleFactory + { + IRole Create(string name); + } +} diff --git a/src/Squidex.Read/Users/IUser.cs b/src/Squidex.Read/Users/IUser.cs new file mode 100644 index 000000000..810590ded --- /dev/null +++ b/src/Squidex.Read/Users/IUser.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// IUser.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Security.Claims; + +namespace Squidex.Read.Users +{ + public interface IUser + { + bool IsLocked { get; } + + string Id { get; } + + string Email { get; } + + string NormalizedEmail { get; } + + IReadOnlyList Claims { get; } + + void UpdateEmail(string email); + + void AddClaim(Claim claim); + + void SetClaim(string type, string value); + } +} diff --git a/src/Squidex.Read/Users/IUserFactory.cs b/src/Squidex.Read/Users/IUserFactory.cs new file mode 100644 index 000000000..c90c6a924 --- /dev/null +++ b/src/Squidex.Read/Users/IUserFactory.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IUserFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Read.Users +{ + public interface IUserFactory + { + IUser Create(string email); + } +} diff --git a/src/Squidex.Read/Users/IUserEntity.cs b/src/Squidex.Read/Users/IUserResolver.cs similarity index 63% rename from src/Squidex.Read/Users/IUserEntity.cs rename to src/Squidex.Read/Users/IUserResolver.cs index 50cb41ada..2fb226013 100644 --- a/src/Squidex.Read/Users/IUserEntity.cs +++ b/src/Squidex.Read/Users/IUserResolver.cs @@ -1,23 +1,17 @@ // ========================================================================== -// IUserEntity.cs +// IUserResolver.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== +using System.Threading.Tasks; + namespace Squidex.Read.Users { - public interface IUserEntity + public interface IUserResolver { - string Id { get; } - - string Email { get; } - - string PictureUrl { get; } - - string DisplayName { get; } - - bool IsLocked { get; } + Task FindById(string id); } } diff --git a/src/Squidex.Read/Users/Repositories/IUserRepository.cs b/src/Squidex.Read/Users/Repositories/IUserRepository.cs deleted file mode 100644 index e1c2b9386..000000000 --- a/src/Squidex.Read/Users/Repositories/IUserRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -// ========================================================================== -// IUserRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Squidex.Read.Users.Repositories -{ - public interface IUserRepository - { - Task> QueryByEmailAsync(string email = null, int take = 10, int skip = 0); - - Task FindUserByIdAsync(string id); - - Task CreateAsync(string email, string displayName, string password); - - Task UpdateAsync(string id, string email, string displayName, string password); - - Task LockAsync(string id); - - Task UnlockAsync(string id); - - Task CountAsync(string email = null); - } -} diff --git a/src/Squidex.Read/Users/UserExtensions.cs b/src/Squidex.Read/Users/UserExtensions.cs new file mode 100644 index 000000000..8a85567bd --- /dev/null +++ b/src/Squidex.Read/Users/UserExtensions.cs @@ -0,0 +1,42 @@ +// ========================================================================== +// UserExtensions.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using Squidex.Core.Identity; +using Squidex.Infrastructure; + +namespace Squidex.Read.Users +{ + public static class UserExtensions + { + public static void SetDisplayName(this IUser user, string displayName) + { + user.SetClaim(SquidexClaimTypes.SquidexDisplayName, displayName); + } + + public static void SetPictureUrl(this IUser user, string pictureUrl) + { + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl); + } + + public static void SetPictureUrlFromGravatar(this IUser user, string email) + { + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email)); + } + + public static string PictureUrl(this IUser user) + { + return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)?.Value; + } + + public static string DisplayName(this IUser user) + { + return user.Claims.FirstOrDefault(x => x.Type == SquidexClaimTypes.SquidexDisplayName)?.Value; + } + } +} diff --git a/src/Squidex.Read.MongoDb/Users/MongoUserRepository.cs b/src/Squidex.Read/Users/UserManagerExtensions.cs similarity index 53% rename from src/Squidex.Read.MongoDb/Users/MongoUserRepository.cs rename to src/Squidex.Read/Users/UserManagerExtensions.cs index 8f98a6a41..1a1b724e3 100644 --- a/src/Squidex.Read.MongoDb/Users/MongoUserRepository.cs +++ b/src/Squidex.Read/Users/UserManagerExtensions.cs @@ -1,5 +1,5 @@ // ========================================================================== -// MongoUserRepository.cs +// UserManagerExtensions.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,66 +9,53 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.MongoDB; using Squidex.Core.Identity; using Squidex.Infrastructure; -using Squidex.Read.Users; -using Squidex.Read.Users.Repositories; // ReSharper disable ImplicitlyCapturedClosure // ReSharper disable InvertIf +// ReSharper disable ReturnTypeCanBeEnumerable.Local -namespace Squidex.Read.MongoDb.Users +namespace Squidex.Read.Users { - public sealed class MongoUserRepository : IUserRepository + public static class UserManagerExtensions { - private readonly UserManager userManager; - - public MongoUserRepository(UserManager userManager) + public static Task> QueryByEmailAsync(this UserManager userManager, string email = null, int take = 10, int skip = 0) { - Guard.NotNull(userManager, nameof(userManager)); - - this.userManager = userManager; - } + var users = QueryUsers(userManager, email).Skip(skip).Take(take).ToList(); - public Task> QueryByEmailAsync(string email, int take = 10, int skip = 0) - { - var users = QueryUsers(email).Skip(skip).Take(take).ToList(); - - return Task.FromResult>(users.Select(x => (IUserEntity)new MongoUserEntity(x)).ToList()); + return Task.FromResult>(users); } - public Task CountAsync(string email = null) + public static Task CountAsync(this UserManager userManager, string email = null) { - var count = QueryUsers(email).LongCount(); + var count = QueryUsers(userManager, email).LongCount(); return Task.FromResult(count); } - public async Task FindUserByIdAsync(string id) + private static IQueryable QueryUsers(UserManager userManager, string email = null) { - var user = await userManager.FindByIdAsync(id); + var result = userManager.Users; - return user != null ? new MongoUserEntity(user) : null; + if (!string.IsNullOrWhiteSpace(email)) + { + var upperEmail = email.ToUpperInvariant(); + + result = result.Where(x => x.NormalizedEmail.Contains(upperEmail)); + } + + return result; } - public async Task CreateAsync(string email, string displayName, string password) + public static async Task CreateAsync(this UserManager userManager, IUserFactory factory, string email, string displayName, string password) { - var pictureUrl = GravatarHelper.CreatePictureUrl(email); + var user = factory.Create(email); - var user = new IdentityUser - { - Email = email, - Claims = new List - { - new IdentityUserClaim(new Claim(SquidexClaimTypes.SquidexPictureUrl, pictureUrl)), - new IdentityUserClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, displayName)) - }, - UserName = email - }; + user.SetDisplayName(displayName); + user.SetPictureUrlFromGravatar(email); await DoChecked(() => userManager.CreateAsync(user), "Cannot create user."); @@ -80,57 +67,53 @@ namespace Squidex.Read.MongoDb.Users return user.Id; } - public async Task UpdateAsync(string id, string email, string displayName, string password) + public static async Task UpdateAsync(this UserManager userManager, string id, string email, string displayName, string password) { var user = await userManager.FindByIdAsync(id); if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id, typeof(IUser)); } if (!string.IsNullOrWhiteSpace(email)) { - user.Email = user.UserName = email; + user.UpdateEmail(email); } if (!string.IsNullOrWhiteSpace(displayName)) { - user.Claims.Find(x => x.Type == SquidexClaimTypes.SquidexDisplayName).Value = displayName; - } - - if (!string.IsNullOrWhiteSpace(password)) - { - user.PasswordHash = null; + user.SetClaim(SquidexClaimTypes.SquidexDisplayName, displayName); } await DoChecked(() => userManager.UpdateAsync(user), "Cannot update user."); if (!string.IsNullOrWhiteSpace(password)) { + await DoChecked(() => userManager.RemovePasswordAsync(user), "Cannot update user."); await DoChecked(() => userManager.AddPasswordAsync(user, password), "Cannot update user."); } } - public async Task LockAsync(string id) + public static async Task LockAsync(this UserManager userManager, string id) { var user = await userManager.FindByIdAsync(id); if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id, typeof(IUser)); } await DoChecked(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100)), "Cannot lock user."); } - public async Task UnlockAsync(string id) + public static async Task UnlockAsync(this UserManager userManager, string id) { var user = await userManager.FindByIdAsync(id); if (user == null) { - throw new DomainObjectNotFoundException(id, typeof(IdentityUser)); + throw new DomainObjectNotFoundException(id, typeof(IUser)); } await DoChecked(() => userManager.SetLockoutEndDateAsync(user, null), "Cannot unlock user."); @@ -145,19 +128,5 @@ namespace Squidex.Read.MongoDb.Users throw new ValidationException(message, result.Errors.Select(x => new ValidationError(x.Description)).ToArray()); } } - - private IQueryable QueryUsers(string email = null) - { - var result = userManager.Users; - - if (!string.IsNullOrWhiteSpace(email)) - { - var upperEmail = email.ToUpperInvariant(); - - result = userManager.Users.Where(x => x.NormalizedEmail.Contains(upperEmail)); - } - - return result; - } } } diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs index d6ea0c2aa..d4f2cf4f2 100644 --- a/src/Squidex.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Write/Apps/AppCommandHandler.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Tasks; using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Services; -using Squidex.Read.Users.Repositories; +using Squidex.Read.Users; using Squidex.Write.Apps.Commands; // ReSharper disable InvertIf @@ -25,25 +25,25 @@ namespace Squidex.Write.Apps private readonly IAggregateHandler handler; private readonly IAppRepository appRepository; private readonly IAppLimitsProvider appLimitsProvider; - private readonly IUserRepository userRepository; + private readonly IUserResolver userResolver; private readonly ClientKeyGenerator keyGenerator; public AppCommandHandler( IAggregateHandler handler, IAppRepository appRepository, IAppLimitsProvider appLimitsProvider, - IUserRepository userRepository, + IUserResolver userResolver, ClientKeyGenerator keyGenerator) { Guard.NotNull(handler, nameof(handler)); Guard.NotNull(keyGenerator, nameof(keyGenerator)); Guard.NotNull(appRepository, nameof(appRepository)); - Guard.NotNull(userRepository, nameof(userRepository)); + Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(appLimitsProvider, nameof(appLimitsProvider)); this.handler = handler; this.keyGenerator = keyGenerator; - this.userRepository = userRepository; + this.userResolver = userResolver; this.appRepository = appRepository; this.appLimitsProvider = appLimitsProvider; } @@ -69,7 +69,7 @@ namespace Squidex.Write.Apps protected async Task On(AssignContributor command, CommandContext context) { - if (await userRepository.FindUserByIdAsync(command.ContributorId) == null) + if (await userResolver.FindById(command.ContributorId) == null) { var error = new ValidationError("Cannot find contributor the contributor", diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs index de5f51eef..e0fe38301 100644 --- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs +++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs @@ -10,7 +10,6 @@ using Autofac; using Autofac.Core; using IdentityServer4.Stores; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.Extensions.Configuration; using MongoDB.Driver; using Squidex.Core.Schemas; @@ -33,7 +32,7 @@ using Squidex.Read.MongoDb.Schemas; using Squidex.Read.MongoDb.Users; using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Services.Implementations; -using Squidex.Read.Users.Repositories; +using Squidex.Read.Users; namespace Squidex.Config.Domain { @@ -85,31 +84,18 @@ namespace Squidex.Config.Domain .Named(MongoContentDatabaseRegistration) .SingleInstance(); - builder.Register>(c => - { - var usersCollection = c.ResolveNamed(MongoDatabaseRegistration).GetCollection("Identity_Users"); - - IndexChecks.EnsureUniqueIndexOnNormalizedEmail(usersCollection); - IndexChecks.EnsureUniqueIndexOnNormalizedUserName(usersCollection); - - return new UserStore(usersCollection); - }) + builder.RegisterType() + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) + .As>() + .As() .SingleInstance(); - builder.Register>(c => - { - var rolesCollection = c.ResolveNamed(MongoDatabaseRegistration).GetCollection("Identity_Roles"); - - IndexChecks.EnsureUniqueIndexOnNormalizedRoleName(rolesCollection); - - return new RoleStore(rolesCollection); - }) + builder.RegisterType() + .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) + .As>() + .As() .SingleInstance(); - builder.RegisterType() - .As() - .InstancePerLifetimeScope(); - builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) .As() diff --git a/src/Squidex/Config/Identity/IdentityServices.cs b/src/Squidex/Config/Identity/IdentityServices.cs index 1295ddde0..44f82b750 100644 --- a/src/Squidex/Config/Identity/IdentityServices.cs +++ b/src/Squidex/Config/Identity/IdentityServices.cs @@ -16,11 +16,11 @@ using IdentityModel; using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Core.Identity; using Squidex.Infrastructure; +using Squidex.Read.Users; using StackExchange.Redis; namespace Squidex.Config.Identity @@ -100,7 +100,7 @@ namespace Squidex.Config.Identity { options.UserInteraction.ErrorUrl = "/account/error/"; }) - .AddAspNetIdentity() + .AddAspNetIdentity() .AddInMemoryApiResources(GetApiResources()) .AddInMemoryIdentityResources(GetIdentityResources()) .AddSigningCredential(certificate); @@ -110,7 +110,7 @@ namespace Squidex.Config.Identity public static IServiceCollection AddMyIdentity(this IServiceCollection services) { - services.AddIdentity().AddDefaultTokenProviders(); + services.AddIdentity().AddDefaultTokenProviders(); return services; } diff --git a/src/Squidex/Config/Identity/IdentityUsage.cs b/src/Squidex/Config/Identity/IdentityUsage.cs index 0819f9355..54947b8ee 100644 --- a/src/Squidex/Config/Identity/IdentityUsage.cs +++ b/src/Squidex/Config/Identity/IdentityUsage.cs @@ -10,10 +10,10 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Squidex.Core.Identity; +using Squidex.Read.Users; // ReSharper disable InvertIf @@ -37,9 +37,10 @@ namespace Squidex.Config.Identity public static IApplicationBuilder UseAdminRole(this IApplicationBuilder app) { - var roleManager = app.ApplicationServices.GetRequiredService>(); + var roleManager = app.ApplicationServices.GetRequiredService>(); + var roleFactory = app.ApplicationServices.GetRequiredService(); - roleManager.CreateAsync(new IdentityRole { Name = SquidexRoles.Administrator, NormalizedName = SquidexRoles.Administrator }).Wait(); + roleManager.CreateAsync(roleFactory.Create(SquidexRoles.Administrator)).Wait(); return app; } @@ -48,7 +49,8 @@ namespace Squidex.Config.Identity { var options = app.ApplicationServices.GetService>().Value; - var userManager = app.ApplicationServices.GetService>(); + var userManager = app.ApplicationServices.GetService>(); + var userFactory = app.ApplicationServices.GetService(); if (options.IsAdminConfigured()) { @@ -59,7 +61,7 @@ namespace Squidex.Config.Identity { var user = await userManager.FindByEmailAsync(adminPass); - async Task userInitAsync(IdentityUser theUser) + async Task userInitAsync(IUser theUser) { await userManager.RemovePasswordAsync(theUser); await userManager.ChangePasswordAsync(theUser, null, adminEmail); @@ -75,7 +77,7 @@ namespace Squidex.Config.Identity } else if ((userManager.SupportsQueryableUsers && !userManager.Users.Any()) || options.EnforceAdmin) { - user = new IdentityUser { UserName = adminEmail, Email = adminEmail, EmailConfirmed = true }; + user = userFactory.Create(adminEmail); await userManager.CreateAsync(user); await userInitAsync(user); diff --git a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs index 0f8b85314..f1e0b0d7a 100644 --- a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs @@ -50,7 +50,7 @@ namespace Squidex.Controllers.Api.Apps [ProducesResponseType(typeof(LanguageDto[]), 200)] public IActionResult GetLanguages(string app) { - var model = App.LanguagesConfig.OfType().Select(x => + var response = App.LanguagesConfig.OfType().Select(x => SimpleMapper.Map(x.Language, new AppLanguageDto { @@ -61,7 +61,7 @@ namespace Squidex.Controllers.Api.Apps Response.Headers["ETag"] = new StringValues(App.Version.ToString()); - return Ok(model); + return Ok(response); } /// diff --git a/src/Squidex/Controllers/Api/Assets/AssetsController.cs b/src/Squidex/Controllers/Api/Assets/AssetsController.cs index d800c9350..4caf582ad 100644 --- a/src/Squidex/Controllers/Api/Assets/AssetsController.cs +++ b/src/Squidex/Controllers/Api/Assets/AssetsController.cs @@ -102,13 +102,13 @@ namespace Squidex.Controllers.Api.Assets await Task.WhenAll(taskForAssets, taskForCount); - var model = new AssetsDto + var response = new AssetsDto { Total = taskForCount.Result, Items = taskForAssets.Result.Select(x => SimpleMapper.Map(x, new AssetDto())).ToArray() }; - return Ok(model); + return Ok(response); } /// @@ -133,11 +133,11 @@ namespace Squidex.Controllers.Api.Assets return NotFound(); } - var model = SimpleMapper.Map(entity, new AssetDto()); + var response = SimpleMapper.Map(entity, new AssetDto()); Response.Headers["ETag"] = new StringValues(entity.Version.ToString()); - return Ok(model); + return Ok(response); } /// diff --git a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs index 504a17770..7229bd15c 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs @@ -55,9 +55,9 @@ namespace Squidex.Controllers.Api.Schemas { var schemas = await schemaRepository.QueryAllAsync(AppId); - var model = schemas.Select(s => s.ToModel()).ToList(); + var response = schemas.Select(s => s.ToModel()).ToList(); - return Ok(model); + return Ok(response); } /// @@ -83,11 +83,11 @@ namespace Squidex.Controllers.Api.Schemas return NotFound(); } - var model = entity.ToDetailsModel(); + var response = entity.ToDetailsModel(); Response.Headers["ETag"] = new StringValues(entity.Version.ToString()); - return Ok(model); + return Ok(response); } /// diff --git a/src/Squidex/Controllers/Api/Users/UserManagementController.cs b/src/Squidex/Controllers/Api/Users/UserManagementController.cs index b4fa920d0..8829f7bf4 100644 --- a/src/Squidex/Controllers/Api/Users/UserManagementController.cs +++ b/src/Squidex/Controllers/Api/Users/UserManagementController.cs @@ -9,6 +9,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Controllers.Api.Users.Models; @@ -16,7 +17,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; using Squidex.Pipeline; -using Squidex.Read.Users.Repositories; +using Squidex.Read.Users; namespace Squidex.Controllers.Api.Users { @@ -25,11 +26,13 @@ namespace Squidex.Controllers.Api.Users [SwaggerIgnore] public class UserManagementController : Controller { - private readonly IUserRepository userRepository; + private readonly UserManager userManager; + private readonly IUserFactory userFactory; - public UserManagementController(IUserRepository userRepository) + public UserManagementController(UserManager userManager, IUserFactory userFactory) { - this.userRepository = userRepository; + this.userManager = userManager; + this.userFactory = userFactory; } [HttpGet] @@ -37,18 +40,18 @@ namespace Squidex.Controllers.Api.Users [ApiCosts(0)] public async Task GetUsers([FromQuery] string query = null, [FromQuery] int skip = 0, [FromQuery] int take = 10) { - var taskForUsers = userRepository.QueryByEmailAsync(query, take, skip); - var taskForCount = userRepository.CountAsync(query); + var taskForUsers = userManager.QueryByEmailAsync(query, take, skip); + var taskForCount = userManager.CountAsync(query); await Task.WhenAll(taskForUsers, taskForCount); - var model = new UsersDto + var response = new UsersDto { Total = taskForCount.Result, - Items = taskForUsers.Result.Select(x => SimpleMapper.Map(x, new UserDto())).ToArray() + Items = taskForUsers.Result.Select(x => SimpleMapper.Map(x, new UserDto { DisplayName = x.DisplayName(), PictureUrl = x.PictureUrl() })).ToArray() }; - return Ok(model); + return Ok(response); } [HttpPost] @@ -56,11 +59,11 @@ namespace Squidex.Controllers.Api.Users [ApiCosts(0)] public async Task Create([FromBody] CreateUserDto request) { - var id = await userRepository.CreateAsync(request.Email, request.DisplayName, request.Password); + var id = await userManager.CreateAsync(userFactory, request.Email, request.DisplayName, request.Password); - var model = new EntityCreatedDto { Id = id }; + var response = new EntityCreatedDto { Id = id }; - return Ok(model); + return Ok(response); } [HttpPut] @@ -68,7 +71,7 @@ namespace Squidex.Controllers.Api.Users [ApiCosts(0)] public async Task Update(string id, [FromBody] UpdateUserDto request) { - await userRepository.UpdateAsync(id, request.Email, request.DisplayName, request.Password); + await userManager.UpdateAsync(id, request.Email, request.DisplayName, request.Password); return NoContent(); } @@ -83,7 +86,7 @@ namespace Squidex.Controllers.Api.Users throw new ValidationException("Locking user failed.", new ValidationError("You cannot lock yourself.")); } - await userRepository.LockAsync(id); + await userManager.LockAsync(id); return NoContent(); } @@ -98,7 +101,7 @@ namespace Squidex.Controllers.Api.Users throw new ValidationException("Unlocking user failed.", new ValidationError("You cannot unlock yourself.")); } - await userRepository.UnlockAsync(id); + await userManager.UnlockAsync(id); return NoContent(); } diff --git a/src/Squidex/Controllers/Api/Users/UsersController.cs b/src/Squidex/Controllers/Api/Users/UsersController.cs index 0159a51df..81813ce30 100644 --- a/src/Squidex/Controllers/Api/Users/UsersController.cs +++ b/src/Squidex/Controllers/Api/Users/UsersController.cs @@ -9,12 +9,13 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Controllers.Api.Users.Models; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; -using Squidex.Read.Users.Repositories; +using Squidex.Read.Users; namespace Squidex.Controllers.Api.Users { @@ -26,11 +27,11 @@ namespace Squidex.Controllers.Api.Users [SwaggerTag("Users")] public class UsersController : Controller { - private readonly IUserRepository userRepository; + private readonly UserManager userManager; - public UsersController(IUserRepository userRepository) + public UsersController(UserManager userManager) { - this.userRepository = userRepository; + this.userManager = userManager; } /// @@ -48,11 +49,11 @@ namespace Squidex.Controllers.Api.Users [ProducesResponseType(typeof(UserDto[]), 200)] public async Task GetUsers(string query) { - var entities = await userRepository.QueryByEmailAsync(query ?? string.Empty); + var entities = await userManager.QueryByEmailAsync(query ?? string.Empty); - var response = entities.Select(x => SimpleMapper.Map(x, new UserDto())).ToList(); + var models = entities.Select(x => SimpleMapper.Map(x, new UserDto { DisplayName = x.DisplayName(), PictureUrl = x.PictureUrl() })).ToArray(); - return Ok(response); + return Ok(models); } /// @@ -68,14 +69,14 @@ namespace Squidex.Controllers.Api.Users [ProducesResponseType(typeof(UserDto), 200)] public async Task GetUser(string id) { - var entity = await userRepository.FindUserByIdAsync(id); + var entity = await userManager.FindByIdAsync(id); if (entity == null) { return NotFound(); } - var response = SimpleMapper.Map(entity, new UserDto()); + var response = SimpleMapper.Map(entity, new UserDto { DisplayName = entity.DisplayName(), PictureUrl = entity.PictureUrl() }); return Ok(response); } diff --git a/src/Squidex/Controllers/ContentApi/ContentsController.cs b/src/Squidex/Controllers/ContentApi/ContentsController.cs index b960ae10c..b639bbeca 100644 --- a/src/Squidex/Controllers/ContentApi/ContentsController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentsController.cs @@ -59,7 +59,7 @@ namespace Squidex.Controllers.ContentApi await Task.WhenAll(taskForContents, taskForCount); - var model = new AssetsDto + var response = new AssetsDto { Total = taskForCount.Result, Items = taskForContents.Result.Take(200).Select(x => @@ -75,7 +75,7 @@ namespace Squidex.Controllers.ContentApi }).ToArray() }; - return Ok(model); + return Ok(response); } [HttpGet] @@ -97,16 +97,16 @@ namespace Squidex.Controllers.ContentApi return NotFound(); } - var model = SimpleMapper.Map(entity, new ContentDto()); + var resposne = SimpleMapper.Map(entity, new ContentDto()); if (entity.Data != null) { - model.Data = entity.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, hidden); + resposne.Data = entity.Data.ToApiModel(schemaEntity.Schema, App.LanguagesConfig, null, hidden); } Response.Headers["ETag"] = new StringValues(entity.Version.ToString()); - return Ok(model); + return Ok(resposne); } [HttpPost] diff --git a/src/Squidex/Controllers/UI/Account/AccountController.cs b/src/Squidex/Controllers/UI/Account/AccountController.cs index a2fad3b24..26a20390a 100644 --- a/src/Squidex/Controllers/UI/Account/AccountController.cs +++ b/src/Squidex/Controllers/UI/Account/AccountController.cs @@ -14,7 +14,6 @@ using System.Text; using System.Threading.Tasks; using IdentityServer4.Services; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Microsoft.Extensions.Options; @@ -24,6 +23,7 @@ using Squidex.Core.Identity; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Tasks; +using Squidex.Read.Users; // ReSharper disable InvertIf // ReSharper disable RedundantIfElseBlock @@ -34,16 +34,18 @@ namespace Squidex.Controllers.UI.Account [SwaggerIgnore] public sealed class AccountController : Controller { - private readonly SignInManager signInManager; - private readonly UserManager userManager; + private readonly SignInManager signInManager; + private readonly UserManager userManager; + private readonly IUserFactory userFactory; private readonly IOptions identityOptions; private readonly IOptions urlOptions; private readonly ISemanticLog log; private readonly IIdentityServerInteractionService interactions; public AccountController( - SignInManager signInManager, - UserManager userManager, + SignInManager signInManager, + UserManager userManager, + IUserFactory userFactory, IOptions identityOptions, IOptions urlOptions, ISemanticLog log, @@ -52,6 +54,7 @@ namespace Squidex.Controllers.UI.Account this.log = log; this.urlOptions = urlOptions; this.userManager = userManager; + this.userFactory = userFactory; this.interactions = interactions; this.identityOptions = identityOptions; this.signInManager = signInManager; @@ -258,12 +261,12 @@ namespace Squidex.Controllers.UI.Account } } - private Task AddLoginAsync(IdentityUser user, UserLoginInfo externalLogin) + private Task AddLoginAsync(IUser user, UserLoginInfo externalLogin) { return MakeIdentityOperation(() => userManager.AddLoginAsync(user, externalLogin)); } - private Task AddUserAsync(IdentityUser user) + private Task AddUserAsync(IUser user) { return MakeIdentityOperation(() => userManager.CreateAsync(user)); } @@ -275,7 +278,7 @@ namespace Squidex.Controllers.UI.Account return result.Succeeded; } - private Task LockAsync(IdentityUser user, bool isFirst) + private Task LockAsync(IUser user, bool isFirst) { if (isFirst || !identityOptions.Value.LockAutomatically) { @@ -285,7 +288,7 @@ namespace Squidex.Controllers.UI.Account return MakeIdentityOperation(() => userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.AddYears(100))); } - private Task MakeAdminAsync(IdentityUser user, bool isFirst) + private Task MakeAdminAsync(IUser user, bool isFirst) { if (!isFirst) { @@ -295,18 +298,18 @@ namespace Squidex.Controllers.UI.Account return MakeIdentityOperation(() => userManager.AddToRoleAsync(user, SquidexRoles.Administrator)); } - private static IdentityUser CreateUser(ExternalLoginInfo externalLogin, string email) + private IUser CreateUser(ExternalLoginInfo externalLogin, string email) { - var user = new IdentityUser { Email = email, UserName = email }; + var user = userFactory.Create(email); if (!externalLogin.Principal.HasClaim(x => x.Type == SquidexClaimTypes.SquidexPictureUrl)) { - user.AddClaim(new Claim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email))); + user.SetClaim(SquidexClaimTypes.SquidexPictureUrl, GravatarHelper.CreatePictureUrl(email)); } if (!externalLogin.Principal.HasClaim(x => x.Type == SquidexClaimTypes.SquidexDisplayName)) { - user.AddClaim(new Claim(SquidexClaimTypes.SquidexDisplayName, email)); + user.SetClaim(SquidexClaimTypes.SquidexDisplayName, email); } foreach (var squidexClaim in externalLogin.Principal.Claims.Where(c => c.Type.StartsWith(SquidexClaimTypes.Prefix))) diff --git a/src/Squidex/Controllers/UI/Account/ExternalProvider.cs b/src/Squidex/Controllers/UI/Account/ExternalProvider.cs index 2bbd37922..f38e1f436 100644 --- a/src/Squidex/Controllers/UI/Account/ExternalProvider.cs +++ b/src/Squidex/Controllers/UI/Account/ExternalProvider.cs @@ -5,6 +5,7 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== + namespace Squidex.Controllers.UI.Account { public class ExternalProvider diff --git a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs index aaee8877e..b9848a0f3 100644 --- a/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs @@ -17,7 +17,6 @@ using Squidex.Read.Apps.Repositories; using Squidex.Read.Apps.Services; using Squidex.Read.Apps.Services.Implementations; using Squidex.Read.Users; -using Squidex.Read.Users.Repositories; using Squidex.Write.Apps.Commands; using Squidex.Write.TestHelpers; using Xunit; @@ -32,7 +31,7 @@ namespace Squidex.Write.Apps private readonly Mock keyGenerator = new Mock(); private readonly Mock appRepository = new Mock(); private readonly Mock appLimitsProvider = new Mock(); - private readonly Mock userRepository = new Mock(); + private readonly Mock userResolver = new Mock(); private readonly AppCommandHandler sut; private readonly AppDomainObject app; private readonly Language language = Language.DE; @@ -44,7 +43,7 @@ namespace Squidex.Write.Apps { app = new AppDomainObject(AppId, -1); - sut = new AppCommandHandler(Handler, appRepository.Object, appLimitsProvider.Object, userRepository.Object, keyGenerator.Object); + sut = new AppCommandHandler(Handler, appRepository.Object, appLimitsProvider.Object, userResolver.Object, keyGenerator.Object); } [Fact] @@ -88,7 +87,7 @@ namespace Squidex.Write.Apps var context = CreateContextForCommand(new AssignContributor { ContributorId = contributorId }); - userRepository.Setup(x => x.FindUserByIdAsync(contributorId)).Returns(Task.FromResult(null)); + userResolver.Setup(x => x.FindById(contributorId)).Returns(Task.FromResult(null)); await TestUpdate(app, async _ => { @@ -107,7 +106,7 @@ namespace Squidex.Write.Apps var context = CreateContextForCommand(new AssignContributor { ContributorId = contributorId }); - userRepository.Setup(x => x.FindUserByIdAsync(It.IsAny())).Returns(Task.FromResult(new Mock().Object)); + userResolver.Setup(x => x.FindById(It.IsAny())).Returns(Task.FromResult(new Mock().Object)); await TestUpdate(app, async _ => { @@ -122,7 +121,7 @@ namespace Squidex.Write.Apps var context = CreateContextForCommand(new AssignContributor { ContributorId = contributorId }); - userRepository.Setup(x => x.FindUserByIdAsync(contributorId)).Returns(Task.FromResult(null)); + userResolver.Setup(x => x.FindById(contributorId)).Returns(Task.FromResult(null)); await TestUpdate(app, async _ => { @@ -139,7 +138,7 @@ namespace Squidex.Write.Apps var context = CreateContextForCommand(new AssignContributor { ContributorId = contributorId }); - userRepository.Setup(x => x.FindUserByIdAsync(contributorId)).Returns(Task.FromResult(new Mock().Object)); + userResolver.Setup(x => x.FindById(contributorId)).Returns(Task.FromResult(new Mock().Object)); await TestUpdate(app, async _ => {