Browse Source

Merge pull request #3732 from abpframework/pr/2563

Organization units for the Identity module
pull/4082/head
Alper Ebicoglu 6 years ago
committed by GitHub
parent
commit
589615d3f6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs
  2. 2
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs
  3. 7
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs
  4. 5
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json
  5. 1
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json
  6. 5
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json
  7. 1
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json
  8. 25
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs
  9. 7
      modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs
  10. 102
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs
  11. 4
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs
  12. 29
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs
  13. 72
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs
  14. 42
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs
  15. 145
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  16. 44
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserOrganizationUnit.cs
  17. 50
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs
  18. 212
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs
  19. 188
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs
  20. 53
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitRole.cs
  21. 2
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj
  22. 1
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs
  23. 22
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs
  24. 89
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs
  25. 160
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs
  26. 4
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs
  27. 2
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs
  28. 44
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
  29. 14
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs
  30. 2
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs
  31. 5
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs
  32. 1
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs
  33. 2
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs
  34. 24
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs
  35. 98
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs
  36. 138
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs
  37. 6
      modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs
  38. 62
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs
  39. 121
      modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs
  40. 6
      modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/OrganizationUnitRepository_Tests.cs
  41. 10
      modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs
  42. 68
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs
  43. 34
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs
  44. 2
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs
  45. 18
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs
  46. 155
      modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs

3
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;
using Volo.Abp.ObjectExtending;

2
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs

@ -45,6 +45,8 @@ namespace Volo.Abp.Identity
[Authorize(IdentityPermissions.Users.Default)]
public virtual async Task<ListResultDto<IdentityRoleDto>> GetRolesAsync(Guid id)
{
//TODO: Should also include roles of the related OUs.
var roles = await UserRepository.GetRolesAsync(id);
return new ListResultDto<IdentityRoleDto>(

7
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityErrorCodes.cs → modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Identity
namespace Volo.Abp.Identity
{
public static class IdentityErrorCodes
{
public const string UserSelfDeletion = "Volo.Abp.Identity:010001";
public const string MaxAllowedOuMembership = "Volo.Abp.Identity:010002";
}
}

5
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json

@ -63,6 +63,8 @@
"Identity.PasswordConfirmationFailed": "Password does not match the confirm password.",
"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",
@ -99,6 +101,7 @@
"Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Whether the phoneNumber can be confirmed by the user.",
"Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Whether a confirmed telephone number is required to sign in.",
"Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Whether the username can be updated by the user.",
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user."
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "Whether the email can be updated by the user.",
"Volo.Abp.Identity:010002": "Can not set more than {MaxUserMembershipCount} organization unit for a user!"
}
}

1
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json

@ -63,6 +63,7 @@
"Identity.PasswordConfirmationFailed": "A senha não confere com a confirmação de senha.",
"Identity.StaticRoleRenamingErrorMessage": "Perfis estáticos não podem ser renomeados.",
"Identity.StaticRoleDeletionErrorMessage": "Perfis estáticos não podem ser excluídos.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "Já existe uma unidade organizacional com o nome {0}. Duas unidades com o mesmo nome não pode ser criada no mesmo nível.",
"Volo.Abp.Identity:010001": "Você não pode deletar sua própria conta!",
"Permission:IdentityManagement": "Acessos",
"Permission:RoleManagement": "Perfis",

5
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json

@ -63,6 +63,8 @@
"Identity.UserNameNotFound": "{0} kullanıcısı bulunamadı.",
"Identity.UserNotInRole": "Kullanıcı '{0}' rolünde değil.",
"Identity.PasswordConfirmationFailed": "Yeni şifre ile onay şifresi uyuşmuyor.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "{0} isminde bir birim zaten var. Aynı seviyede aynı isimli iki birim olamaz.",
"Identity.OrganizationUnit.MaxUserMembershipCount": "Bir kullanıcı için izin verilen en fazla organizasyon birimi sayısı",
"Volo.Abp.Identity:010001": "Kendi hesabınızı silemezsiniz!",
"Permission:IdentityManagement": "Kimlik yönetimi",
"Permission:RoleManagement": "Rol yönetimi",
@ -72,7 +74,8 @@
"Permission:ChangePermissions": "İzinleri değiştirme",
"Permission:UserManagement": "Kullanıcı yönetimi",
"Permission:UserLookup": "Kullanıcı sorgulama",
"DisplayName:Abp.Identity.Password.RequiredLength": "Uzunluk gerekli",
"Volo.Abp.Identity:010002": "Bir kullanıcı en fazla {MaxUserMembershipCount} organizasyon birimine üye olabilir!",
"DisplayName:Abp.Identity.Password.RequiredLength": "Uzunluk gerekli",
"DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Tekil karakter gerekli",
"DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Alfasayısal olmayan karakter gerekli",
"DisplayName:Abp.Identity.Password.RequireLowercase": "Küçük harf gerekli",

1
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json

@ -63,6 +63,7 @@
"Identity.PasswordConfirmationFailed": "密码或确认密码不一致.",
"Identity.StaticRoleRenamingErrorMessage": "无法重命名静态角色.",
"Identity.StaticRoleDeletionErrorMessage": "无法删除静态角色.",
"Identity.OrganizationUnit.DuplicateDisplayNameWarning": "已存在名为 {0} 的组织单位. 无法在同一级别创建相同名称的组织单位.",
"Volo.Abp.Identity:010001": "您无法删除自己的帐户!",
"Permission:IdentityManagement": "身份标识管理",
"Permission:RoleManagement": "角色管理",

25
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/OrganizationUnitConsts.cs

@ -0,0 +1,25 @@
namespace Volo.Abp.Identity
{
public static class OrganizationUnitConsts
{
/// <summary>
/// Maximum length of the <see cref="DisplayName"/> property.
/// </summary>
public const int MaxDisplayNameLength = 128;
/// <summary>
/// Maximum depth of an OU hierarchy.
/// </summary>
public const int MaxDepth = 16;
/// <summary>
/// Length of a code unit between dots.
/// </summary>
public const int CodeUnitLength = 5;
/// <summary>
/// Maximum length of the <see cref="Code"/> property.
/// </summary>
public const int MaxCodeLength = MaxDepth * (CodeUnitLength + 1) - 1;
}
}

7
modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Settings/IdentitySettingNames.cs

@ -41,5 +41,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";
}
}
}

102
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs

@ -11,88 +11,106 @@ 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.EnablePhoneNumberConfirmation,
true.ToString(), L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"),
true.ToString(),
L("DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"),
L("Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation"),
true),
new SettingDefinition(
IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber,
false.ToString(), L("DisplayName:Abp.Identity.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)
);
}

4
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs

