diff --git a/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskEntityFrameworkCoreModule.cs b/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskEntityFrameworkCoreModule.cs index 2ee6335c37..3538a2186e 100644 --- a/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskEntityFrameworkCoreModule.cs +++ b/src/AbpDesk/AbpDesk.EntityFrameworkCore/AbpDesk/EntityFrameworkCore/AbpDeskEntityFrameworkCoreModule.cs @@ -11,7 +11,7 @@ namespace AbpDesk.EntityFrameworkCore { services.AddAbpDbContext(options => { - options.AddDefaultRepositories(); + options.WithDefaultRepositories(); }); services.AddAssemblyOf(); diff --git a/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs b/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs index c1eaf32744..0d3d78cdd9 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreServiceCollectionExtensions.cs @@ -24,7 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection var options = new AddAbpDbContextOptions(); optionsAction?.Invoke(options); - services.AddRepositories(options.Repositories); + services.AddRepositories(options.RepositoryOptions); return services; } diff --git a/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AddAbpDbContextOptions.cs b/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AddAbpDbContextOptions.cs index 7abf1d50f7..f0ded835df 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AddAbpDbContextOptions.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AddAbpDbContextOptions.cs @@ -8,11 +8,11 @@ namespace Microsoft.Extensions.DependencyInjection { public class AddAbpDbContextOptions { - internal RepositoryRegistrationOptions Repositories { get; set; } + internal RepositoryRegistrationOptions RepositoryOptions { get; set; } public AddAbpDbContextOptions() { - Repositories = new RepositoryRegistrationOptions(); + RepositoryOptions = new RepositoryRegistrationOptions(); } /// @@ -22,18 +22,24 @@ namespace Microsoft.Extensions.DependencyInjection /// Registers repositories only for aggregate root entities by default. /// set to true to include all entities. /// - public void AddDefaultRepositories(bool includeAllEntities = false) + public void WithDefaultRepositories(bool includeAllEntities = false) { - Repositories.RegisterDefaultRepositories = true; - Repositories.IncludeAllEntitiesForDefaultRepositories = includeAllEntities; + RepositoryOptions.RegisterDefaultRepositories = true; + RepositoryOptions.IncludeAllEntitiesForDefaultRepositories = includeAllEntities; } - public void AddCustomRepository() + /// + /// Registers custom repository for a specific entity. + /// Custom repositories overrides default repositories. + /// + /// Entity type + /// Repository type + public void WithCustomRepository() { - AddCustomRepository(typeof(TEntity), typeof(TRepository)); + WithCustomRepository(typeof(TEntity), typeof(TRepository)); } - private void AddCustomRepository(Type entityType, Type repositoryType) + private void WithCustomRepository(Type entityType, Type repositoryType) { if (!ReflectionHelper.IsAssignableToGenericType(entityType, typeof(IEntity<>))) { @@ -45,7 +51,7 @@ namespace Microsoft.Extensions.DependencyInjection throw new AbpException($"Given repositoryType is not a repository: {entityType.AssemblyQualifiedName}. It must implement {typeof(IRepository<,>).AssemblyQualifiedName}."); } - Repositories.CustomRepositories[entityType] = repositoryType; + RepositoryOptions.CustomRepositories[entityType] = repositoryType; } } } \ No newline at end of file diff --git a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityRoleRepository.cs b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityRoleRepository.cs index a127887687..3f9c322f81 100644 --- a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityRoleRepository.cs +++ b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityRoleRepository.cs @@ -7,7 +7,6 @@ using Volo.Abp.Identity.EntityFrameworkCore; namespace Volo.Abp.Identity { - //TODO: Register for all repositories! public class EfCoreIdentityRoleRepository : EfCoreRepository, IIdentityRoleRepository { public EfCoreIdentityRoleRepository(IDbContextProvider dbContextProvider) diff --git a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs index 22554181c1..2fad6cd8c2 100644 --- a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs +++ b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EfCoreIdentityUserRepository.cs @@ -10,12 +10,6 @@ using Volo.Abp.Identity.EntityFrameworkCore; namespace Volo.Abp.Identity { - //TODO: Register for; - //- IIdentityUserRepository OK, by convention - //- IRepository -> none! - //- IRepository -> OK, by AddDefaultEfCoreRepositories but note to EfCoreIdentityUserRepository! - //- IQueryableRepository -> none! - //- IQueryableRepository - OK, by AddDefaultEfCoreRepositories, but not to EfCoreIdentityUserRepository! public class EfCoreIdentityUserRepository : EfCoreRepository, IIdentityUserRepository { public EfCoreIdentityUserRepository(IDbContextProvider dbContextProvider) diff --git a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs index 24d7a9ede9..83f4d56c78 100644 --- a/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs +++ b/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs @@ -11,8 +11,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore { services.AddAbpDbContext(options => { - options.AddDefaultRepositories(true); - options.AddCustomRepository(); + options.WithDefaultRepositories(true); + options.WithCustomRepository(); + options.WithCustomRepository(); }); services.AddAssemblyOf(); diff --git a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRole.cs b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRole.cs index 4f7ed611ff..dc8cc0b067 100644 --- a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRole.cs +++ b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRole.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Security.Claims; using JetBrains.Annotations; using Volo.Abp.Domain.Entities; +using Volo.ExtensionMethods.Collections.Generic; namespace Volo.Abp.Identity { @@ -15,7 +17,7 @@ namespace Volo.Abp.Identity /// /// Represents a role in the identity system /// - public class IdentityRole : AggregateRoot + public class IdentityRole : AggregateRoot, IHasConcurrencyStamp { /// /// Gets or sets the name for this role. @@ -53,6 +55,23 @@ namespace Volo.Abp.Identity Name = name; } + public void AddClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.Add(new IdentityRoleClaim(Id, claim)); + } + + public void AddClaims([NotNull] IEnumerable claims) + { + Check.NotNull(claims, nameof(claims)); + + foreach (var claim in claims) + { + AddClaim(claim); + } + } + /// /// Returns the name of the role. /// @@ -61,5 +80,12 @@ namespace Volo.Abp.Identity { return Name; } + + public void RemoveClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.RemoveAll(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value); + } } } diff --git a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRoleManager.cs b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRoleManager.cs index 3515733710..5efadf1df6 100644 --- a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRoleManager.cs +++ b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityRoleManager.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Identity public class IdentityRoleManager : RoleManager, IDomainService { public IdentityRoleManager( - IRoleStore store, + RoleStore store, IEnumerable> roleValidators, ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, diff --git a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs index 57adc8969d..e22ab4829c 100644 --- a/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs +++ b/src/Volo.Abp.Identity/Volo/Abp/Identity/IdentityUser.cs @@ -159,13 +159,20 @@ namespace Volo.Abp.Identity return Roles.Any(r => r.RoleId == roleId); } + public void AddClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.Add(new IdentityUserClaim(Id, claim)); + } + public void AddClaims([NotNull] IEnumerable claims) { Check.NotNull(claims, nameof(claims)); foreach (var claim in claims) { - Claims.Add(new IdentityUserClaim(Id, claim)); + AddClaim(claim); } } @@ -187,10 +194,17 @@ namespace Volo.Abp.Identity foreach (var claim in claims) { - Claims.RemoveAll(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); + RemoveClaim(claim); } } + public void RemoveClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.RemoveAll(c => c.ClaimValue == claim.Value && c.ClaimType == claim.Type); + } + public void AddLogin([NotNull] UserLoginInfo login) { Check.NotNull(login, nameof(login)); diff --git a/src/Volo.Abp.Identity/Volo/Abp/Identity/RoleStore.cs b/src/Volo.Abp.Identity/Volo/Abp/Identity/RoleStore.cs new file mode 100644 index 0000000000..e1606b53b1 --- /dev/null +++ b/src/Volo.Abp.Identity/Volo/Abp/Identity/RoleStore.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Uow; +using Volo.DependencyInjection; + +namespace Volo.Abp.Identity +{ + /// + /// Creates a new instance of a persistence store for roles. + /// + public class RoleStore : + IRoleStore, + IRoleClaimStore, + ITransientDependency + { + private readonly IIdentityRoleRepository _roleRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + /// + /// Constructs a new instance of . + /// + public RoleStore( + IIdentityRoleRepository roleRepository, + IUnitOfWorkManager unitOfWorkManager, + IdentityErrorDescriber describer = null) + { + _roleRepository = roleRepository; + _unitOfWorkManager = unitOfWorkManager; + + ErrorDescriber = describer ?? new IdentityErrorDescriber(); + } + + private bool _disposed; //TODO: Remove! + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + /// Saves the current store. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + protected Task SaveChanges(CancellationToken cancellationToken) + { + if (!AutoSaveChanges || _unitOfWorkManager.Current == null) + { + return Task.CompletedTask; + } + + return _unitOfWorkManager.Current.SaveChangesAsync(cancellationToken); + } + + /// + /// Creates a new role in a store as an asynchronous operation. + /// + /// The role to create in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task CreateAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.InsertAsync(role); + await SaveChanges(cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Updates a role in a store as an asynchronous operation. + /// + /// The role to update in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task UpdateAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.UpdateAsync(role); + + try + { + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + //TODO: Log...? + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Deletes a role from the store as an asynchronous operation. + /// + /// The role to delete from the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task DeleteAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + try + { + await _roleRepository.DeleteAsync(role); + await SaveChanges(cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + //TODO: Log... + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the ID of the role. + public Task GetRoleIdAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Id); + } + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// The role whose name should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public Task GetRoleNameAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Name); + } + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public Task SetRoleNameAsync([NotNull] IdentityRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + role.Name = roleName; + return Task.CompletedTask; + } + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByIdAsync([NotNull] string id, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(id, nameof(id)); + + //TODO: Add cancellationToken to Repository.FindAsync method as overload + return _roleRepository.FindAsync(id); + } + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByNameAsync([NotNull] string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(normalizedName, nameof(normalizedName)); + + return _roleRepository.FindByNormalizedNameAsync(normalizedName, cancellationToken); + } + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetNormalizedRoleNameAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.NormalizedName); + } + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedRoleNameAsync([NotNull] IdentityRole role, string normalizedName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + role.NormalizedName = normalizedName; + + return Task.CompletedTask; + } + + /// + /// Throws if this class has been disposed. + /// + protected void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Dispose the stores + /// + public void Dispose() + { + _disposed = true; + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a role. + public Task> GetClaimsAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult>(role.Claims.Select(c => c.ToClaim()).ToList()); + } + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public Task AddClaimAsync([NotNull] IdentityRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + role.AddClaim(claim); + + return Task.FromResult(false); + } + + /// + /// Removes the given from the specified . + /// + /// The role to remove the claim from. + /// The claim to remove from the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public Task RemoveClaimAsync([NotNull] IdentityRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default(CancellationToken)) + { + ThrowIfDisposed(); + + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + role.RemoveClaim(claim); + + return Task.CompletedTask; + } + } +} diff --git a/src/Volo.Abp.Identity/Volo/Abp/Identity/UserStore.cs b/src/Volo.Abp.Identity/Volo/Abp/Identity/UserStore.cs index 0b5478818a..e69da199d4 100644 --- a/src/Volo.Abp.Identity/Volo/Abp/Identity/UserStore.cs +++ b/src/Volo.Abp.Identity/Volo/Abp/Identity/UserStore.cs @@ -197,9 +197,9 @@ namespace Volo.Abp.Identity await _userRepository.UpdateAsync(user); await SaveChanges(cancellationToken); } - catch (AbpDbConcurrencyException ex) //(DbUpdateConcurrencyException) + catch (AbpDbConcurrencyException ex) { - //TODO: Log... + //TODO: Log...? return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); } @@ -224,7 +224,7 @@ namespace Volo.Abp.Identity await _userRepository.DeleteAsync(user); await SaveChanges(cancellationToken); } - catch (AbpDbConcurrencyException ex) //(DbUpdateConcurrencyException) + catch (AbpDbConcurrencyException ex) { //TODO: Log... return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); diff --git a/test/Volo.Abp.Identity.Tests/Volo/Abp/Identity/Initialize_Tests.cs b/test/Volo.Abp.Identity.Tests/Volo/Abp/Identity/Initialize_Tests.cs index 77b1f23dcf..e7f3b28ead 100644 --- a/test/Volo.Abp.Identity.Tests/Volo/Abp/Identity/Initialize_Tests.cs +++ b/test/Volo.Abp.Identity.Tests/Volo/Abp/Identity/Initialize_Tests.cs @@ -9,12 +9,21 @@ namespace Volo.Abp.Identity public class Initialize_Tests : AbpIntegratedTest { [Fact] - public void Should_Resolve_Services() + public void Should_Resolve_UserManager() { ServiceProvider.GetRequiredService(); + } - //TODO: Move this service to Volo.Abp.EntityFrameworkCore.Tests since it's actually testing the EF Core repository registration! + [Fact] + public void Should_Resolve_RoleManager() + { + ServiceProvider.GetRequiredService(); + } + //TODO: Move this service to Volo.Abp.EntityFrameworkCore.Tests since it's actually testing the EF Core repository registration! + [Fact] + public void Should_Resolve_Repositories() + { (ServiceProvider.GetRequiredService() is EfCoreIdentityUserRepository).ShouldBeTrue(); (ServiceProvider.GetRequiredService>() is EfCoreIdentityUserRepository).ShouldBeTrue();