diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs index 0715d2512a..77b2ff89b7 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; namespace Volo.Abp.Identity @@ -29,5 +30,7 @@ namespace Volo.Abp.Identity [CanBeNull] public string[] RoleNames { get; set; } + + public Guid[] OrganizationUnits { get; set; } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 87c599ea4f..c542f45ea3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -64,6 +64,7 @@ "Identity.StaticRoleRenamingErrorMessage": "Static roles can not be renamed.", "Identity.StaticRoleDeletionErrorMessage": "Static roles can not be deleted.", "Identity.OrganizationUnit.DuplicateDisplayNameWarning": "There is already an organization unit with name {0}. Two units with same name can not be created in same level.", + "Identity.OrganizationUnit.MaxUserMembershipCount": "Maximum allowed organization unit membership count for a user", "Volo.Abp.Identity:010001": "You can not delete your own account!", "Permission:IdentityManagement": "Identity management", "Permission:RoleManagement": "Role management", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs index babeb4fb77..144f3c5603 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs @@ -40,5 +40,12 @@ public const string IsUserNameUpdateEnabled = UserPrefix + ".IsUserNameUpdateEnabled"; public const string IsEmailUpdateEnabled = UserPrefix + ".IsEmailUpdateEnabled"; } + + public static class OrganizationUnit + { + private const string OrganizationUnitPrefix = Prefix + ".OrganizationUnit"; + + public const string MaxUserMembershipCount = OrganizationUnitPrefix + ".MaxUserMembershipCount"; + } } } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs index efae6e467a..1c569fb2e2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs @@ -11,83 +11,89 @@ namespace Volo.Abp.Identity { context.Add( new SettingDefinition( - IdentitySettingNames.Password.RequiredLength, - 6.ToString(), - L("DisplayName:Abp.Identity.Password.RequiredLength"), - L("Description:Abp.Identity.Password.RequiredLength"), + IdentitySettingNames.Password.RequiredLength, + 6.ToString(), + L("DisplayName:Abp.Identity.Password.RequiredLength"), + L("Description:Abp.Identity.Password.RequiredLength"), true), new SettingDefinition( - IdentitySettingNames.Password.RequiredUniqueChars, - 1.ToString(), - L("DisplayName:Abp.Identity.Password.RequiredUniqueChars"), - L("Description:Abp.Identity.Password.RequiredUniqueChars"), + IdentitySettingNames.Password.RequiredUniqueChars, + 1.ToString(), + L("DisplayName:Abp.Identity.Password.RequiredUniqueChars"), + L("Description:Abp.Identity.Password.RequiredUniqueChars"), true), new SettingDefinition( - IdentitySettingNames.Password.RequireNonAlphanumeric, - true.ToString(), - L("DisplayName:Abp.Identity.Password.RequireNonAlphanumeric"), - L("Description:Abp.Identity.Password.RequireNonAlphanumeric"), + IdentitySettingNames.Password.RequireNonAlphanumeric, + true.ToString(), + L("DisplayName:Abp.Identity.Password.RequireNonAlphanumeric"), + L("Description:Abp.Identity.Password.RequireNonAlphanumeric"), true), new SettingDefinition( - IdentitySettingNames.Password.RequireLowercase, - true.ToString(), L("DisplayName:Abp.Identity.Password.RequireLowercase"), - L("Description:Abp.Identity.Password.RequireLowercase"), + IdentitySettingNames.Password.RequireLowercase, + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireLowercase"), + L("Description:Abp.Identity.Password.RequireLowercase"), true), new SettingDefinition( - IdentitySettingNames.Password.RequireUppercase, - true.ToString(), L("DisplayName:Abp.Identity.Password.RequireUppercase"), - L("Description:Abp.Identity.Password.RequireUppercase"), + IdentitySettingNames.Password.RequireUppercase, + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireUppercase"), + L("Description:Abp.Identity.Password.RequireUppercase"), true), new SettingDefinition( - IdentitySettingNames.Password.RequireDigit, - true.ToString(), L("DisplayName:Abp.Identity.Password.RequireDigit"), - L("Description:Abp.Identity.Password.RequireDigit"), + IdentitySettingNames.Password.RequireDigit, + true.ToString(), L("DisplayName:Abp.Identity.Password.RequireDigit"), + L("Description:Abp.Identity.Password.RequireDigit"), true), new SettingDefinition( - IdentitySettingNames.Lockout.AllowedForNewUsers, - true.ToString(), L("DisplayName:Abp.Identity.Lockout.AllowedForNewUsers"), - L("Description:Abp.Identity.Lockout.AllowedForNewUsers"), + IdentitySettingNames.Lockout.AllowedForNewUsers, + true.ToString(), L("DisplayName:Abp.Identity.Lockout.AllowedForNewUsers"), + L("Description:Abp.Identity.Lockout.AllowedForNewUsers"), true), - + new SettingDefinition( - IdentitySettingNames.Lockout.LockoutDuration, - (5*60).ToString(), L("DisplayName:Abp.Identity.Lockout.LockoutDuration"), - L("Description:Abp.Identity.Lockout.LockoutDuration"), + IdentitySettingNames.Lockout.LockoutDuration, + (5 * 60).ToString(), L("DisplayName:Abp.Identity.Lockout.LockoutDuration"), + L("Description:Abp.Identity.Lockout.LockoutDuration"), true), new SettingDefinition( - IdentitySettingNames.Lockout.MaxFailedAccessAttempts, - 5.ToString(), L("DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts"), - L("Description:Abp.Identity.Lockout.MaxFailedAccessAttempts"), + IdentitySettingNames.Lockout.MaxFailedAccessAttempts, + 5.ToString(), L("DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts"), + L("Description:Abp.Identity.Lockout.MaxFailedAccessAttempts"), true), new SettingDefinition( - IdentitySettingNames.SignIn.RequireConfirmedEmail, - false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail"), - L("Description:Abp.Identity.SignIn.RequireConfirmedEmail"), + IdentitySettingNames.SignIn.RequireConfirmedEmail, + false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail"), + L("Description:Abp.Identity.SignIn.RequireConfirmedEmail"), true), new SettingDefinition( - IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, - false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), - L("Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), + IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, + false.ToString(), L("DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), + L("Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber"), true), new SettingDefinition( - IdentitySettingNames.User.IsUserNameUpdateEnabled, - true.ToString(), L("DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled"), - L("Description:Abp.Identity.User.IsUserNameUpdateEnabled"), + IdentitySettingNames.User.IsUserNameUpdateEnabled, + true.ToString(), L("DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled"), + L("Description:Abp.Identity.User.IsUserNameUpdateEnabled"), true), new SettingDefinition( - IdentitySettingNames.User.IsEmailUpdateEnabled, - true.ToString(), L("DisplayName:Abp.Identity.User.IsEmailUpdateEnabled"), - L("Description:Abp.Identity.User.IsEmailUpdateEnabled"), + IdentitySettingNames.User.IsEmailUpdateEnabled, + true.ToString(), L("DisplayName:Abp.Identity.User.IsEmailUpdateEnabled"), + L("Description:Abp.Identity.User.IsEmailUpdateEnabled"), + true), + + new SettingDefinition( + IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount, + int.MaxValue.ToString(), L("Identity.OrganizationUnit.MaxUserMembershipCount"), + L("Identity.OrganizationUnit.MaxUserMembershipCount"), true) ); } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs index 2afc246394..6b155aa574 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -129,7 +129,7 @@ namespace Volo.Abp.Identity /// /// Navigation property for this organization units. /// - public virtual ICollection OrganizationUnits { get; protected set; } + public virtual ICollection OrganizationUnits { get; protected set; } protected IdentityUser() { @@ -154,7 +154,7 @@ namespace Volo.Abp.Identity Claims = new Collection(); Logins = new Collection(); Tokens = new Collection(); - OrganizationUnits = new Collection(); + OrganizationUnits = new Collection(); ExtraProperties = new Dictionary(); } @@ -293,7 +293,7 @@ namespace Volo.Abp.Identity return; } - OrganizationUnits.Add(new OrganizationUnitUser(TenantId, Id, organizationUnitId)); + OrganizationUnits.Add(new IdentityUserOrganizationUnit(TenantId, Id, organizationUnitId)); } public virtual void RemoveOrganizationUnit(Guid organizationUnitId) diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs index 9e66b7b824..acb6b56dae 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -9,7 +9,12 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Services; +using Volo.Abp.Identity.Organizations; +using Volo.Abp.Domain.Repositories; using Volo.Abp.Threading; +using Volo.Abp.Uow; +using Volo.Abp.Settings; +using Volo.Abp.Identity.Settings; namespace Volo.Abp.Identity { @@ -18,6 +23,9 @@ namespace Volo.Abp.Identity protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; private readonly ICancellationTokenProvider _cancellationTokenProvider; + protected IOrganizationUnitRepository _organizationUnitRepository { get; private set; } + protected IIdentityUserRepository _identityUserRepository { get; private set; } + private readonly ISettingProvider _settingProvider; public IdentityUserManager( IdentityUserStore store, @@ -28,7 +36,10 @@ namespace Volo.Abp.Identity ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, IServiceProvider services, ILogger logger, - ICancellationTokenProvider cancellationTokenProvider) + ICancellationTokenProvider cancellationTokenProvider, + IOrganizationUnitRepository organizationUnitRepository, + IIdentityUserRepository identityUserRepository, + ISettingProvider settingProvider) : base( store, optionsAccessor, @@ -41,6 +52,9 @@ namespace Volo.Abp.Identity logger) { _cancellationTokenProvider = cancellationTokenProvider; + _organizationUnitRepository = organizationUnitRepository; + _identityUserRepository = identityUserRepository; + _settingProvider = settingProvider; } public virtual async Task GetByIdAsync(Guid id) @@ -58,7 +72,7 @@ namespace Volo.Abp.Identity { Check.NotNull(user, nameof(user)); Check.NotNull(roleNames, nameof(roleNames)); - + var currentRoleNames = await GetRolesAsync(user).ConfigureAwait(false); var result = await RemoveFromRolesAsync(user, currentRoleNames.Except(roleNames).Distinct()).ConfigureAwait(false); @@ -75,5 +89,115 @@ namespace Volo.Abp.Identity return IdentityResult.Success; } + + public virtual async Task IsInOrganizationUnitAsync(Guid userId, Guid ouId) + { + return await IsInOrganizationUnitAsync( + await GetByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual Task IsInOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou) + { + return Task.FromResult(user.IsInOrganizationUnit(ou.Id)); + } + + public virtual async Task AddToOrganizationUnitAsync(Guid userId, Guid ouId) + { + await AddToOrganizationUnitAsync( + await GetByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual async Task AddToOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou) + { + await _identityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, _cancellationTokenProvider.Token).ConfigureAwait(false); + + var currentOus = user.OrganizationUnits; + + if (currentOus.Any(cou => cou.Id == ou.Id)) + { + return; + } + + await CheckMaxUserOrganizationUnitMembershipCountAsync(user.TenantId, currentOus.Count + 1); + + user.AddOrganizationUnit(ou.Id); + } + + public virtual async Task RemoveFromOrganizationUnitAsync(Guid userId, Guid ouId) + { + await RemoveFromOrganizationUnitAsync( + await GetByIdAsync(userId), + await _organizationUnitRepository.GetAsync(ouId) + ); + } + + public virtual async Task RemoveFromOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou) + { + await _identityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, _cancellationTokenProvider.Token).ConfigureAwait(false); + + user.RemoveOrganizationUnit(ou.Id); + } + + public virtual async Task SetOrganizationUnitsAsync(Guid userId, params Guid[] organizationUnitIds) + { + await SetOrganizationUnitsAsync( + await GetByIdAsync(userId), + organizationUnitIds + ); + } + + public virtual async Task SetOrganizationUnitsAsync(IdentityUser user, params Guid[] organizationUnitIds) + { + Check.NotNull(user, nameof(user)); + Check.NotNull(organizationUnitIds, nameof(organizationUnitIds)); + + await CheckMaxUserOrganizationUnitMembershipCountAsync(user.TenantId, organizationUnitIds.Length); + + var currentOus = user.OrganizationUnits; + + //Remove from removed OUs + foreach (var currentOu in currentOus) + { + if (!organizationUnitIds.Contains(currentOu.Id)) + { + await RemoveFromOrganizationUnitAsync(user.Id, currentOu.Id); + } + } + + //Add to added OUs + foreach (var organizationUnitId in organizationUnitIds) + { + if (currentOus.All(ou => ou.Id != organizationUnitId)) + { + await AddToOrganizationUnitAsync( + user, + await _organizationUnitRepository.GetAsync(organizationUnitId) + ); + } + } + } + + private async Task CheckMaxUserOrganizationUnitMembershipCountAsync(Guid? tenantId, int requestedCount) + { + var maxCount = await _settingProvider.GetAsync(IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount).ConfigureAwait(false); + if (requestedCount > maxCount) + { + throw new AbpException(string.Format("Can not set more than {0} organization unit for a user!", maxCount)); + } + } + + [UnitOfWork] + public virtual async Task> GetOrganizationUnitsAsync(IdentityUser user) + { + await _identityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, _cancellationTokenProvider.Token).ConfigureAwait(false); + + var ouOfUser = user.OrganizationUnits; + + return await _organizationUnitRepository.GetListAsync(ouOfUser.Select(t => t.OrganizationUnitId)); + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/OrganizationUnitUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserOrganizationUnit.cs similarity index 83% rename from modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/OrganizationUnitUser.cs rename to modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserOrganizationUnit.cs index 3ab7a1c711..3721210bd6 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/OrganizationUnitUser.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserOrganizationUnit.cs @@ -2,12 +2,12 @@ using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.MultiTenancy; -namespace Volo.Abp.Identity.Organizations +namespace Volo.Abp.Identity { /// /// Represents membership of a User to an OU. /// - public class OrganizationUnitUser : CreationAuditedEntity, IMultiTenant, ISoftDelete + public class IdentityUserOrganizationUnit : CreationAuditedEntity, IMultiTenant, ISoftDelete { /// @@ -33,7 +33,7 @@ namespace Volo.Abp.Identity.Organizations /// /// Initializes a new instance of the class. /// - public OrganizationUnitUser() + public IdentityUserOrganizationUnit() { } @@ -44,7 +44,7 @@ namespace Volo.Abp.Identity.Organizations /// TenantId /// Id of the User. /// Id of the . - public OrganizationUnitUser(Guid? tenantId, Guid userId, Guid organizationUnitId) + public IdentityUserOrganizationUnit(Guid? tenantId, Guid userId, Guid organizationUnitId) { TenantId = tenantId; UserId = userId; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/IOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/IOrganizationUnitRepository.cs index 0136d9ef4c..2d0987a855 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/IOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/Organizations/IOrganizationUnitRepository.cs @@ -10,5 +10,7 @@ namespace Volo.Abp.Identity.Organizations Task> GetChildrenAsync(Guid? parentId); Task> GetAllChildrenWithParentCodeAsync(string code, Guid? parentId); + + Task> GetListAsync(IEnumerable ids); } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs index f24adad3f7..37648adef8 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs @@ -28,5 +28,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore return await DbSet.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value) .ToListAsync(); } + + public async Task> GetListAsync(IEnumerable ids) + { + return await DbSet.Where(t => ids.Contains(t.Id)).ToListAsync(); + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs index 759fe9a1b8..20e15d1dff 100644 --- a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -165,9 +165,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore b.HasIndex(ou => new { ou.RoleId, ou.OrganizationUnitId }); }); - builder.Entity(b => + builder.Entity(b => { - b.ToTable(options.TablePrefix + "OrganizationUnitUsers", options.Schema); + b.ToTable(options.TablePrefix + "UserOrganizationUnits", options.Schema); b.HasKey(ou => new { ou.OrganizationUnitId, ou.UserId });