@ -20,6 +20,10 @@ namespace Volo.Abp.Identity
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<IdentityRole>> GetListAsync(
IEnumerable<Guid> ids,
CancellationToken cancellationToken = default
);
Task<List<IdentityRole>> GetDefaultOnesAsync(

29
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs

@ -21,6 +21,10 @@ namespace Volo.Abp.Identity
CancellationToken cancellationToken = default
);
Task<List<string>> GetRoleNamesInOrganizationUnitAsync(
Guid id,
CancellationToken cancellationToken = default);
Task<IdentityUser> FindByLoginAsync(
[NotNull] string loginProvider,
[NotNull] string providerKey,
@ -29,19 +33,19 @@ namespace Volo.Abp.Identity
);
Task<IdentityUser> FindByNormalizedEmailAsync(
[NotNull] string normalizedEmail,
[NotNull] string normalizedEmail,
bool includeDetails = true,
CancellationToken cancellationToken = default
);
Task<List<IdentityUser>> GetListByClaimAsync(
Claim claim,
Claim claim,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
@ -61,6 +65,25 @@ namespace Volo.Abp.Identity
CancellationToken cancellationToken = default
);
Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(
Guid id,
bool includeDetails = false,
CancellationToken cancellationToken = default);
Task<List<IdentityUser>> GetUsersInOrganizationUnitAsync(
Guid organizationUnitId,
CancellationToken cancellationToken = default
);
Task<List<IdentityUser>> GetUsersInOrganizationsListAsync(
List<Guid> organizationUnitIds,
CancellationToken cancellationToken = default
);
Task<List<IdentityUser>> GetUsersInOrganizationUnitWithChildrenAsync(
string code,
CancellationToken cancellationToken = default
);
Task<long> GetCountAsync(
string filter = null,
CancellationToken cancellationToken = default

72
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IOrganizationUnitRepository.cs

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Abp.Identity
{
public interface IOrganizationUnitRepository : IBasicRepository<OrganizationUnit, Guid>
{
Task<List<OrganizationUnit>> GetChildrenAsync(
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<OrganizationUnit>> GetAllChildrenWithParentCodeAsync(
string code,
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<OrganizationUnit> GetAsync(
string displayName,
bool includeDetails = true,
CancellationToken cancellationToken = default
);
Task<List<OrganizationUnit>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<OrganizationUnit>> GetListAsync(
IEnumerable<Guid> ids,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<List<IdentityRole>> GetRolesAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<int> GetRolesCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default
);
Task<List<IdentityUser>> GetMembersAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default
);
}
}

42
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs

@ -124,9 +124,13 @@ namespace Volo.Abp.Identity
/// </summary>
public virtual ICollection<IdentityUserToken> Tokens { get; protected set; }
/// <summary>
/// Navigation property for this organization units.
/// </summary>
public virtual ICollection<IdentityUserOrganizationUnit> OrganizationUnits { get; protected set; }
protected IdentityUser()
{
ExtraProperties = new Dictionary<string, object>();
}
public IdentityUser(Guid id, [NotNull] string userName, [NotNull] string email, Guid? tenantId = null)
@ -147,6 +151,7 @@ namespace Volo.Abp.Identity
Claims = new Collection<IdentityUserClaim>();
Logins = new Collection<IdentityUserLogin>();
Tokens = new Collection<IdentityUserToken>();
OrganizationUnits = new Collection<IdentityUserOrganizationUnit>();
ExtraProperties = new Dictionary<string, object>();
}
@ -276,6 +281,41 @@ namespace Volo.Abp.Identity
Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name);
}
public virtual void AddOrganizationUnit(Guid organizationUnitId)
{
if (IsInOrganizationUnit(organizationUnitId))
{
return;
}
OrganizationUnits.Add(
new IdentityUserOrganizationUnit(
Id,
organizationUnitId,
TenantId
)
);
}
public virtual void RemoveOrganizationUnit(Guid organizationUnitId)
{
if (!IsInOrganizationUnit(organizationUnitId))
{
return;
}
OrganizationUnits.RemoveAll(
ou => ou.OrganizationUnitId == organizationUnitId
);
}
public virtual bool IsInOrganizationUnit(Guid organizationUnitId)
{
return OrganizationUnits.Any(
ou => ou.OrganizationUnitId == organizationUnitId
);
}
public override string ToString()
{
return $"{base.ToString()}, UserName = {UserName}";

145
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -11,6 +11,9 @@ using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
using Volo.Abp.Settings;
using Volo.Abp.Identity.Settings;
namespace Volo.Abp.Identity
{
@ -18,11 +21,13 @@ namespace Volo.Abp.Identity
{
protected IIdentityRoleRepository RoleRepository { get; }
protected IIdentityUserRepository UserRepository { get; }
protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
protected IIdentityUserRepository IdentityUserRepository { get; }
protected ISettingProvider SettingProvider { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected override CancellationToken CancellationToken => CancellationTokenProvider.Token;
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public IdentityUserManager(
IdentityUserStore store,
IIdentityRoleRepository roleRepository,
@ -35,7 +40,10 @@ namespace Volo.Abp.Identity
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider)
ICancellationTokenProvider cancellationTokenProvider,
IOrganizationUnitRepository organizationUnitRepository,
IIdentityUserRepository identityUserRepository,
ISettingProvider settingProvider)
: base(
store,
optionsAccessor,
@ -47,6 +55,9 @@ namespace Volo.Abp.Identity
services,
logger)
{
OrganizationUnitRepository = organizationUnitRepository;
IdentityUserRepository = identityUserRepository;
SettingProvider = settingProvider;
RoleRepository = roleRepository;
UserRepository = userRepository;
CancellationTokenProvider = cancellationTokenProvider;
@ -67,7 +78,7 @@ namespace Volo.Abp.Identity
{
Check.NotNull(user, nameof(user));
Check.NotNull(roleNames, nameof(roleNames));
var currentRoleNames = await GetRolesAsync(user);
var result = await RemoveFromRolesAsync(user, currentRoleNames.Except(roleNames).Distinct());
@ -85,10 +96,132 @@ namespace Volo.Abp.Identity
return IdentityResult.Success;
}
public virtual async Task<bool> IsInOrganizationUnitAsync(Guid userId, Guid ouId)
{
var user = await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken);
return user.IsInOrganizationUnit(ouId);
}
public virtual async Task<bool> IsInOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
return user.IsInOrganizationUnit(ou.Id);
}
public virtual async Task AddToOrganizationUnitAsync(Guid userId, Guid ouId)
{
await AddToOrganizationUnitAsync(
await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken),
await OrganizationUnitRepository.GetAsync(ouId, cancellationToken: CancellationToken)
);
}
public virtual async Task AddToOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
if (user.OrganizationUnits.Any(cou => cou.OrganizationUnitId == ou.Id))
{
return;
}
await CheckMaxUserOrganizationUnitMembershipCountAsync(user.OrganizationUnits.Count + 1);
user.AddOrganizationUnit(ou.Id);
}
public virtual async Task RemoveFromOrganizationUnitAsync(Guid userId, Guid ouId)
{
var user = await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken);
user.RemoveOrganizationUnit(ouId);
}
public virtual async Task RemoveFromOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
user.RemoveOrganizationUnit(ou.Id);
}
public virtual async Task SetOrganizationUnitsAsync(Guid userId, params Guid[] organizationUnitIds)
{
await SetOrganizationUnitsAsync(
await IdentityUserRepository.GetAsync(userId, cancellationToken: CancellationToken),
organizationUnitIds
);
}
public virtual async Task SetOrganizationUnitsAsync(IdentityUser user, params Guid[] organizationUnitIds)
{
Check.NotNull(user, nameof(user));
Check.NotNull(organizationUnitIds, nameof(organizationUnitIds));
await CheckMaxUserOrganizationUnitMembershipCountAsync(organizationUnitIds.Length);
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
//Remove from removed OUs
foreach (var ouId in user.OrganizationUnits.Select(uou => uou.OrganizationUnitId).ToArray())
{
if (!organizationUnitIds.Contains(ouId))
{
user.RemoveOrganizationUnit(ouId);
}
}
//Add to added OUs
foreach (var organizationUnitId in organizationUnitIds)
{
if (!user.IsInOrganizationUnit(organizationUnitId))
{
user.AddOrganizationUnit(organizationUnitId);
}
}
}
private async Task CheckMaxUserOrganizationUnitMembershipCountAsync(int requestedCount)
{
var maxCount = await SettingProvider.GetAsync<int>(IdentitySettingNames.OrganizationUnit.MaxUserMembershipCount);
if (requestedCount > maxCount)
{
throw new BusinessException(IdentityErrorCodes.MaxAllowedOuMembership)
.WithData("MaxUserMembershipCount", maxCount);
}
}
[UnitOfWork]
public virtual async Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(IdentityUser user)
{
await IdentityUserRepository.EnsureCollectionLoadedAsync(user, u => u.OrganizationUnits, CancellationTokenProvider.Token);
return await OrganizationUnitRepository.GetListAsync(
user.OrganizationUnits.Select(t => t.OrganizationUnitId),
cancellationToken: CancellationToken
);
}
[UnitOfWork]
public virtual async Task<List<IdentityUser>> GetUsersInOrganizationUnitAsync(
OrganizationUnit organizationUnit,
bool includeChildren = false)
{
if (includeChildren)
{
return await IdentityUserRepository
.GetUsersInOrganizationUnitWithChildrenAsync(organizationUnit.Code, CancellationToken);
}
else
{
return await IdentityUserRepository
.GetUsersInOrganizationUnitAsync(organizationUnit.Id, CancellationToken);
}
}
public virtual async Task<IdentityResult> AddDefaultRolesAsync([NotNull] IdentityUser user)
{
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, CancellationToken);
foreach (var role in await RoleRepository.GetDefaultOnesAsync(cancellationToken: CancellationToken))
{
if (!user.IsInRole(role.Id))
@ -96,7 +229,7 @@ namespace Volo.Abp.Identity
user.AddRole(role.Id);
}
}
return await UpdateUserAsync(user);
}
}

44
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserOrganizationUnit.cs

@ -0,0 +1,44 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity
{
/// <summary>
/// Represents membership of a User to an OU.
/// </summary>
public class IdentityUserOrganizationUnit : CreationAuditedEntity, IMultiTenant
{
/// <summary>
/// TenantId of this entity.
/// </summary>
public virtual Guid? TenantId { get; protected set; }
/// <summary>
/// Id of the User.
/// </summary>
public virtual Guid UserId { get; protected set; }
/// <summary>
/// Id of the related <see cref="OrganizationUnit"/>.
/// </summary>
public virtual Guid OrganizationUnitId { get; protected set; }
protected IdentityUserOrganizationUnit()
{
}
public IdentityUserOrganizationUnit(Guid userId, Guid organizationUnitId, Guid? tenantId = null)
{
UserId = userId;
OrganizationUnitId = organizationUnitId;
TenantId = tenantId;
}
public override object[] GetKeys()
{
return new object[] { UserId, OrganizationUnitId };
}
}
}

50
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs

@ -53,6 +53,7 @@ namespace Volo.Abp.Identity
protected IIdentityRoleRepository RoleRepository { get; }
protected IGuidGenerator GuidGenerator { get; }
protected ILogger<IdentityRoleStore> Logger { get; }
protected ILookupNormalizer LookupNormalizer { get; }
protected IIdentityUserRepository UserRepository { get; }
public IdentityUserStore(
@ -60,12 +61,14 @@ namespace Volo.Abp.Identity
IIdentityRoleRepository roleRepository,
IGuidGenerator guidGenerator,
ILogger<IdentityRoleStore> logger,
ILookupNormalizer lookupNormalizer,
IdentityErrorDescriber describer = null)
{
UserRepository = userRepository;
RoleRepository = roleRepository;
GuidGenerator = guidGenerator;
Logger = logger;
LookupNormalizer = lookupNormalizer;
ErrorDescriber = describer ?? new IdentityErrorDescriber();
}
@ -313,13 +316,17 @@ namespace Volo.Abp.Identity
Check.NotNull(user, nameof(user));
Check.NotNull(normalizedRoleName, nameof(normalizedRoleName));
var role = await RoleRepository.FindByNormalizedNameAsync(normalizedRoleName, cancellationToken: cancellationToken);
if (await IsInRoleAsync(user, normalizedRoleName, cancellationToken))
{
return;
}
var role = await RoleRepository.FindByNormalizedNameAsync(normalizedRoleName, cancellationToken: cancellationToken);
if (role == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Role {0} does not exist!", normalizedRoleName));
}
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken);
user.AddRole(role.Id);
@ -337,11 +344,7 @@ namespace Volo.Abp.Identity
cancellationToken.ThrowIfCancellationRequested();
Check.NotNull(user, nameof(user));
if (string.IsNullOrWhiteSpace(normalizedRoleName))
{
throw new ArgumentException(nameof(normalizedRoleName) + " can not be null or whitespace");
}
Check.NotNullOrWhiteSpace(normalizedRoleName, nameof(normalizedRoleName));
var role = await RoleRepository.FindByNormalizedNameAsync(normalizedRoleName, cancellationToken: cancellationToken);
if (role == null)
@ -366,7 +369,13 @@ namespace Volo.Abp.Identity
Check.NotNull(user, nameof(user));
return await UserRepository.GetRoleNamesAsync(user.Id, cancellationToken: cancellationToken);
var userRoles = await UserRepository
.GetRoleNamesAsync(user.Id, cancellationToken: cancellationToken);
var userOrganizationUnitRoles = await UserRepository
.GetRoleNamesInOrganizationUnitAsync(user.Id, cancellationToken: cancellationToken);
return userRoles.Union(userOrganizationUnitRoles).ToList();
}
/// <summary>
@ -377,26 +386,21 @@ namespace Volo.Abp.Identity
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>A <see cref="Task{TResult}"/> containing a flag indicating if the specified user is a member of the given group. If the
/// user is a member of the group the returned value with be true, otherwise it will be false.</returns>
public virtual async Task<bool> IsInRoleAsync([NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default)
public virtual async Task<bool> IsInRoleAsync(
[NotNull] IdentityUser user,
[NotNull] string normalizedRoleName,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Check.NotNull(user, nameof(user));
Check.NotNullOrWhiteSpace(normalizedRoleName, nameof(normalizedRoleName));
if (string.IsNullOrWhiteSpace(normalizedRoleName))
{
throw new ArgumentException(nameof(normalizedRoleName) + " can not be null or whitespace");
}
var role = await RoleRepository.FindByNormalizedNameAsync(normalizedRoleName, cancellationToken: cancellationToken);
if (role == null)
{
return false;
}
await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken);
var roles = await GetRolesAsync(user, cancellationToken);
return user.IsInRole(role.Id);
return roles
.Select(r => LookupNormalizer.NormalizeName(r))
.Contains(normalizedRoleName);
}
/// <summary>

212
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnit.cs

@ -0,0 +1,212 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity
{
/// <summary>
/// Represents an organization unit (OU).
/// </summary>
public class OrganizationUnit : FullAuditedAggregateRoot<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
/// <summary>
/// Parent <see cref="OrganizationUnit"/> Id.
/// Null, if this OU is a root.
/// </summary>
public virtual Guid? ParentId { get; internal set; }
/// <summary>
/// Hierarchical Code of this organization unit.
/// Example: "00001.00042.00005".
/// This is a unique code for a Tenant.
/// It's changeable if OU hierarchy is changed.
/// </summary>
public virtual string Code { get; internal set; }
/// <summary>
/// Display name of this role.
/// </summary>
public virtual string DisplayName { get; set; }
/// <summary>
/// Roles of this OU.
/// </summary>
public virtual ICollection<OrganizationUnitRole> Roles { get; protected set; }
/// <summary>
/// Initializes a new instance of the <see cref="OrganizationUnit"/> class.
/// </summary>
public OrganizationUnit()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OrganizationUnit"/> class.
/// </summary>
/// <param name="tenantId">Tenant's Id or null for host.</param>
/// <param name="displayName">Display name.</param>
/// <param name="parentId">Parent's Id or null if OU is a root.</param>
public OrganizationUnit(Guid id, string displayName, Guid? parentId = null, Guid? tenantId = null)
{
Id = id;
TenantId = tenantId;
DisplayName = displayName;
ParentId = parentId;
Roles = new Collection<OrganizationUnitRole>();
}
/// <summary>
/// Creates code for given numbers.
/// Example: if numbers are 4,2 then returns "00004.00002";
/// </summary>
/// <param name="numbers">Numbers</param>
public static string CreateCode(params int[] numbers)
{
if (numbers.IsNullOrEmpty())
{
return null;
}
return numbers.Select(number => number.ToString(new string('0', OrganizationUnitConsts.CodeUnitLength))).JoinAsString(".");
}
/// <summary>
/// Appends a child code to a parent code.
/// Example: if parentCode = "00001", childCode = "00042" then returns "00001.00042".
/// </summary>
/// <param name="parentCode">Parent code. Can be null or empty if parent is a root.</param>
/// <param name="childCode">Child code.</param>
public static string AppendCode(string parentCode, string childCode)
{
if (childCode.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(childCode), "childCode can not be null or empty.");
}
if (parentCode.IsNullOrEmpty())
{
return childCode;
}
return parentCode + "." + childCode;
}
/// <summary>
/// Gets relative code to the parent.
/// Example: if code = "00019.00055.00001" and parentCode = "00019" then returns "00055.00001".
/// </summary>
/// <param name="code">The code.</param>
/// <param name="parentCode">The parent code.</param>
public static string GetRelativeCode(string code, string parentCode)
{
if (code.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(code), "code can not be null or empty.");
}
if (parentCode.IsNullOrEmpty())
{
return code;
}
if (code.Length == parentCode.Length)
{
return null;
}
return code.Substring(parentCode.Length + 1);
}
/// <summary>
/// Calculates next code for given code.
/// Example: if code = "00019.00055.00001" returns "00019.00055.00002".
/// </summary>
/// <param name="code">The code.</param>
public static string CalculateNextCode(string code)
{
if (code.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(code), "code can not be null or empty.");
}
var parentCode = GetParentCode(code);
var lastUnitCode = GetLastUnitCode(code);
return AppendCode(parentCode, CreateCode(Convert.ToInt32(lastUnitCode) + 1));
}
/// <summary>
/// Gets the last unit code.
/// Example: if code = "00019.00055.00001" returns "00001".
/// </summary>
/// <param name="code">The code.</param>
public static string GetLastUnitCode(string code)
{
if (code.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(code), "code can not be null or empty.");
}
var splittedCode = code.Split('.');
return splittedCode[splittedCode.Length - 1];
}
/// <summary>
/// Gets parent code.
/// Example: if code = "00019.00055.00001" returns "00019.00055".
/// </summary>
/// <param name="code">The code.</param>
public static string GetParentCode(string code)
{
if (code.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(code), "code can not be null or empty.");
}
var splittedCode = code.Split('.');
if (splittedCode.Length == 1)
{
return null;
}
return splittedCode.Take(splittedCode.Length - 1).JoinAsString(".");
}
public virtual void AddRole(Guid roleId)
{
Check.NotNull(roleId, nameof(roleId));
if (IsInRole(roleId))
{
return;
}
Roles.Add(new OrganizationUnitRole(roleId, Id, TenantId));
}
public virtual void RemoveRole(Guid roleId)
{
Check.NotNull(roleId, nameof(roleId));
if (!IsInRole(roleId))
{
return;
}
Roles.RemoveAll(r => r.RoleId == roleId);
}
public virtual bool IsInRole(Guid roleId)
{
Check.NotNull(roleId, nameof(roleId));
return Roles.Any(r => r.RoleId == roleId);
}
}
}

188
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs

@ -0,0 +1,188 @@
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Volo.Abp.Identity
{
/// <summary>
/// Performs domain logic for Organization Units.
/// </summary>
public class OrganizationUnitManager : DomainService
{
protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
protected IStringLocalizer<IdentityResource> Localizer { get; }
protected IIdentityRoleRepository IdentityRoleRepository { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public OrganizationUnitManager(
IOrganizationUnitRepository organizationUnitRepository,
IStringLocalizer<IdentityResource> localizer,
IIdentityRoleRepository identityRoleRepository,
ICancellationTokenProvider cancellationTokenProvider)
{
OrganizationUnitRepository = organizationUnitRepository;
Localizer = localizer;
IdentityRoleRepository = identityRoleRepository;
CancellationTokenProvider = cancellationTokenProvider;
}
[UnitOfWork]
public virtual async Task CreateAsync(OrganizationUnit organizationUnit)
{
organizationUnit.Code = await GetNextChildCodeAsync(organizationUnit.ParentId);
await ValidateOrganizationUnitAsync(organizationUnit);
await OrganizationUnitRepository.InsertAsync(organizationUnit);
}
public virtual async Task UpdateAsync(OrganizationUnit organizationUnit)
{
await ValidateOrganizationUnitAsync(organizationUnit);
await OrganizationUnitRepository.UpdateAsync(organizationUnit);
}
public virtual async Task<string> GetNextChildCodeAsync(Guid? parentId)
{
var lastChild = await GetLastChildOrNullAsync(parentId);
if (lastChild != null)
{
return OrganizationUnit.CalculateNextCode(lastChild.Code);
}
var parentCode = parentId != null
? await GetCodeOrDefaultAsync(parentId.Value)
: null;
return OrganizationUnit.AppendCode(
parentCode,
OrganizationUnit.CreateCode(1)
);
}
public virtual async Task<OrganizationUnit> GetLastChildOrNullAsync(Guid? parentId)
{
var children = await OrganizationUnitRepository.GetChildrenAsync(parentId);
return children.OrderBy(c => c.Code).LastOrDefault();
}
[UnitOfWork]
public virtual async Task DeleteAsync(Guid id)
{
var children = await FindChildrenAsync(id, true);
foreach (var child in children)
{
await OrganizationUnitRepository.DeleteAsync(child);
}
await OrganizationUnitRepository.DeleteAsync(id);
}
[UnitOfWork]
public virtual async Task MoveAsync(Guid id, Guid? parentId)
{
var organizationUnit = await OrganizationUnitRepository.GetAsync(id);
if (organizationUnit.ParentId == parentId)
{
return;
}
//Should find children before Code change
var children = await FindChildrenAsync(id, true);
//Store old code of OU
var oldCode = organizationUnit.Code;
//Move OU
organizationUnit.Code = await GetNextChildCodeAsync(parentId);
organizationUnit.ParentId = parentId;
await ValidateOrganizationUnitAsync(organizationUnit);
//Update Children Codes
foreach (var child in children)
{
child.Code = OrganizationUnit.AppendCode(organizationUnit.Code, OrganizationUnit.GetRelativeCode(child.Code, oldCode));
}
}
public virtual async Task<string> GetCodeOrDefaultAsync(Guid id)
{
var ou = await OrganizationUnitRepository.GetAsync(id);
return ou?.Code;
}
protected virtual async Task ValidateOrganizationUnitAsync(OrganizationUnit organizationUnit)
{
var siblings = (await FindChildrenAsync(organizationUnit.ParentId))
.Where(ou => ou.Id != organizationUnit.Id)
.ToList();
if (siblings.Any(ou => ou.DisplayName == organizationUnit.DisplayName))
{
throw new UserFriendlyException(Localizer["OrganizationUnitDuplicateDisplayNameWarning", organizationUnit.DisplayName]);
}
}
public async Task<List<OrganizationUnit>> FindChildrenAsync(Guid? parentId, bool recursive = false)
{
if (!recursive)
{
return await OrganizationUnitRepository.GetChildrenAsync(parentId);
}
if (!parentId.HasValue)
{
return await OrganizationUnitRepository.GetListAsync(includeDetails: true);
}
var code = await GetCodeOrDefaultAsync(parentId.Value);
return await OrganizationUnitRepository.GetAllChildrenWithParentCodeAsync(code, parentId);
}
public virtual Task<bool> IsInOrganizationUnitAsync(IdentityUser user, OrganizationUnit ou)
{
return Task.FromResult(user.IsInOrganizationUnit(ou.Id));
}
public virtual async Task AddRoleToOrganizationUnitAsync(Guid roleId, Guid ouId)
{
await AddRoleToOrganizationUnitAsync(
await IdentityRoleRepository.GetAsync(roleId),
await OrganizationUnitRepository.GetAsync(ouId, true)
);
}
public virtual Task AddRoleToOrganizationUnitAsync(IdentityRole role, OrganizationUnit ou)
{
var currentRoles = ou.Roles;
if (currentRoles.Any(r => r.OrganizationUnitId == ou.Id && r.RoleId == role.Id))
{
return Task.FromResult(0);
}
ou.AddRole(role.Id);
return Task.FromResult(0);
}
public virtual async Task RemoveRoleFromOrganizationUnitAsync(Guid roleId, Guid ouId)
{
await RemoveRoleFromOrganizationUnitAsync(
await IdentityRoleRepository.GetAsync(roleId),
await OrganizationUnitRepository.GetAsync(ouId, true)
);
}
public virtual Task RemoveRoleFromOrganizationUnitAsync(IdentityRole role, OrganizationUnit organizationUnit)
{
organizationUnit.RemoveRole(role.Id);
return Task.FromResult(0);
}
}
}

53
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitRole.cs

@ -0,0 +1,53 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity
{
/// <summary>
/// Represents membership of a User to an OU.
/// </summary>
public class OrganizationUnitRole : CreationAuditedEntity, IMultiTenant
{
/// <summary>
/// TenantId of this entity.
/// </summary>
public virtual Guid? TenantId { get; protected set; }
/// <summary>
/// Id of the Role.
/// </summary>
public virtual Guid RoleId { get; protected set; }
/// <summary>
/// Id of the <see cref="OrganizationUnit"/>.
/// </summary>
public virtual Guid OrganizationUnitId { get; protected set; }
/// <summary>
/// Initializes a new instance of the <see cref="OrganizationUnitRole"/> class.
/// </summary>
protected OrganizationUnitRole()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OrganizationUnitRole"/> class.
/// </summary>
/// <param name="tenantId">TenantId</param>
/// <param name="roleId">Id of the User.</param>
/// <param name="organizationUnitId">Id of the <see cref="OrganizationUnit"/>.</param>
public OrganizationUnitRole(Guid roleId, Guid organizationUnitId, Guid? tenantId = null)
{
RoleId = roleId;
OrganizationUnitId = organizationUnitId;
TenantId = tenantId;
}
public override object[] GetKeys()
{
return new object[] { OrganizationUnitId, RoleId };
}
}
}

2
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj

@ -15,8 +15,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj" />
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.EntityFrameworkCore\Volo.Abp.Users.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj" />
</ItemGroup>
</Project>

1
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs

@ -16,6 +16,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
options.AddRepository<IdentityUser, EfCoreIdentityUserRepository>();
options.AddRepository<IdentityRole, EfCoreIdentityRoleRepository>();
options.AddRepository<IdentityClaimType, EfCoreIdentityClaimTypeRepository>();
options.AddRepository<OrganizationUnit, EfCoreOrganizationUnitRepository>();
});
}
}

22
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs

@ -7,7 +7,6 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Guids;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
@ -19,7 +18,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<IdentityRole> FindByNormalizedNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
@ -29,10 +28,10 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<List<IdentityRole>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = true,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbSet
@ -40,6 +39,15 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityRole>> GetListAsync(
IEnumerable<Guid> ids,
CancellationToken cancellationToken = default)
{
return await DbSet
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityRole>> GetDefaultOnesAsync(
@ -51,6 +59,6 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
public override IQueryable<IdentityRole> WithDetails()
{
return GetQueryable().IncludeDetails();
}
}
}
}

89
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs

@ -6,6 +6,7 @@ using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
@ -39,6 +40,22 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
join role in DbContext.Roles on userRole.RoleId equals role.Id
where userRole.UserId == id
select role.Name;
var organizationUnitIds = DbContext.Set<IdentityUserOrganizationUnit>().Where(q => q.UserId == id).Select(q => q.OrganizationUnitId).ToArray();
var organizationRoleIds = DbContext.Set<OrganizationUnitRole>().Where(our => organizationUnitIds.Contains(our.OrganizationUnitId)).Select(r => r.RoleId).ToArray();
var orgUnitRoleNameQuery = DbContext.Roles.Where(r => organizationRoleIds.Contains(r.Id)).Select(n => n.Name);
var resultQuery = query.Union(orgUnitRoleNameQuery);
return await resultQuery.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<string>> GetRoleNamesInOrganizationUnitAsync(
Guid id,
CancellationToken cancellationToken = default)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join roleOu in DbContext.Set<OrganizationUnitRole>() on userOu.OrganizationUnitId equals roleOu.OrganizationUnitId
join userOuRoles in DbContext.Roles on roleOu.RoleId equals userOuRoles.Id
where userOu.UserId == id
select userOuRoles.Name;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
@ -129,7 +146,21 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
where userRole.UserId == id
select role;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
//TODO: Needs improvement
var userOrganizationsQuery = from userOrg in DbContext.Set<IdentityUserOrganizationUnit>()
join ou in DbContext.OrganizationUnits.IncludeDetails(includeDetails) on userOrg.OrganizationUnitId equals ou.Id
where userOrg.UserId == id
select ou;
var orgUserRoleQuery = DbContext.Set<OrganizationUnitRole>().Where(q => userOrganizationsQuery.Select(t => t.Id).Contains(q.OrganizationUnitId))
.Select(t => t.RoleId);
var orgRoles = DbContext.Roles.Where(q => orgUserRoleQuery.Contains(q.Id));
var resultQuery = query.Union(orgRoles);
return await resultQuery.ToListAsync(GetCancellationToken(cancellationToken));
//return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<long> GetCountAsync(
@ -147,9 +178,63 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
.LongCountAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(
Guid id,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var query = from userOU in DbContext.Set<IdentityUserOrganizationUnit>()
join ou in DbContext.OrganizationUnits.IncludeDetails(includeDetails) on userOU.OrganizationUnitId equals ou.Id
where userOU.UserId == id
select ou;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityUser>> GetUsersInOrganizationUnitAsync(
Guid organizationUnitId,
CancellationToken cancellationToken = default
)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbSet on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == organizationUnitId
select user;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityUser>> GetUsersInOrganizationsListAsync(
List<Guid> organizationUnitIds,
CancellationToken cancellationToken = default
)
{
//var userIds = DbContext.Set<IdentityUserOrganizationUnit>()
// .Where(q => organizationUnitIds.Contains(q.OrganizationUnitId))
// .Select(u => u.UserId);
//var query = DbContext.Users.Where(u => userIds.Contains(u.Id));
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbSet on userOu.UserId equals user.Id
where organizationUnitIds.Contains(userOu.OrganizationUnitId)
select user;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityUser>> GetUsersInOrganizationUnitWithChildrenAsync(
string code,
CancellationToken cancellationToken = default
)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbSet on userOu.UserId equals user.Id
join ou in DbContext.Set<OrganizationUnit>() on userOu.OrganizationUnitId equals ou.Id
where ou.Code.StartsWith(code)
select user;
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public override IQueryable<IdentityUser> WithDetails()
{
return GetQueryable().IncludeDetails();
}
}
}
}

160
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs

@ -0,0 +1,160 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq.Dynamic.Core;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Identity.EntityFrameworkCore
{
public class EfCoreOrganizationUnitRepository
: EfCoreRepository<IIdentityDbContext, OrganizationUnit, Guid>,
IOrganizationUnitRepository
{
public EfCoreOrganizationUnitRepository(
IDbContextProvider<IIdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<List<OrganizationUnit>> GetChildrenAsync(
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.Where(x => x.ParentId == parentId)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetAllChildrenWithParentCodeAsync(
string code,
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
IEnumerable<Guid> ids,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<OrganizationUnit> GetAsync(
string displayName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.FirstOrDefaultAsync(
ou => ou.DisplayName == displayName,
GetCancellationToken(cancellationToken)
);
}
public virtual async Task<List<IdentityRole>> GetRolesAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var query = from organizationRole in DbContext.Set<OrganizationUnitRole>()
join role in DbContext.Roles.IncludeDetails(includeDetails) on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
query = query
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.PageBy(skipCount, maxResultCount);
return await query.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<int> GetRolesCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default)
{
var query = from organizationRole in DbContext.Set<OrganizationUnitRole>()
join role in DbContext.Roles on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
return await query.CountAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityUser>> GetMembersAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default
)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbContext.Users.IncludeDetails(includeDetails) on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == organizationUnit.Id
select user;
if (!filter.IsNullOrWhiteSpace())
{
query = query.Where(u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
);
}
return await query.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbContext.Users on userOu.UserId equals user.Id
where userOu.OrganizationUnitId == organizationUnit.Id
select user;
return await query.CountAsync(GetCancellationToken(cancellationToken));
}
public override IQueryable<OrganizationUnit> WithDetails()
{
return GetQueryable().IncludeDetails();
}
}
}

4
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs

@ -12,5 +12,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
DbSet<IdentityRole> Roles { get; set; }
DbSet<IdentityClaimType> ClaimTypes { get; set; }
DbSet<OrganizationUnit> OrganizationUnits { get; set; }
}
}
}

2
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs

@ -16,6 +16,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
public DbSet<IdentityClaimType> ClaimTypes { get; set; }
public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
public IdentityDbContext(DbContextOptions<IdentityDbContext> options)
: base(options)
{

44
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs

@ -40,7 +40,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
b.HasMany(u => u.Tokens).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
b.HasMany(u => u.OrganizationUnits).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
b.HasIndex(u => u.NormalizedUserName);
b.HasIndex(u => u.NormalizedEmail);
b.HasIndex(u => u.UserName);
@ -146,6 +147,47 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
b.Property(uc => uc.Description).HasMaxLength(IdentityClaimTypeConsts.MaxDescriptionLength);
b.Property(uc => uc.ConcurrencyStamp).IsRequired().IsConcurrencyToken().HasMaxLength(IdentityClaimTypeConsts.MaxConcurrencyStampLength).HasColumnName(nameof(IdentityClaimType.ConcurrencyStamp));
});
builder.Entity<OrganizationUnit>(b =>
{
b.ToTable(options.TablePrefix + "OrganizationUnits", options.Schema);
b.ConfigureByConvention();
b.Property(ou => ou.Code).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxCodeLength).HasColumnName(nameof(OrganizationUnit.Code));
b.Property(ou => ou.DisplayName).IsRequired().HasMaxLength(OrganizationUnitConsts.MaxDisplayNameLength).HasColumnName(nameof(OrganizationUnit.DisplayName));
b.HasMany<OrganizationUnit>().WithOne().HasForeignKey(ou => ou.ParentId);
b.HasMany(ou => ou.Roles).WithOne().HasForeignKey(our => our.OrganizationUnitId).IsRequired();
b.HasIndex(ou => ou.Code);
});
builder.Entity<OrganizationUnitRole>(b =>
{
b.ToTable(options.TablePrefix + "OrganizationUnitRoles", options.Schema);
b.ConfigureByConvention();
b.HasKey(ou => new { ou.OrganizationUnitId, ou.RoleId });
b.HasOne<IdentityRole>().WithMany().HasForeignKey(ou => ou.RoleId).IsRequired();
b.HasIndex(ou => new { ou.RoleId, ou.OrganizationUnitId });
});
builder.Entity<IdentityUserOrganizationUnit>(b =>
{
b.ToTable(options.TablePrefix + "UserOrganizationUnits", options.Schema);
b.ConfigureByConvention();
b.HasKey(ou => new { ou.OrganizationUnitId, ou.UserId });
b.HasOne<OrganizationUnit>().WithMany().HasForeignKey(ou => ou.OrganizationUnitId).IsRequired();
b.HasIndex(ou => new { ou.UserId, ou.OrganizationUnitId });
});
}
}
}

14
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs

@ -16,7 +16,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
.Include(x => x.Roles)
.Include(x => x.Logins)
.Include(x => x.Claims)
.Include(x => x.Tokens);
.Include(x => x.Tokens)
.Include(x => x.OrganizationUnits);
}
public static IQueryable<IdentityRole> IncludeDetails(this IQueryable<IdentityRole> queryable, bool include = true)
@ -29,5 +30,16 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
return queryable
.Include(x => x.Claims);
}
public static IQueryable<OrganizationUnit> IncludeDetails(this IQueryable<OrganizationUnit> queryable, bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(x => x.Roles);
}
}
}

2
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs

@ -13,6 +13,8 @@ namespace Volo.Abp.Identity.MongoDB
public IMongoCollection<IdentityClaimType> ClaimTypes => Collection<IdentityClaimType>();
public IMongoCollection<OrganizationUnit> OrganizationUnits => Collection<OrganizationUnit>();
protected override void CreateModel(IMongoModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);

5
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs

@ -31,6 +31,11 @@ namespace Volo.Abp.Identity.MongoDB
{
b.CollectionName = options.CollectionPrefix + "ClaimTypes";
});
builder.Entity<OrganizationUnit>(b =>
{
b.CollectionName = options.CollectionPrefix + "OrganizationUnits";
});
}
}
}

1
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs

@ -17,6 +17,7 @@ namespace Volo.Abp.Identity.MongoDB
options.AddRepository<IdentityUser, MongoIdentityUserRepository>();
options.AddRepository<IdentityRole, MongoIdentityRoleRepository>();
options.AddRepository<IdentityClaimType, MongoIdentityRoleRepository>();
options.AddRepository<OrganizationUnit, MongoIdentityRoleRepository>();
});
}
}

2
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs

@ -12,5 +12,7 @@ namespace Volo.Abp.Identity.MongoDB
IMongoCollection<IdentityRole> Roles { get; }
IMongoCollection<IdentityClaimType> ClaimTypes { get; }
IMongoCollection<OrganizationUnit> OrganizationUnits { get; }
}
}

24
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs

@ -7,30 +7,29 @@ using System.Linq.Dynamic.Core;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
namespace Volo.Abp.Identity.MongoDB
{
public class MongoIdentityRoleRepository : MongoDbRepository<IAbpIdentityMongoDbContext, IdentityRole, Guid>, IIdentityRoleRepository
{
public MongoIdentityRoleRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
public MongoIdentityRoleRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<IdentityRole> FindByNormalizedNameAsync(
string normalizedRoleName,
string normalizedRoleName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityRole>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
public virtual async Task<List<IdentityRole>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
@ -41,10 +40,19 @@ namespace Volo.Abp.Identity.MongoDB
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityRole>> GetListAsync(
IEnumerable<Guid> ids,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<IdentityRole>> GetDefaultOnesAsync(
bool includeDetails = false, CancellationToken cancellationToken = default)
{
return await GetMongoQueryable().Where(r => r.IsDefault).ToListAsync(cancellationToken: GetCancellationToken(cancellationToken));
}
}
}
}

98
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs

@ -8,7 +8,6 @@ using System.Threading.Tasks;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.Guids;
using Volo.Abp.MongoDB;
namespace Volo.Abp.Identity.MongoDB
@ -37,8 +36,41 @@ namespace Volo.Abp.Identity.MongoDB
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
var organizationUnitIds = user.OrganizationUnits
.Select(r => r.OrganizationUnitId)
.ToArray();
var organizationUnits = DbContext.OrganizationUnits
.AsQueryable()
.Where(ou => organizationUnitIds.Contains(ou.Id))
.ToArray();
var orgUnitRoleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray();
var roleIds = user.Roles.Select(r => r.RoleId).ToArray();
return await DbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id)).Select(r => r.Name).ToListAsync(GetCancellationToken(cancellationToken));
var allRoleIds = orgUnitRoleIds.Union(roleIds);
return await DbContext.Roles.AsQueryable().Where(r => allRoleIds.Contains(r.Id)).Select(r => r.Name).ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<string>> GetRoleNamesInOrganizationUnitAsync(
Guid id,
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
var organizationUnitIds = user.OrganizationUnits
.Select(r => r.OrganizationUnitId)
.ToArray();
var organizationUnits = DbContext.OrganizationUnits
.AsQueryable()
.Where(ou => organizationUnitIds.Contains(ou.Id))
.ToArray();
var roleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray();
return await DbContext.Roles //TODO: Such usage suppress filters!
.AsQueryable()
.Where(r => roleIds.Contains(r.Id))
.Select(r => r.Name)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<IdentityUser> FindByLoginAsync(
@ -116,8 +148,30 @@ namespace Volo.Abp.Identity.MongoDB
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
var organizationUnitIds = user.OrganizationUnits
.Select(r => r.OrganizationUnitId)
.ToArray();
var organizationUnits = DbContext.OrganizationUnits
.AsQueryable()
.Where(ou => organizationUnitIds.Contains(ou.Id))
.ToArray();
var orgUnitRoleIds = organizationUnits.SelectMany(x => x.Roles.Select(r => r.RoleId)).ToArray();
var roleIds = user.Roles.Select(r => r.RoleId).ToArray();
return await DbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id)).ToListAsync(GetCancellationToken(cancellationToken));
var allRoleIds = orgUnitRoleIds.Union(roleIds);
return await DbContext.Roles.AsQueryable().Where(r => allRoleIds.Contains(r.Id)).ToListAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<OrganizationUnit>> GetOrganizationUnitsAsync(
Guid id,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken));
var organizationUnitIds = user.OrganizationUnits.Select(r => r.OrganizationUnitId);
return await DbContext.OrganizationUnits.AsQueryable()
.Where(ou => organizationUnitIds.Contains(ou.Id))
.ToListAsync(GetCancellationToken(cancellationToken))
;
}
public virtual async Task<long> GetCountAsync(
@ -135,5 +189,43 @@ namespace Volo.Abp.Identity.MongoDB
)
.LongCountAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityUser>> GetUsersInOrganizationUnitAsync(
Guid organizationUnitId,
CancellationToken cancellationToken = default)
{
var result = await GetMongoQueryable()
.Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnitId))
.ToListAsync(GetCancellationToken(cancellationToken))
;
return result;
}
public async Task<List<IdentityUser>> GetUsersInOrganizationsListAsync(
List<Guid> organizationUnitIds,
CancellationToken cancellationToken = default)
{
var result = await GetMongoQueryable()
.Where(u => u.OrganizationUnits.Any(uou => organizationUnitIds.Contains(uou.OrganizationUnitId)))
.ToListAsync(GetCancellationToken(cancellationToken))
;
return result;
}
public async Task<List<IdentityUser>> GetUsersInOrganizationUnitWithChildrenAsync(
string code,
CancellationToken cancellationToken = default)
{
var organizationUnitIds = await DbContext.OrganizationUnits.AsQueryable()
.Where(ou => ou.Code.StartsWith(code))
.Select(ou => ou.Id)
.ToListAsync(GetCancellationToken(cancellationToken))
;
return await GetMongoQueryable()
.Where(u => u.OrganizationUnits.Any(uou => organizationUnitIds.Contains(uou.OrganizationUnitId)))
.ToListAsync(GetCancellationToken(cancellationToken))
;
}
}
}

138
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs

@ -0,0 +1,138 @@
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.MongoDB;
namespace Volo.Abp.Identity.MongoDB
{
public class MongoOrganizationUnitRepository : MongoDbRepository<IAbpIdentityMongoDbContext, OrganizationUnit, Guid>, IOrganizationUnitRepository
{
public MongoOrganizationUnitRepository(
IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public virtual async Task<List<OrganizationUnit>> GetChildrenAsync(
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(ou => ou.ParentId == parentId)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetAllChildrenWithParentCodeAsync(
string code,
Guid? parentId,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
IEnumerable<Guid> ids,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.As<IMongoQueryable<OrganizationUnit>>()
.PageBy<OrganizationUnit, IMongoQueryable<OrganizationUnit>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<OrganizationUnit> GetAsync(
string displayName,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.FirstOrDefaultAsync(
ou => ou.DisplayName == displayName,
GetCancellationToken(cancellationToken)
);
}
public virtual async Task<List<IdentityRole>> GetRolesAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray();
return await DbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id))
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.As<IMongoQueryable<IdentityRole>>()
.PageBy<IdentityRole, IMongoQueryable<IdentityRole>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
}
public virtual async Task<int> GetRolesCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default)
{
var roleIds = organizationUnit.Roles.Select(r => r.RoleId).ToArray();
return await DbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id))
.As<IMongoQueryable<IdentityRole>>()
.CountAsync(cancellationToken);
}
public virtual async Task<List<IdentityUser>> GetMembersAsync(
OrganizationUnit organizationUnit,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await DbContext.Users.AsQueryable()
.Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id))
.WhereIf<IdentityUser, IMongoQueryable<IdentityUser>>(
!filter.IsNullOrWhiteSpace(),
u =>
u.UserName.Contains(filter) ||
u.Email.Contains(filter)
)
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.As<IMongoQueryable<IdentityUser>>()
.PageBy<IdentityUser, IMongoQueryable<IdentityUser>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<int> GetMembersCountAsync(
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default)
{
return await DbContext.Users.AsQueryable()
.Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id))
.As<IMongoQueryable<IdentityUser>>()
.CountAsync(GetCancellationToken(cancellationToken));
}
}
}

6
modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs

@ -189,9 +189,10 @@ namespace Volo.Abp.Identity
//Assert
result.Items.Count.ShouldBe(2);
result.Items.Count.ShouldBe(3);
result.Items.ShouldContain(r => r.Name == "moderator");
result.Items.ShouldContain(r => r.Name == "supporter");
result.Items.ShouldContain(r => r.Name == "manager");
}
[Fact]
@ -214,9 +215,10 @@ namespace Volo.Abp.Identity
//Assert
var roleNames = await _userRepository.GetRoleNamesAsync(johnNash.Id);
roleNames.Count.ShouldBe(2);
roleNames.Count.ShouldBe(3);
roleNames.ShouldContain("admin");
roleNames.ShouldContain("moderator");
roleNames.ShouldContain("manager");
}
private static string CreateRandomEmail()

62
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Shouldly;
using Volo.Abp.Castle.DynamicProxy;
using Volo.Abp.Uow;
using Xunit;
@ -16,6 +14,7 @@ namespace Volo.Abp.Identity
private readonly IdentityUserManager _identityUserManager;
private readonly IIdentityUserRepository _identityUserRepository;
private readonly IIdentityRoleRepository _identityRoleRepository;
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IdentityTestData _testData;
@ -25,6 +24,7 @@ namespace Volo.Abp.Identity
_identityUserManager = GetRequiredService<IdentityUserManager>();
_identityUserRepository = GetRequiredService<IIdentityUserRepository>();
_identityRoleRepository = GetRequiredService<IIdentityRoleRepository>();
_organizationUnitRepository = GetRequiredService<IOrganizationUnitRepository>();
_lookupNormalizer = GetRequiredService<ILookupNormalizer>();
_testData = GetRequiredService<IdentityTestData>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
@ -88,6 +88,35 @@ namespace Volo.Abp.Identity
}
}
[Fact]
public async Task SetOrganizationUnitsAsync()
{
using (var uow = _unitOfWorkManager.Begin())
{
var user = await _identityUserRepository.FindByNormalizedUserNameAsync(
_lookupNormalizer.NormalizeName("david"));
user.ShouldNotBeNull();
var ou = await _organizationUnitRepository.GetAsync(
_lookupNormalizer.NormalizeName("OU11"));
ou.ShouldNotBeNull();
await _identityUserManager.SetOrganizationUnitsAsync(user, new Guid[]
{
ou.Id
});
user = await _identityUserRepository.FindByNormalizedUserNameAsync(
_lookupNormalizer.NormalizeName("david"));
user.OrganizationUnits.Count.ShouldBeGreaterThan(0);
user.OrganizationUnits.FirstOrDefault(uou => uou.OrganizationUnitId == ou.Id).ShouldNotBeNull();
await uow.CompleteAsync();
}
}
[Fact]
public async Task AddDefaultRolesAsync_In_Same_Uow()
{
@ -111,6 +140,35 @@ namespace Volo.Abp.Identity
role.IsDefault.ShouldBe(true);
}
await uow.CompleteAsync();
}
}
[Fact]
public async Task SetOrganizationUnits_Should_Remove()
{
using (var uow = _unitOfWorkManager.Begin())
{
var ou = await _organizationUnitRepository.GetAsync(
_lookupNormalizer.NormalizeName("OU111"));
ou.ShouldNotBeNull();
var user = await _identityUserRepository.FindByNormalizedUserNameAsync(
_lookupNormalizer.NormalizeName("john.nash"));
user.ShouldNotBeNull();
var ouNew = await _organizationUnitRepository.GetAsync(
_lookupNormalizer.NormalizeName("OU2"));
ouNew.ShouldNotBeNull();
await _identityUserManager.SetOrganizationUnitsAsync(user, new Guid[]
{
ouNew.Id
});
user.OrganizationUnits.ShouldNotContain(x => x.OrganizationUnitId == ou.Id);
await uow.CompleteAsync();
}
}

121
modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs

@ -0,0 +1,121 @@
using Microsoft.AspNetCore.Identity;
using Shouldly;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity
{
public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase
{
private readonly OrganizationUnitManager _organizationUnitManager;
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly IdentityTestData _testData;
private readonly IIdentityRoleRepository _identityRoleRepository;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IGuidGenerator _guidGenerator;
public OrganizationUnitManager_Tests()
{
_organizationUnitManager = GetRequiredService<OrganizationUnitManager>();
_organizationUnitRepository = GetRequiredService<IOrganizationUnitRepository>();
_identityRoleRepository = GetRequiredService<IIdentityRoleRepository>();
_lookupNormalizer = GetRequiredService<ILookupNormalizer>();
_testData = GetRequiredService<IdentityTestData>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
_guidGenerator = GetService<IGuidGenerator>();
}
[Fact]
public async Task CreateAsnyc()
{
await _organizationUnitManager.CreateAsync(new OrganizationUnit(_guidGenerator.Create(), "Root 1"));
var root1 = await _organizationUnitRepository.GetAsync("Root 1");
root1.ShouldNotBeNull();
}
[Fact]
public async Task UpdateAsync()
{
var ou = await _organizationUnitRepository.GetAsync("OU111");
ou.DisplayName = "OU111 Updated";
await _organizationUnitManager.UpdateAsync(ou);
var ouAfterChange = await _organizationUnitRepository.GetAsync("OU111 Updated");
ouAfterChange.DisplayName.ShouldContain("OU111 Updated");
}
[Fact]
public async Task DeleteAsync()
{
var ou = await _organizationUnitRepository.GetAsync("OU11");
await _organizationUnitManager.DeleteAsync(ou.Id);
(await _organizationUnitRepository.GetAsync("OU11")).ShouldBeNull();
}
[Fact]
public async Task MoveAsync()
{
var ou1 = await _organizationUnitRepository.GetAsync("OU1");
var ou2 = await _organizationUnitRepository.GetAsync("OU2");
await _organizationUnitManager.MoveAsync(ou1.Id, ou2.Id);
ou1 = await _organizationUnitRepository.GetAsync("OU1");
ou1.ParentId.ShouldBe(ou2.Id);
ou1.Code.ShouldBe(OrganizationUnit.CreateCode(2, 2));
var ou11 = await _organizationUnitRepository.GetAsync("OU11");
ou11.ParentId.ShouldBe(ou1.Id);
ou11.Code.ShouldBe(OrganizationUnit.CreateCode(2, 2, 1));
var ou111 = await _organizationUnitRepository.GetAsync("OU111");
ou111.ParentId.ShouldBe(ou11.Id);
ou111.Code.ShouldBe(OrganizationUnit.CreateCode(2, 2, 1, 1));
var ou112 = await _organizationUnitRepository.GetAsync("OU112");
ou112.ParentId.ShouldBe(ou11.Id);
ou112.Code.ShouldBe(OrganizationUnit.CreateCode(2, 2, 1, 2));
var ou12 = await _organizationUnitRepository.GetAsync("OU12");
ou12.ParentId.ShouldBe(ou1.Id);
ou12.Code.ShouldBe(OrganizationUnit.CreateCode(2, 2, 2));
}
[Fact]
public async Task AddRoleToOrganizationUnitAsync()
{
OrganizationUnit ou = null;
IdentityRole adminRole = null;
using (var uow = _unitOfWorkManager.Begin())
{
ou = await _organizationUnitRepository.GetAsync("OU1", true);
adminRole = await _identityRoleRepository.FindByNormalizedNameAsync(_lookupNormalizer.NormalizeName("admin"));
await _organizationUnitManager.AddRoleToOrganizationUnitAsync(adminRole, ou);
await _organizationUnitRepository.UpdateAsync(ou);
await uow.CompleteAsync();
}
ou = await _organizationUnitRepository.GetAsync("OU1", includeDetails: true);
ou.Roles.First().RoleId.ShouldBe(adminRole.Id);
}
[Fact]
public async Task RemoveRoleFromOrganizationUnitAsync()
{
var ou = await _organizationUnitRepository.GetAsync("OU1", true);
var adminRole = await _identityRoleRepository.FindByNormalizedNameAsync(_lookupNormalizer.NormalizeName("admin"));
await _organizationUnitManager.AddRoleToOrganizationUnitAsync(adminRole.Id, ou.Id);
await _organizationUnitManager.RemoveRoleFromOrganizationUnitAsync(adminRole.Id, ou.Id);
ou = await _organizationUnitRepository.GetAsync("OU1", includeDetails: true);
ou.Roles.FirstOrDefault(r => r.RoleId == adminRole.Id).ShouldBeNull();
}
}
}

6
modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/OrganizationUnitRepository_Tests.cs

@ -0,0 +1,6 @@
namespace Volo.Abp.Identity.EntityFrameworkCore
{
public class OrganizationUnitRepository_Tests : OrganizationUnitRepository_Tests<AbpIdentityEntityFrameworkCoreTestModule>
{
}
}

10
modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Volo.Abp.Identity.MongoDB
{
public class OrganizationUnitRepository_Tests : OrganizationUnitRepository_Tests<AbpIdentityMongoDbTestModule>
{
}
}

68
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs

@ -1,4 +1,5 @@
using System.Security.Claims;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.DependencyInjection;
@ -12,20 +13,27 @@ namespace Volo.Abp.Identity
private readonly IIdentityUserRepository _userRepository;
private readonly IIdentityClaimTypeRepository _identityClaimTypeRepository;
private readonly IIdentityRoleRepository _roleRepository;
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IdentityTestData _testData;
private readonly OrganizationUnitManager _organizationUnitManager;
private IdentityRole _adminRole;
private IdentityRole _moderator;
private IdentityRole _moderatorRole;
private IdentityRole _supporterRole;
private IdentityRole _managerRole;
private OrganizationUnit _ou111;
private OrganizationUnit _ou112;
public AbpIdentityTestDataBuilder(
IGuidGenerator guidGenerator,
IIdentityUserRepository userRepository,
IIdentityClaimTypeRepository identityClaimTypeRepository,
IIdentityRoleRepository roleRepository,
IOrganizationUnitRepository organizationUnitRepository,
ILookupNormalizer lookupNormalizer,
IdentityTestData testData)
IdentityTestData testData,
OrganizationUnitManager organizationUnitManager)
{
_guidGenerator = guidGenerator;
_userRepository = userRepository;
@ -33,11 +41,14 @@ namespace Volo.Abp.Identity
_roleRepository = roleRepository;
_lookupNormalizer = lookupNormalizer;
_testData = testData;
_organizationUnitRepository = organizationUnitRepository;
_organizationUnitManager = organizationUnitManager;
}
public async Task Build()
{
await AddRoles();
await AddOrganizationUnits();
await AddUsers();
await AddClaimTypes();
}
@ -46,12 +57,41 @@ namespace Volo.Abp.Identity
{
_adminRole = await _roleRepository.FindByNormalizedNameAsync(_lookupNormalizer.NormalizeName("admin"));
_moderator = new IdentityRole(_testData.RoleModeratorId, "moderator");
_moderator.AddClaim(_guidGenerator, new Claim("test-claim", "test-value"));
await _roleRepository.InsertAsync(_moderator);
_moderatorRole = new IdentityRole(_testData.RoleModeratorId, "moderator");
_moderatorRole.AddClaim(_guidGenerator, new Claim("test-claim", "test-value"));
await _roleRepository.InsertAsync(_moderatorRole);
_supporterRole = new IdentityRole(_guidGenerator.Create(), "supporter");
await _roleRepository.InsertAsync(_supporterRole);
_managerRole = new IdentityRole(_guidGenerator.Create(), "manager");
await _roleRepository.InsertAsync(_managerRole);
}
/* Creates OU tree as shown below:
*
* - OU1
* - OU11
* - OU111
* - OU112
* - OU12
* - OU2
* - OU21
*/
private async Task AddOrganizationUnits()
{
var ou1 = await CreateOU("OU1", OrganizationUnit.CreateCode(1));
var ou11 = await CreateOU("OU11", OrganizationUnit.CreateCode(1, 1), ou1.Id);
_ou112 = await CreateOU("OU112", OrganizationUnit.CreateCode(1, 1, 2), ou11.Id);
var ou12 = await CreateOU("OU12", OrganizationUnit.CreateCode(1, 2), ou1.Id);
var ou2 = await CreateOU("OU2", OrganizationUnit.CreateCode(2));
var ou21 = await CreateOU("OU21", OrganizationUnit.CreateCode(2, 1), ou2.Id);
_ou111 = new OrganizationUnit(_guidGenerator.Create(), "OU111", ou11.Id);
_ou111.Code = OrganizationUnit.CreateCode(1, 1, 1);
_ou111.AddRole(_moderatorRole.Id);
_ou111.AddRole(_managerRole.Id);
await _organizationUnitRepository.InsertAsync(_ou111);
}
private async Task AddUsers()
@ -62,8 +102,10 @@ namespace Volo.Abp.Identity
await _userRepository.InsertAsync(adminUser);
var john = new IdentityUser(_testData.UserJohnId, "john.nash", "john.nash@abp.io");
john.AddRole(_moderator.Id);
john.AddRole(_moderatorRole.Id);
john.AddRole(_supporterRole.Id);
john.AddOrganizationUnit(_ou111.Id);
john.AddOrganizationUnit(_ou112.Id);
john.AddLogin(new UserLoginInfo("github", "john", "John Nash"));
john.AddLogin(new UserLoginInfo("twitter", "johnx", "John Nash"));
john.AddClaim(_guidGenerator, new Claim("TestClaimType", "42"));
@ -71,20 +113,30 @@ namespace Volo.Abp.Identity
await _userRepository.InsertAsync(john);
var david = new IdentityUser(_testData.UserDavidId, "david", "david@abp.io");
david.AddOrganizationUnit(_ou112.Id);
await _userRepository.InsertAsync(david);
var neo = new IdentityUser(_testData.UserNeoId, "neo", "neo@abp.io");
neo.AddRole(_supporterRole.Id);
neo.AddClaim(_guidGenerator, new Claim("TestClaimType", "43"));
neo.AddOrganizationUnit(_ou111.Id);
await _userRepository.InsertAsync(neo);
}
private async Task AddClaimTypes()
{
var ageClaim = new IdentityClaimType(_testData.AgeClaimId, "Age", false, false, null, null, null,IdentityClaimValueType.Int);
var ageClaim = new IdentityClaimType(_testData.AgeClaimId, "Age", false, false, null, null, null, IdentityClaimValueType.Int);
await _identityClaimTypeRepository.InsertAsync(ageClaim);
var educationClaim = new IdentityClaimType(_testData.EducationClaimId, "Education", true, false, null, null, null);
await _identityClaimTypeRepository.InsertAsync(educationClaim);
}
private async Task<OrganizationUnit> CreateOU(string displayName, string code, Guid? parentId = null)
{
var ou = await _organizationUnitRepository.InsertAsync(new OrganizationUnit(_guidGenerator.Create(), displayName, parentId) { Code = code });
return ou;
}
}
}

34
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs

@ -15,11 +15,13 @@ namespace Volo.Abp.Identity
{
protected IIdentityUserRepository UserRepository { get; }
protected ILookupNormalizer LookupNormalizer { get; }
protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
protected IdentityUserRepository_Tests()
{
UserRepository = ServiceProvider.GetRequiredService<IIdentityUserRepository>();
LookupNormalizer = ServiceProvider.GetRequiredService<ILookupNormalizer>();
OrganizationUnitRepository = ServiceProvider.GetRequiredService<IOrganizationUnitRepository>();
}
[Fact]
@ -42,9 +44,10 @@ namespace Volo.Abp.Identity
{
var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.NormalizeName("john.nash"));
var roles = await UserRepository.GetRoleNamesAsync(john.Id);
roles.Count.ShouldBe(2);
roles.Count.ShouldBe(3);
roles.ShouldContain("moderator");
roles.ShouldContain("supporter");
roles.ShouldContain("manager");
}
[Fact]
@ -116,9 +119,10 @@ namespace Volo.Abp.Identity
{
var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.NormalizeName("john.nash"));
var roles = await UserRepository.GetRolesAsync(john.Id);
roles.Count.ShouldBe(2);
roles.Count.ShouldBe(3);
roles.ShouldContain(r => r.Name == "moderator");
roles.ShouldContain(r => r.Name == "supporter");
roles.ShouldContain(r => r.Name == "manager");
}
[Fact]
@ -128,6 +132,22 @@ namespace Volo.Abp.Identity
(await UserRepository.GetCountAsync("undefined-username")).ShouldBe(0);
}
[Fact]
public async Task GetUsersInOrganizationUnitAsync()
{
var users = await UserRepository.GetUsersInOrganizationUnitAsync((await GetOU("OU111")).Id);
users.ShouldNotBeNull();
users.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task GetUsersInOrganizationUnitWithChildrenAsync()
{
var users = await UserRepository.GetUsersInOrganizationUnitWithChildrenAsync((await GetOU("OU111")).Code);
users.ShouldNotBeNull();
users.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task Should_Eager_Load_User_Collections()
{
@ -144,6 +164,16 @@ namespace Volo.Abp.Identity
john.Tokens.ShouldNotBeNull();
john.Tokens.Any().ShouldBeTrue();
john.OrganizationUnits.ShouldNotBeNull();
john.OrganizationUnits.Any().ShouldBeTrue();
}
private async Task<OrganizationUnit> GetOU(string diplayName)
{
var organizationUnit = await OrganizationUnitRepository.GetAsync(diplayName);
organizationUnit.ShouldNotBeNull();
return organizationUnit;
}
}
}

2
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs

@ -20,6 +20,8 @@ namespace Volo.Abp.Identity
ServiceProvider.GetService<IRepository<IdentityRole>>().ShouldNotBeNull();
ServiceProvider.GetService<IRepository<IdentityRole, Guid>>().ShouldNotBeNull();
ServiceProvider.GetService<IIdentityRoleRepository>().ShouldNotBeNull();
ServiceProvider.GetService<IOrganizationUnitRepository>().ShouldNotBeNull();
}
}
}

18
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs

@ -14,6 +14,7 @@ namespace Volo.Abp.Identity
{
protected IIdentityUserRepository UserRepository { get; }
protected IIdentityRoleRepository RoleRepository { get; }
protected IOrganizationUnitRepository OrganizationUnitRepository { get; }
protected ILookupNormalizer LookupNormalizer { get; }
protected LazyLoading_Tests()
@ -21,6 +22,7 @@ namespace Volo.Abp.Identity
UserRepository = ServiceProvider.GetRequiredService<IIdentityUserRepository>();
RoleRepository = ServiceProvider.GetRequiredService<IIdentityRoleRepository>();
LookupNormalizer = ServiceProvider.GetRequiredService<ILookupNormalizer>();
OrganizationUnitRepository = ServiceProvider.GetRequiredService<IOrganizationUnitRepository>();
}
[Fact]
@ -55,6 +57,22 @@ namespace Volo.Abp.Identity
john.Tokens.ShouldNotBeNull();
john.Tokens.Any().ShouldBeTrue();
john.OrganizationUnits.ShouldNotBeNull();
john.OrganizationUnits.Any().ShouldBeTrue();
await uow.CompleteAsync();
}
}
[Fact]
public async Task Should_Lazy_Load_OrganizationUnit_Collections()
{
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin())
{
var ou = await OrganizationUnitRepository.GetAsync(LookupNormalizer.NormalizeName("OU111"), includeDetails: false);
ou.Roles.ShouldNotBeNull(); //?
ou.Roles.Any().ShouldBeTrue();
await uow.CompleteAsync();
}
}

155
modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/OrganizationUnitRepository_Tests.cs

@ -0,0 +1,155 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.Identity
{
public abstract class OrganizationUnitRepository_Tests<TStartupModule> : AbpIdentityTestBase<TStartupModule>
where TStartupModule : IAbpModule
{
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly ILookupNormalizer _lookupNormalizer;
private readonly IdentityTestData _testData;
private readonly IGuidGenerator _guidGenerator;
private readonly OrganizationUnitManager _organizationUnitManager;
private readonly IIdentityRoleRepository _identityRoleRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IIdentityUserRepository _identityUserRepository;
public OrganizationUnitRepository_Tests()
{
_organizationUnitRepository = ServiceProvider.GetRequiredService<IOrganizationUnitRepository>();
_lookupNormalizer = ServiceProvider.GetRequiredService<ILookupNormalizer>();
_testData = GetRequiredService<IdentityTestData>();
_guidGenerator = GetRequiredService<IGuidGenerator>();
_organizationUnitManager = GetRequiredService<OrganizationUnitManager>();
_identityRoleRepository = GetRequiredService<IIdentityRoleRepository>();
_unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
_identityUserRepository = GetRequiredService<IIdentityUserRepository>();
}
[Fact]
public async Task GetChildrenAsync()
{
(await _organizationUnitRepository.GetChildrenAsync(_testData.RoleModeratorId)).ShouldNotBeNull();
}
[Fact]
public async Task GetAllChildrenWithParentCodeAsync()
{
(await _organizationUnitRepository.GetAllChildrenWithParentCodeAsync(OrganizationUnit.CreateCode(0), _guidGenerator.Create())).ShouldNotBeNull();
}
[Fact]
public async Task GetListAsync()
{
var ouIds = (await _organizationUnitRepository.GetListAsync(includeDetails: true))
.Select(ou => ou.Id).Take(2);
var ous = await _organizationUnitRepository.GetListAsync(ouIds);
ous.Count.ShouldBe(2);
ous.ShouldContain(ou => ou.Id == ouIds.First());
}
[Fact]
public async Task GetOrganizationUnitAsync()
{
var organizationUnit = await _organizationUnitRepository.GetAsync("OU111");
organizationUnit.ShouldNotBeNull();
}
[Fact]
public async Task GetCountAsync()
{
(await _organizationUnitRepository.GetCountAsync()).ShouldBeGreaterThan(0);
}
[Fact]
public async Task Should_Eager_Load_OrganizationUnit_Collections()
{
var ou = (await _organizationUnitRepository.GetListAsync(includeDetails: true))
.FirstOrDefault(ou => ou.DisplayName == "OU111");
ou.Roles.ShouldNotBeNull();
ou.Roles.Any().ShouldBeTrue();
}
[Fact]
public async Task GetOrganizationUnitRolesAsync()
{
OrganizationUnit ou = await _organizationUnitRepository.GetAsync("OU111", includeDetails: true);
var ou111Roles = await _organizationUnitRepository.GetRolesAsync(ou, includeDetails: true);
ou111Roles.Count.ShouldBe(2);
ou111Roles.ShouldContain(n => n.Name == "manager");
ou111Roles.ShouldContain(n => n.Name == "moderator");
}
[Fact]
public async Task GetOrganizationUnitRolesWithPagingAsync()
{
OrganizationUnit ou = await _organizationUnitRepository.GetAsync("OU111", includeDetails: true);
var ou111Roles = await _organizationUnitRepository.GetRolesAsync(ou, sorting: "name desc", maxResultCount: 1, includeDetails: true);
ou111Roles.Count.ShouldBe(1);
ou111Roles.ShouldContain(n => n.Name == "moderator");
}
[Fact]
public async Task GetMembersInOrganizationUnitListAsync()
{
OrganizationUnit ou1 = await _organizationUnitRepository.GetAsync("OU111", true);
OrganizationUnit ou2 = await _organizationUnitRepository.GetAsync("OU112", true);
var users = await _identityUserRepository.GetUsersInOrganizationsListAsync(new List<Guid> { ou1.Id, ou2.Id });
users.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task GetMembersInOrganizationUnitWithParamsAsync()
{
OrganizationUnit ou = await _organizationUnitRepository.GetAsync("OU111", true);
var users = await _organizationUnitRepository.GetMembersAsync(ou, "UserName DESC", 5, 0, "n");
users.Count.ShouldBeGreaterThan(1);
users.Count.ShouldBeLessThanOrEqualTo(5);
//Filter check
users.ShouldAllBe(u => u.UserName.Contains("ne") || u.Email.Contains("n"));
//Order check
for (var i = 0; i < users.Count - 1; i++)
{
string.Compare(
users[i].UserName,
users[i + 1].UserName,
StringComparison.OrdinalIgnoreCase
).ShouldBeGreaterThan(0);
}
users = await _organizationUnitRepository.GetMembersAsync(ou, null, 999, 0, "undefined-username");
users.Count.ShouldBe(0);
}
[Fact]
public async Task GetMembersCountOfOrganizationUnit()
{
OrganizationUnit ou = await _organizationUnitRepository.GetAsync("OU111", true);
var usersCount = await _organizationUnitRepository.GetMembersCountAsync(ou);
usersCount.ShouldBeGreaterThan(1);
}
[Fact]
public async Task GetRolesCountOfOrganizationUnit()
{
OrganizationUnit ou = await _organizationUnitRepository.GetAsync("OU111", true);
var rolesCount = await _organizationUnitRepository.GetRolesCountAsync(ou);
rolesCount.ShouldBeGreaterThan(1);
}
}
}
Loading…
Cancel
Save