From fbe0ad3f49f92be3113c6c63430ceafb123017e2 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Wed, 20 Jun 2018 09:04:52 +0300 Subject: [PATCH] Added identity & account modules. --- modules/account/README.md | 2 + modules/account/Volo.Abp.Account.sln | 37 + modules/account/common.props | 16 + .../Volo.Abp.Account.Web/AbpAccountOptions.cs | 16 + .../AbpAccountUserMenuContributor.cs | 30 + .../AbpAccountWebModule.cs | 57 + .../Account/Controllers/LogoutController.cs | 26 + .../Localization/AccountResource.cs | 10 + .../Resources/AbpAccount/Web/en.json | 15 + .../Resources/AbpAccount/Web/tr.json | 15 + .../Pages/Account/AccountPage.cs | 13 + .../Pages/Account/AccountPageModel.cs | 75 ++ .../Pages/Account/Login.cshtml | 36 + .../Pages/Account/Login.cshtml.cs | 163 +++ .../Pages/Account/Register.cshtml | 14 + .../Pages/Account/Register.cshtml.cs | 81 ++ .../Pages/Account/SendSecurityCode.cshtml | 6 + .../Pages/Account/SendSecurityCode.cshtml.cs | 45 + .../Pages/Account/_ViewImports.cshtml | 2 + .../Properties/launchSettings.json | 27 + .../AccountSettingDefinitionProvider.cs | 14 + .../Settings/AccountSettingNames.cs | 7 + .../Volo.Abp.Account.Web.csproj | 29 + modules/identity/README.md | 2 + modules/identity/Volo.Abp.Identity.sln | 123 ++ modules/identity/common.props | 16 + .../Properties/AssemblyInfo.cs | 18 + ....Abp.Identity.Application.Contracts.csproj | 29 + .../AbpIdentityApplicationContractsModule.cs | 39 + .../Abp/Identity/GetIdentityRolesInput.cs | 9 + .../Abp/Identity/GetIdentityUsersInput.cs | 9 + .../Abp/Identity/IIdentityRoleAppService.cs | 18 + .../Abp/Identity/IIdentityUserAppService.cs | 19 + .../IdentityPermissionDefinitionProvider.cs | 31 + .../Volo/Abp/Identity/IdentityPermissions.cs | 43 + .../Abp/Identity/IdentityRoleCreateDto.cs | 7 + .../IdentityRoleCreateOrUpdateDtoBase.cs | 11 + .../Volo/Abp/Identity/IdentityRoleDto.cs | 10 + .../Abp/Identity/IdentityRoleUpdateDto.cs | 7 + .../Abp/Identity/IdentityUserCreateDto.cs | 11 + .../IdentityUserCreateOrUpdateDtoBase.cs | 27 + .../Volo/Abp/Identity/IdentityUserDto.cs | 27 + .../Abp/Identity/IdentityUserUpdateDto.cs | 6 + .../Identity/IdentityUserUpdateRolesDto.cs | 10 + .../Localization/ApplicationContracts/en.json | 12 + .../Localization/ApplicationContracts/tr.json | 12 + .../Properties/AssemblyInfo.cs | 18 + .../Volo.Abp.Identity.Application.csproj | 25 + .../Identity/AbpIdentityApplicationModule.cs | 26 + ...ntityApplicationModuleAutoMapperProfile.cs | 13 + .../Abp/Identity/IdentityAppServiceBase.cs | 21 + .../Abp/Identity/IdentityRoleAppService.cs | 104 ++ .../Abp/Identity/IdentityUserAppService.cs | 128 ++ .../Volo.Abp.Identity.Domain.Shared.csproj | 22 + .../Identity/AbpIdentityDomainSharedModule.cs | 23 + .../Abp/Identity/IdentityRoleClaimConsts.cs | 9 + .../Volo/Abp/Identity/IdentityRoleConsts.cs | 8 + .../Abp/Identity/IdentityUserClaimConsts.cs | 9 + .../Volo/Abp/Identity/IdentityUserConsts.cs | 25 + .../Abp/Identity/IdentityUserLoginConsts.cs | 9 + .../Abp/Identity/IdentityUserTokenConsts.cs | 9 + .../Identity/Localization/IdentityResource.cs | 10 + .../AbpIdentityServiceCollectionExtensions.cs | 44 + .../Properties/AssemblyInfo.cs | 22 + .../Volo.Abp.Identity.Domain.csproj | 29 + ...olo.Abp.Identity.Domain.csproj.DotSettings | 2 + .../Volo/Abp/Identity/AbpIdentityConsts.cs | 9 + .../Abp/Identity/AbpIdentityDomainModule.cs | 50 + .../Abp/Identity/AbpIdentityOptionsFactory.cs | 53 + .../AbpIdentitySettingDefinitionProvider.cs | 28 + .../Abp/Identity/AbpSecurityStampValidator.cs | 29 + .../Identity/AbpUserClaimsPrincipalFactory.cs | 40 + .../Volo/Abp/Identity/IIdentityDataSeeder.cs | 14 + .../Abp/Identity/IIdentityRoleRepository.cs | 29 + .../Abp/Identity/IIdentityUserRepository.cs | 69 + .../Volo/Abp/Identity/IdentityClaim.cs | 61 + .../Volo/Abp/Identity/IdentityDataSeeder.cs | 109 ++ .../Volo/Abp/Identity/IdentityRole.cs | 88 ++ .../Volo/Abp/Identity/IdentityRoleClaim.cs | 50 + .../Volo/Abp/Identity/IdentityRoleManager.cs | 47 + .../IdentityRoleRepositoryExtensions.cs | 14 + .../Volo/Abp/Identity/IdentityRoleStore.cs | 293 +++++ .../Volo/Abp/Identity/IdentitySettingNames.cs | 36 + .../Volo/Abp/Identity/IdentityUser.cs | 269 ++++ .../Volo/Abp/Identity/IdentityUserClaim.cs | 34 + .../Volo/Abp/Identity/IdentityUserLogin.cs | 76 ++ .../Volo/Abp/Identity/IdentityUserManager.cs | 79 ++ .../IdentityUserRepositoryExtensions.cs | 15 + ...sitoryExternalUserLookupServiceProvider.cs | 49 + .../Volo/Abp/Identity/IdentityUserRole.cs | 36 + .../Volo/Abp/Identity/IdentityUserStore.cs | 1108 +++++++++++++++++ .../Volo/Abp/Identity/IdentityUserToken.cs | 57 + .../RolePermissionManagementProvider.cs | 57 + .../UserPermissionManagementProvider.cs | 24 + .../RolePermissionManagerExtensions.cs | 32 + .../UserPermissionManagerExtensions.cs | 26 + .../Properties/AssemblyInfo.cs | 18 + ...lo.Abp.Identity.EntityFrameworkCore.csproj | 22 + ...ity.EntityFrameworkCore.csproj.DotSettings | 2 + .../AbpIdentityEntityFrameworkCoreModule.cs | 23 + .../EfCoreIdentityRoleRepository.cs | 55 + .../EfCoreIdentityUserRepository.cs | 152 +++ .../EntityFrameworkCore/IIdentityDbContext.cs | 14 + .../EntityFrameworkCore/IdentityDbContext.cs | 38 + ...IdentityDbContextModelBuilderExtensions.cs | 117 ++ .../IdentityEfCoreQueryableExtensions.cs | 33 + ...dentityModelBuilderConfigurationOptions.cs | 18 + .../Properties/AssemblyInfo.cs | 18 + .../Volo.Abp.Identity.HttpApi.Client.csproj | 24 + ...Identity.HttpApi.Client.csproj.DotSettings | 2 + .../AbpIdentityHttpApiClientModule.cs | 23 + .../HttpClientIdentityUserLookupService.cs | 33 + .../Abp/Identity/IdentityUserExtensions.cs | 20 + .../Properties/AssemblyInfo.cs | 18 + .../Volo.Abp.Identity.HttpApi.csproj | 22 + .../Abp/Identity/AbpIdentityHttpApiModule.cs | 15 + .../Abp/Identity/IdentityRoleController.cs | 65 + .../Abp/Identity/IdentityUserController.cs | 69 + .../Volo.Abp.Identity.MongoDB.csproj | 22 + ...lo.Abp.Identity.MongoDB.csproj.DotSettings | 2 + .../MongoDB/AbpIdentityBsonClassMap.cs | 28 + .../MongoDB/AbpIdentityMongoDbContext.cs | 26 + .../AbpIdentityMongoDbContextExtensions.cs | 29 + .../MongoDB/AbpIdentityMongoDbModule.cs | 26 + .../MongoDB/IAbpIdentityMongoDbContext.cs | 14 + ...tyMongoModelBuilderConfigurationOptions.cs | 12 + .../MongoDB/MongoIdentityRoleRepository.cs | 49 + .../MongoDB/MongoIdentityUserRepository.cs | 135 ++ .../AbpIdentityWebAutoMapperProfile.cs | 48 + .../AbpIdentityWebModule.cs | 72 ++ .../Resources/AbpIdentity/en.json | 22 + .../Resources/AbpIdentity/tr.json | 22 + .../AbpIdentityWebMainMenuContributor.cs | 36 + .../Pages/Identity/Roles/CreateModal.cshtml | 18 + .../Identity/Roles/CreateModal.cshtml.cs | 38 + .../Pages/Identity/Roles/EditModal.cshtml | 19 + .../Pages/Identity/Roles/EditModal.cshtml.cs | 49 + .../Pages/Identity/Roles/Index.cshtml | 42 + .../Pages/Identity/Roles/Index.cshtml.cs | 11 + .../Pages/Identity/Users/CreateModal.cshtml | 35 + .../Identity/Users/CreateModal.cshtml.cs | 87 ++ .../Pages/Identity/Users/EditModal.cshtml | 35 + .../Pages/Identity/Users/EditModal.cshtml.cs | 93 ++ .../Pages/Identity/Users/Index.cshtml | 52 + .../Pages/Identity/Users/Index.cshtml.cs | 12 + .../Pages/Identity/_ViewImports.cshtml | 3 + .../IdentityPermissionAppServiceGateway.cs | 51 + .../Properties/launchSettings.json | 27 + .../Volo.Abp.Identity.Web.csproj | 32 + .../Volo.Abp.Identity.Web/compilerconfig.json | 10 + .../compilerconfig.json.defaults | 49 + .../wwwroot/pages/identity/roles/index.css | 21 + .../wwwroot/pages/identity/roles/index.es5.js | 6 + .../pages/identity/roles/index.es5.min.js | 1 + .../wwwroot/pages/identity/roles/index.js | 90 ++ .../wwwroot/pages/identity/roles/index.less | 38 + .../pages/identity/roles/index.min.css | 1 + .../wwwroot/pages/identity/users/index.css | 21 + .../wwwroot/pages/identity/users/index.es5.js | 92 ++ .../pages/identity/users/index.es5.min.js | 1 + .../wwwroot/pages/identity/users/index.js | 99 ++ .../wwwroot/pages/identity/users/index.less | 39 + .../pages/identity/users/index.min.css | 1 + ...Volo.Abp.Identity.Application.Tests.csproj | 23 + .../AbpIdentityApplicationTestBase.cs | 7 + .../AbpIdentityApplicationTestModule.cs | 17 + .../Identity/IdentityRoleAppService_Tests.cs | 123 ++ .../Identity/IdentityUserAppService_Tests.cs | 194 +++ .../Properties/AssemblyInfo.cs | 18 + .../Volo.Abp.Identity.Domain.Tests.csproj | 23 + .../Abp/Identity/AbpIdentityDomainTestBase.cs | 7 + .../Identity/AbpIdentityDomainTestModule.cs | 36 + .../Identity/AbpIdentityExtendedTestBase.cs | 44 + .../Abp/Identity/IdentityOptions_Tests.cs | 50 + ...dentityTestPermissionDefinitionProvider.cs | 17 + .../Abp/Identity/PermissionManager_Tests.cs | 121 ++ .../Abp/Identity/TestPermissionDataBuilder.cs | 64 + .../Volo/Abp/Identity/TestPermissionNames.cs | 14 + ....Identity.EntityFrameworkCore.Tests.csproj | 29 + ...bpIdentityEntityFrameworkCoreTestModule.cs | 50 + .../IdentityDataSeeder_Tests.cs | 7 + .../IdentityRoleRepository_Tests.cs | 7 + .../IdentityUserRepository_Tests.cs | 7 + .../Identity_Repository_Resolve_Tests.cs | 10 + .../EntityFrameworkCore/LazyLoading_Tests.cs | 20 + .../Volo.Abp.Identity.MongoDB.Tests.csproj | 26 + .../MongoDB/AbpIdentityMongoDbTestModule.cs | 35 + .../MongoDB/IdentityDataSeeder_Tests.cs | 7 + .../MongoDB/IdentityRoleRepository_Tests.cs | 7 + .../MongoDB/IdentityUserRepository_Tests.cs | 7 + .../Identity_Repository_Resolve_Tests.cs | 6 + .../Volo.Abp.Identity.TestBase.csproj | 29 + .../Volo/Abp/Identity/AbpIdentityTestBase.cs | 13 + .../Abp/Identity/AbpIdentityTestBaseModule.cs | 40 + .../Identity/AbpIdentityTestDataBuilder.cs | 77 ++ .../Abp/Identity/IdentityDataSeeder_Tests.cs | 34 + .../Identity/IdentityRoleRepository_Tests.cs | 54 + .../Volo/Abp/Identity/IdentityTestData.cs | 14 + .../Identity/IdentityUserRepository_Tests.cs | 149 +++ .../Identity_Repository_Resolve_Tests.cs | 25 + .../Volo/Abp/Identity/LazyLoading_Tests.cs | 62 + 201 files changed, 8559 insertions(+) create mode 100644 modules/account/README.md create mode 100644 modules/account/Volo.Abp.Account.sln create mode 100644 modules/account/common.props create mode 100644 modules/account/src/Volo.Abp.Account.Web/AbpAccountOptions.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/LogoutController.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Localization/AccountResource.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json create mode 100644 modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml create mode 100644 modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json create mode 100644 modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingDefinitionProvider.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingNames.cs create mode 100644 modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj create mode 100644 modules/identity/README.md create mode 100644 modules/identity/Volo.Abp.Identity.sln create mode 100644 modules/identity/common.props create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityRolesInput.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityUsersInput.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissionDefinitionProvider.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleUpdateDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateRolesDto.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/tr.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityAppServiceBase.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/IdentityResource.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj.DotSettings create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityConsts.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpSecurityStampValidator.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityDataSeeder.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaim.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDataSeeder.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleClaim.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleRepositoryExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleStore.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingNames.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserClaim.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLogin.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRole.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserToken.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RolePermissionManagementProvider.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserPermissionManagementProvider.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/RolePermissionManagerExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/UserPermissionManagerExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj.DotSettings create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityModelBuilderConfigurationOptions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj.DotSettings create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/AbpIdentityHttpApiClientModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/IdentityUserExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/AssemblyInfo.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj.DotSettings create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IdentityMongoModelBuilderConfigurationOptions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Navigation/AbpIdentityWebMainMenuContributor.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/_ViewImports.cshtml create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Permissions/IdentityPermissionAppServiceGateway.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json.defaults create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.css create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.min.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.less create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.min.css create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.css create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.min.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.js create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.less create mode 100644 modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.min.css create mode 100644 modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo.Abp.Identity.Application.Tests.csproj create mode 100644 modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestBase.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Properties/AssemblyInfo.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo.Abp.Identity.Domain.Tests.csproj create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityTestPermissionDefinitionProvider.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/PermissionManager_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionDataBuilder.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionNames.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityDataSeeder_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityRoleRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/Identity_Repository_Resolve_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/LazyLoading_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo.Abp.Identity.TestBase.csproj create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBase.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBaseModule.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityDataSeeder_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs create mode 100644 modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs diff --git a/modules/account/README.md b/modules/account/README.md new file mode 100644 index 0000000000..21b8402a6d --- /dev/null +++ b/modules/account/README.md @@ -0,0 +1,2 @@ +# abp-account +Account module for ABP framework. diff --git a/modules/account/Volo.Abp.Account.sln b/modules/account/Volo.Abp.Account.sln new file mode 100644 index 0000000000..e4d25df4ed --- /dev/null +++ b/modules/account/Volo.Abp.Account.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B5881429-EFF7-4F30-8C0B-0AC41E36B74E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Account.Web", "src\Volo.Abp.Account.Web\Volo.Abp.Account.Web.csproj", "{FCAC4354-7B13-4A91-A2F4-04D00F253C91}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FCAC4354-7B13-4A91-A2F4-04D00F253C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCAC4354-7B13-4A91-A2F4-04D00F253C91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCAC4354-7B13-4A91-A2F4-04D00F253C91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCAC4354-7B13-4A91-A2F4-04D00F253C91}.Release|Any CPU.Build.0 = Release|Any CPU + {A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FCAC4354-7B13-4A91-A2F4-04D00F253C91} = {B5881429-EFF7-4F30-8C0B-0AC41E36B74E} + {A65A6E45-8FF7-4B78-AEA6-EAA0CDAA47E8} = {B5881429-EFF7-4F30-8C0B-0AC41E36B74E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2B054393-D2B2-4EA8-8A15-D60CBCF3E7A9} + EndGlobalSection +EndGlobal diff --git a/modules/account/common.props b/modules/account/common.props new file mode 100644 index 0000000000..f00a3fc2cb --- /dev/null +++ b/modules/account/common.props @@ -0,0 +1,16 @@ + + + latest + 0.3.0 + $(NoWarn);CS1591 + http://www.aspnetboilerplate.com/images/abp_nupkg.png + http://abp.io + git + https://github.com/volosoft/abp/ + + + + + + + \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountOptions.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountOptions.cs new file mode 100644 index 0000000000..077e9b886c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountOptions.cs @@ -0,0 +1,16 @@ +namespace Volo.Abp.Account.Web +{ + public class AbpAccountOptions + { + /// + /// Default value: "Windows". + /// + public string WindowsAuthenticationSchemeName { get; set; } + + public AbpAccountOptions() + { + //TODO: This makes us depend on the Microsoft.AspNetCore.Server.IISIntegration package. + WindowsAuthenticationSchemeName = "Windows"; //Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs new file mode 100644 index 0000000000..ef5e661771 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Account.Web +{ + public class AbpAccountUserMenuContributor : IMenuContributor + { + public AbpAccountUserMenuContributor() + { + + } + + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.User) + { + return Task.CompletedTask; + } + + var l = context.ServiceProvider.GetRequiredService>(); + + context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", l["Logout"], url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs new file mode 100644 index 0000000000..d212310648 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -0,0 +1,57 @@ +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Account.Web.Localization; +using Volo.Abp.Account.Web.Settings; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.Identity; +using Volo.Abp.Localization; +using Volo.Abp.Localization.Resources.AbpValidation; +using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Account.Web +{ + [DependsOn(typeof(AbpIdentityDomainModule))] + [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))] + public class AbpAccountWebModule : AbpModule + { + public override void PreConfigureServices(IServiceCollection services) + { + services.PreConfigure(options => + { + options.AddAssemblyResource(typeof(AccountResource), typeof(AbpAccountWebModule).Assembly); + }); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.DefinitionProviders.Add(); + }); + + services.Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + }); + + services.Configure(options => + { + options.MenuContributors.Add(new AbpAccountUserMenuContributor()); + }); + + services.Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization/Resources/AbpAccount/Web") + .AddBaseTypes(typeof(AbpUiResource), typeof(AbpValidationResource)); + }); + + services.AddAssemblyOf(); + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/LogoutController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/LogoutController.cs new file mode 100644 index 0000000000..e1620ed342 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/LogoutController.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Identity; + +namespace Volo.Abp.Account.Web.Areas.Account.Controllers +{ + [Area("Account")] + public class LogoutController : AbpController + { + private readonly SignInManager _signInManager; + + public LogoutController(SignInManager signInManager) + { + _signInManager = signInManager; + } + + public async Task Index() + { + await _signInManager.SignOutAsync(); + + return RedirectToPage("/Account/Login"); + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Localization/AccountResource.cs b/modules/account/src/Volo.Abp.Account.Web/Localization/AccountResource.cs new file mode 100644 index 0000000000..cb3b542887 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Localization/AccountResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Account.Web.Localization +{ + [LocalizationResourceName("AbpAccount")] + public class AccountResource + { + + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json new file mode 100644 index 0000000000..284cc5584f --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/en.json @@ -0,0 +1,15 @@ +{ + "culture": "en", + "texts": { + "UserName": "User name", + "EmailAddress": "Email address", + "UserNameOrEmailAddress": "User name or email address", + "Password": "Password", + "RememberMe": "Remember me", + "UseAnotherServiceToLogin": "Use another service to log in", + "UserLockedOutMessage": "The user account has been locked out due to invalid login attempts. Please wait a while and try again.", + "InvalidUserNameOrPassword": "Invalid username or password!", + "LoginIsNotAllowed": "You are not allowed to login! You need to confirm your email/phone number.", + "SelfRegistrationDisabledMessage": "Self user registration is disabled for this application. Contact to the application administrator to register a new user." + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json new file mode 100644 index 0000000000..a318da87a2 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json @@ -0,0 +1,15 @@ +{ + "culture": "tr", + "texts": { + "UserName": "Kullanıcı adı", + "EmailAddress": "E-posta adresi", + "UserNameOrEmailAddress": "Kullanıcı adı veya e-posta", + "Password": "Şifre", + "RememberMe": "Beni hatırla", + "UseAnotherServiceToLogin": "Başka bir servisle giriş yap", + "UserLockedOutMessage": "Kullanıcı hesabı hatalı giriş denemeleri nedeniyle kilitlenmiştir. Lütfen bir süre bekleyip tekrar deneyin.", + "InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!", + "LoginIsNotAllowed": "You are not allowed to login! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.", + "SelfRegistrationDisabledMessage": "Bu uygulama için kullanıcıların kendi kendilerine kaydolmaları engellenmiştir. Yeni bir kullanıcı kaydetmek için lütfen uygulama yöneticisi ile iletişime geçin." + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs new file mode 100644 index 0000000000..bb7d011235 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPage.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc.Localization; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Volo.Abp.Account.Web.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public abstract class AccountPage : AbpPage + { + [RazorInject] + public IHtmlLocalizer L { get; set; } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs new file mode 100644 index 0000000000..e5b7cedbf0 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using Volo.Abp.Account.Web.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.Identity; +using Volo.Abp.UI; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public abstract class AccountPageModel : AbpPageModel + { + public SignInManager SignInManager { get; set; } + public IdentityUserManager UserManager { get; set; } + public IStringLocalizer L { get; set; } + + protected RedirectResult RedirectSafely(string returnUrl, string returnUrlHash = null) + { + return Redirect(GetRedirectUrl(returnUrl, returnUrlHash)); + } + + protected void CheckIdentityErrors(IdentityResult identityResult) + { + if (!identityResult.Succeeded) + { + throw new UserFriendlyException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", ")); + } + + //identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp + } + + private string GetRedirectUrl(string returnUrl, string returnUrlHash = null) + { + returnUrl = NormalizeReturnUrl(returnUrl); + + if (!returnUrlHash.IsNullOrWhiteSpace()) + { + returnUrl = returnUrl + returnUrlHash; + } + + return returnUrl; + } + + private string NormalizeReturnUrl(string returnUrl) + { + if (returnUrl.IsNullOrEmpty()) + { + return GetAppHomeUrl(); + } + + if (Url.IsLocalUrl(returnUrl)) + { + return returnUrl; + } + + return GetAppHomeUrl(); + } + + protected virtual void CheckCurrentTenant(Guid? tenantId) + { + if (CurrentTenant.Id != tenantId) + { + throw new ApplicationException($"Current tenant is different than given tenant. CurrentTenant.Id: {CurrentTenant.Id}, given tenantId: {tenantId}"); + } + } + + protected virtual string GetAppHomeUrl() + { + return "/"; //TODO: ??? + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml new file mode 100644 index 0000000000..bd118b8019 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml @@ -0,0 +1,36 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.LoginModel +@using Volo.Abp.Account.Web.Settings +@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage +@inject Volo.Abp.Settings.ISettingManager SettingManager +

@L["Login"]

+ + +
+ + + + @L["Login"] + @if (string.Equals(await SettingManager.GetOrNullAsync(AccountSettingNames.IsSelfRegistrationEnabled), "true", StringComparison.OrdinalIgnoreCase)) + { + @L["Register"] + } + +
+ + @if (Model.ExternalLogins.Any()) + { + +

Use another service to log in.

+
+
+ @foreach (var provider in Model.ExternalLogins) + { + @provider.DisplayName + } +
+
+
+ } + +
\ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000000..b580300dda --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; +using Volo.Abp.UI; +using Volo.Abp.Uow; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class LoginModel : AccountPageModel + { + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public PostInput Input { get; set; } + + public IList ExternalLogins { get; set; } + + public async Task OnGetAsync() + { + ExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual async Task OnPostAsync() + { + ValidateModel(); + + var result = await SignInManager.PasswordSignInAsync( + Input.UserNameOrEmailAddress, + Input.Password, + Input.RememberMe, + true + ); + + if (result.IsLockedOut) + { + Alerts.Warning(L["UserLockedOutMessage"]); + return Page(); + } + + if (result.RequiresTwoFactor) + { + return RedirectToPage("./SendSecurityCode"); + } + + if (result.IsNotAllowed) + { + Alerts.Warning(L["LoginIsNotAllowed"]); + return Page(); + } + + if (!result.Succeeded) + { + Alerts.Danger(L["InvalidUserNameOrPassword"]); + return Page(); + } + + return RedirectSafely(ReturnUrl, ReturnUrlHash); + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual IActionResult OnPostExternalLogin(string provider, string returnUrl = "", string returnUrlHash = "") + { + var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { returnUrl, returnUrlHash }); + + var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + + return new ChallengeResult(provider, properties); + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual async Task OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) + { + if (remoteError != null) + { + Logger.LogWarning($"External login callback error: {remoteError}"); + return RedirectToPage("./Login"); + } + + var loginInfo = await SignInManager.GetExternalLoginInfoAsync(); + if (loginInfo == null) + { + Logger.LogWarning("External login info is not available"); + return RedirectToPage("./Login"); + } + + var result = await SignInManager.ExternalLoginSignInAsync( + loginInfo.LoginProvider, + loginInfo.ProviderKey, + isPersistent: false, + bypassTwoFactor: true + ); + + if (result.IsLockedOut) + { + throw new UserFriendlyException("Cannot proceed because user is locked out!"); + } + + //TODO: Handle other cases + + if (result.Succeeded) + { + return RedirectSafely(returnUrl, returnUrlHash); + } + + // Get the information about the user from the external login provider + var info = await SignInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + throw new ApplicationException("Error loading external login information during confirmation."); + } + + var user = await CreateExternalUserAsync(info); + + await SignInManager.SignInAsync(user, false); + return RedirectSafely(returnUrl, returnUrlHash); + } + + private async Task CreateExternalUserAsync(ExternalLoginInfo info) + { + var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email); + + var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id); + + CheckIdentityErrors(await UserManager.CreateAsync(user)); + CheckIdentityErrors(await UserManager.SetEmailAsync(user, emailAddress)); + CheckIdentityErrors(await UserManager.AddLoginAsync(user, info)); + + return user; + } + + public class PostInput + { + [Required] + [StringLength(255)] + [DisplayName(nameof(UserNameOrEmailAddress))] + public string UserNameOrEmailAddress { get; set; } + + [Required] + [StringLength(32)] + [DataType(DataType.Password)] + [DisplayName(nameof(Password))] + public string Password { get; set; } + + [DisplayName(nameof(RememberMe))] + public bool RememberMe { get; set; } + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml new file mode 100644 index 0000000000..c9a98394f5 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml @@ -0,0 +1,14 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.RegisterModel +@inherits Volo.Abp.Account.Web.Pages.Account.AccountPage +

@L["Register"]

+ + +
+ + + + @L["Register"] + +
+
\ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs new file mode 100644 index 0000000000..ae19ee27c4 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Register.cshtml.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Account.Web.Settings; +using Volo.Abp.Identity; +using Volo.Abp.Settings; +using Volo.Abp.UI; +using Volo.Abp.Uow; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class RegisterModel : AccountPageModel + { + [BindProperty(SupportsGet = true)] + public string ReturnUrl { get; set; } + + [BindProperty(SupportsGet = true)] + public string ReturnUrlHash { get; set; } + + [BindProperty] + public PostInput Input { get; set; } + + public virtual async Task OnGet() + { + await CheckSelfRegistrationAsync(); + } + + [UnitOfWork] //TODO: Will be removed when we implement action filter + public virtual async Task OnPostAsync() + { + ValidateModel(); + + await CheckSelfRegistrationAsync(); + + var user = new IdentityUser(GuidGenerator.Create(), Input.UserName, Input.EmailAddress, CurrentTenant.Id); + + var result = await UserManager.CreateAsync(user, Input.Password); + + if (!result.Succeeded) + { + throw new NotImplementedException(); + } + + await UserManager.SetEmailAsync(user, Input.EmailAddress); + + await SignInManager.SignInAsync(user, isPersistent: false); + + return Redirect(ReturnUrl ?? "/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow! + } + + protected virtual async Task CheckSelfRegistrationAsync() + { + if (!await SettingManager.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled)) + { + throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]); + } + } + + public class PostInput + { + [Required] + [StringLength(32)] + [DisplayName(nameof(UserName))] + public string UserName { get; set; } + + [Required] + [EmailAddress] + [StringLength(255)] + [DisplayName(nameof(EmailAddress))] + public string EmailAddress { get; set; } + + [Required] + [StringLength(32)] + [DataType(DataType.Password)] + [DisplayName(nameof(Password))] + public string Password { get; set; } + } + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml new file mode 100644 index 0000000000..93732ce265 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml @@ -0,0 +1,6 @@ +@page +@using Volo.Abp.Account.Web.Pages.Account +@inherits AccountPage +@model SendSecurityCodeModel +

Send security code!

+

TODO: This page is under construction.

\ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs new file mode 100644 index 0000000000..5923d8972c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/SendSecurityCode.cshtml.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Volo.Abp.Account.Web.Pages.Account +{ + public class SendSecurityCodeModel : AccountPageModel + { + public List Providers { get; set; } + + public async Task OnGetAsync() + { + var user = await SignInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return RedirectToPage("./Login"); + } + + return Page(); + + //CheckCurrentTenant(await SignInManager.GetVerifiedTenantIdAsync()); + + //Providers = (await UserManager.GetValidTwoFactorProvidersAsync(user)) + // .Select(userProvider => + // new SelectListItem + // { + // Text = userProvider, + // Value = userProvider + // }).ToList(); + + //return View( + // new SendSecurityCodeViewModel + // { + // ReturnUrl = returnUrl, + // RememberMe = rememberMe + // } + //); + } + + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json b/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json new file mode 100644 index 0000000000..8dab775bff --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56009/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.Account.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:56013/" + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingDefinitionProvider.cs b/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingDefinitionProvider.cs new file mode 100644 index 0000000000..8d3ccba7c9 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingDefinitionProvider.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Settings; + +namespace Volo.Abp.Account.Web.Settings +{ + public class AccountSettingDefinitionProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition(AccountSettingNames.IsSelfRegistrationEnabled, "true") + ); + } + } +} \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingNames.cs b/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingNames.cs new file mode 100644 index 0000000000..3122dbdc6a --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Settings/AccountSettingNames.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Account.Web.Settings +{ + public class AccountSettingNames + { + public const string IsSelfRegistrationEnabled = "Abp.Account.IsSelfRegistrationEnabled"; + } +} diff --git a/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj new file mode 100644 index 0000000000..8804186ce7 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -0,0 +1,29 @@ + + + + + + netstandard2.0 + Volo.Abp.Account.Web + Volo.Abp.Account.Web + true + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + Volo.Abp.Account.Web + Library + + + + + + + + + + + + + + diff --git a/modules/identity/README.md b/modules/identity/README.md new file mode 100644 index 0000000000..cd4a6d1c62 --- /dev/null +++ b/modules/identity/README.md @@ -0,0 +1,2 @@ +# abp-identity +Microsoft ASP.NET Core Identity integration & management module diff --git a/modules/identity/Volo.Abp.Identity.sln b/modules/identity/Volo.Abp.Identity.sln new file mode 100644 index 0000000000..b6973a36ba --- /dev/null +++ b/modules/identity/Volo.Abp.Identity.sln @@ -0,0 +1,123 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AADC5A0A-F100-4511-87DE-B74E55F5B69B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain.Shared", "src\Volo.Abp.Identity.Domain.Shared\Volo.Abp.Identity.Domain.Shared.csproj", "{0A023ED6-1476-4145-B1FB-A87427D1F601}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain", "src\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj", "{762003A8-46D0-48B7-8529-8B78433662C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Application.Contracts", "src\Volo.Abp.Identity.Application.Contracts\Volo.Abp.Identity.Application.Contracts.csproj", "{B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Application", "src\Volo.Abp.Identity.Application\Volo.Abp.Identity.Application.csproj", "{A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.EntityFrameworkCore", "src\Volo.Abp.Identity.EntityFrameworkCore\Volo.Abp.Identity.EntityFrameworkCore.csproj", "{8932D139-42B5-435F-8E54-A0283034EF0C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.HttpApi", "src\Volo.Abp.Identity.HttpApi\Volo.Abp.Identity.HttpApi.csproj", "{8AAE1E26-9804-4691-B308-68FC4940A2CF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.HttpApi.Client", "src\Volo.Abp.Identity.HttpApi.Client\Volo.Abp.Identity.HttpApi.Client.csproj", "{281365DF-9F93-4734-9F37-0EDAA58DF6B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Web", "src\Volo.Abp.Identity.Web\Volo.Abp.Identity.Web.csproj", "{9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9FACAF96-A681-4B36-A938-A37DCA0B7EC1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Application.Tests", "test\Volo.Abp.Identity.Application.Tests\Volo.Abp.Identity.Application.Tests.csproj", "{16D87575-35A5-4AEE-840B-C1EC74C3E22C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.MongoDB", "src\Volo.Abp.Identity.MongoDB\Volo.Abp.Identity.MongoDB.csproj", "{F33E45A6-22E6-4F6E-947F-7E82E5171D4F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.MongoDB.Tests", "test\Volo.Abp.Identity.MongoDB.Tests\Volo.Abp.Identity.MongoDB.Tests.csproj", "{B5891082-0799-474E-8A62-8685935D88C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.EntityFrameworkCore.Tests", "test\Volo.Abp.Identity.EntityFrameworkCore.Tests\Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj", "{7291DCF0-7AA2-41A6-9AA7-98C2E9D13222}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.TestBase", "test\Volo.Abp.Identity.TestBase\Volo.Abp.Identity.TestBase.csproj", "{D7F61598-E7CE-4DAB-99EA-C266F0423606}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain.Tests", "test\Volo.Abp.Identity.Domain.Tests\Volo.Abp.Identity.Domain.Tests.csproj", "{588B6E38-323B-4251-AC21-5F67C815A44E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0A023ED6-1476-4145-B1FB-A87427D1F601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A023ED6-1476-4145-B1FB-A87427D1F601}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A023ED6-1476-4145-B1FB-A87427D1F601}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A023ED6-1476-4145-B1FB-A87427D1F601}.Release|Any CPU.Build.0 = Release|Any CPU + {762003A8-46D0-48B7-8529-8B78433662C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {762003A8-46D0-48B7-8529-8B78433662C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {762003A8-46D0-48B7-8529-8B78433662C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {762003A8-46D0-48B7-8529-8B78433662C3}.Release|Any CPU.Build.0 = Release|Any CPU + {B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9}.Release|Any CPU.Build.0 = Release|Any CPU + {A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED}.Release|Any CPU.Build.0 = Release|Any CPU + {8932D139-42B5-435F-8E54-A0283034EF0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8932D139-42B5-435F-8E54-A0283034EF0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8932D139-42B5-435F-8E54-A0283034EF0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8932D139-42B5-435F-8E54-A0283034EF0C}.Release|Any CPU.Build.0 = Release|Any CPU + {8AAE1E26-9804-4691-B308-68FC4940A2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8AAE1E26-9804-4691-B308-68FC4940A2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AAE1E26-9804-4691-B308-68FC4940A2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8AAE1E26-9804-4691-B308-68FC4940A2CF}.Release|Any CPU.Build.0 = Release|Any CPU + {281365DF-9F93-4734-9F37-0EDAA58DF6B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {281365DF-9F93-4734-9F37-0EDAA58DF6B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {281365DF-9F93-4734-9F37-0EDAA58DF6B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {281365DF-9F93-4734-9F37-0EDAA58DF6B5}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB}.Release|Any CPU.Build.0 = Release|Any CPU + {16D87575-35A5-4AEE-840B-C1EC74C3E22C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16D87575-35A5-4AEE-840B-C1EC74C3E22C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16D87575-35A5-4AEE-840B-C1EC74C3E22C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16D87575-35A5-4AEE-840B-C1EC74C3E22C}.Release|Any CPU.Build.0 = Release|Any CPU + {F33E45A6-22E6-4F6E-947F-7E82E5171D4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F33E45A6-22E6-4F6E-947F-7E82E5171D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F33E45A6-22E6-4F6E-947F-7E82E5171D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F33E45A6-22E6-4F6E-947F-7E82E5171D4F}.Release|Any CPU.Build.0 = Release|Any CPU + {B5891082-0799-474E-8A62-8685935D88C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5891082-0799-474E-8A62-8685935D88C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5891082-0799-474E-8A62-8685935D88C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5891082-0799-474E-8A62-8685935D88C3}.Release|Any CPU.Build.0 = Release|Any CPU + {7291DCF0-7AA2-41A6-9AA7-98C2E9D13222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7291DCF0-7AA2-41A6-9AA7-98C2E9D13222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7291DCF0-7AA2-41A6-9AA7-98C2E9D13222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7291DCF0-7AA2-41A6-9AA7-98C2E9D13222}.Release|Any CPU.Build.0 = Release|Any CPU + {D7F61598-E7CE-4DAB-99EA-C266F0423606}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7F61598-E7CE-4DAB-99EA-C266F0423606}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7F61598-E7CE-4DAB-99EA-C266F0423606}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7F61598-E7CE-4DAB-99EA-C266F0423606}.Release|Any CPU.Build.0 = Release|Any CPU + {588B6E38-323B-4251-AC21-5F67C815A44E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {588B6E38-323B-4251-AC21-5F67C815A44E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {588B6E38-323B-4251-AC21-5F67C815A44E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {588B6E38-323B-4251-AC21-5F67C815A44E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0A023ED6-1476-4145-B1FB-A87427D1F601} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {762003A8-46D0-48B7-8529-8B78433662C3} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {B8A5EBD5-0D41-46B0-8E09-96503A7F2FF9} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {A4EEB11B-C94A-4039-B5B5-FE5E4D6877ED} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {8932D139-42B5-435F-8E54-A0283034EF0C} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {8AAE1E26-9804-4691-B308-68FC4940A2CF} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {281365DF-9F93-4734-9F37-0EDAA58DF6B5} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {9BA53D0D-C68D-4BEB-B1C0-51ED8B1162EB} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {16D87575-35A5-4AEE-840B-C1EC74C3E22C} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + {F33E45A6-22E6-4F6E-947F-7E82E5171D4F} = {AADC5A0A-F100-4511-87DE-B74E55F5B69B} + {B5891082-0799-474E-8A62-8685935D88C3} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + {7291DCF0-7AA2-41A6-9AA7-98C2E9D13222} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + {D7F61598-E7CE-4DAB-99EA-C266F0423606} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + {588B6E38-323B-4251-AC21-5F67C815A44E} = {9FACAF96-A681-4B36-A938-A37DCA0B7EC1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {05740D37-83CF-4041-9C2A-D89F1B3DB5A4} + EndGlobalSection +EndGlobal diff --git a/modules/identity/common.props b/modules/identity/common.props new file mode 100644 index 0000000000..f00a3fc2cb --- /dev/null +++ b/modules/identity/common.props @@ -0,0 +1,16 @@ + + + latest + 0.3.0 + $(NoWarn);CS1591 + http://www.aspnetboilerplate.com/images/abp_nupkg.png + http://abp.io + git + https://github.com/volosoft/abp/ + + + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2d276baf9f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.Application.Contracts")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c714a3ab-8402-4dc2-b120-accb2e29bd8f")] diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.csproj b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.csproj new file mode 100644 index 0000000000..339fd0d69f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.csproj @@ -0,0 +1,29 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.Application.Contracts + Volo.Abp.Identity.Application.Contracts + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs new file mode 100644 index 0000000000..7cadc77a85 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/AbpIdentityApplicationContractsModule.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Application; +using Volo.Abp.Authorization; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Identity +{ + [DependsOn(typeof(AbpIdentityDomainSharedModule))] + [DependsOn(typeof(AbpAuthorizationModule))] + [DependsOn(typeof(AbpDddApplicationModule))] + [DependsOn(typeof(AbpPermissionManagementApplicationContractsModule))] + public class AbpIdentityApplicationContractsModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.DefinitionProviders.Add(); + }); + + services.Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + services.Configure(options => + { + options.Resources.Get().AddVirtualJson("/Volo/Abp/Identity/Localization/ApplicationContracts"); + }); + + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityRolesInput.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityRolesInput.cs new file mode 100644 index 0000000000..c5db3405a8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityRolesInput.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Identity +{ + public class GetIdentityRolesInput : PagedAndSortedResultRequestDto + { + public string Filter { get; set; } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityUsersInput.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityUsersInput.cs new file mode 100644 index 0000000000..29c6e296e7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/GetIdentityUsersInput.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Identity +{ + public class GetIdentityUsersInput : PagedAndSortedResultRequestDto + { + public string Filter { get; set; } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs new file mode 100644 index 0000000000..1d29a13ef3 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + public interface IIdentityRoleAppService : IAsyncCrudAppService + { + //TODO: remove after a better design + Task> GetAllListAsync(); + + Task GetPermissionsAsync(Guid id); + + Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs new file mode 100644 index 0000000000..424a5c3ca2 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityUserAppService.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + public interface IIdentityUserAppService : IAsyncCrudAppService + { + Task> GetRolesAsync(Guid id); + + Task UpdateRolesAsync(Guid id, IdentityUserUpdateRolesDto input); + + Task GetPermissionsAsync(Guid id); + + Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissionDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..5315b9248c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissionDefinitionProvider.cs @@ -0,0 +1,31 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; + +namespace Volo.Abp.Identity +{ + public class IdentityPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var identityGroup = context.AddGroup(IdentityPermissions.GroupName, L("Permission:IdentityManagement")); + + var rolesPermission = identityGroup.AddPermission(IdentityPermissions.Roles.Default, L("Permission:RoleManagement")); + rolesPermission.AddChild(IdentityPermissions.Roles.Create, L("Permission:Create")); + rolesPermission.AddChild(IdentityPermissions.Roles.Update, L("Permission:Edit")); + rolesPermission.AddChild(IdentityPermissions.Roles.Delete, L("Permission:Delete")); + rolesPermission.AddChild(IdentityPermissions.Roles.ManagePermissions, L("Permission:ChangePermissions")); + + var usersPermission = identityGroup.AddPermission(IdentityPermissions.Users.Default, L("Permission:UserManagement")); + usersPermission.AddChild(IdentityPermissions.Users.Create, L("Permission:Create")); + usersPermission.AddChild(IdentityPermissions.Users.Update, L("Permission:Edit")); + usersPermission.AddChild(IdentityPermissions.Users.Delete, L("Permission:Delete")); + usersPermission.AddChild(IdentityPermissions.Users.ManagePermissions, L("Permission:ChangePermissions")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissions.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissions.cs new file mode 100644 index 0000000000..3004cc81df --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityPermissions.cs @@ -0,0 +1,43 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityPermissions + { + public const string GroupName = "AbpIdentity"; + + public static class Roles + { + public const string Default = GroupName + ".Roles"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + public const string ManagePermissions = Default + ".ManagePermissions"; + } + + public static class Users + { + public const string Default = GroupName + ".Users"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + public const string ManagePermissions = Default + ".ManagePermissions"; + } + + public static string[] GetAll() + { + return new[] + { + GroupName, + Roles.Default, + Roles.Create, + Roles.Update, + Roles.Delete, + Roles.ManagePermissions, + Users.Default, + Users.Create, + Users.Update, + Users.Delete, + Users.ManagePermissions + }; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateDto.cs new file mode 100644 index 0000000000..d98edfb638 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity +{ + public class IdentityRoleCreateDto : IdentityRoleCreateOrUpdateDtoBase + { + + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs new file mode 100644 index 0000000000..474e5cbd57 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.Identity +{ + public class IdentityRoleCreateOrUpdateDtoBase + { + [Required] + [StringLength(IdentityRoleConsts.MaxNameLength)] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs new file mode 100644 index 0000000000..13aa96f41b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Volo.Abp.Identity +{ + public class IdentityRoleDto : EntityDto + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleUpdateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleUpdateDto.cs new file mode 100644 index 0000000000..c569957f23 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleUpdateDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity +{ + public class IdentityRoleUpdateDto : IdentityRoleCreateOrUpdateDtoBase + { + + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs new file mode 100644 index 0000000000..cc2e21eeda --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateDto.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.Identity +{ + public class IdentityUserCreateDto : IdentityUserCreateOrUpdateDtoBase + { + [Required] + [StringLength(IdentityUserConsts.MaxPasswordLength)] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs new file mode 100644 index 0000000000..a3461fcd90 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityUserCreateOrUpdateDtoBase + { + [Required] + [StringLength(IdentityUserConsts.MaxUserNameLength)] + public string UserName { get; set; } + + [Required] + [EmailAddress] + [StringLength(IdentityUserConsts.MaxEmailLength)] + public string Email { get; set; } + + [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + public string PhoneNumber { get; set; } + + public bool TwoFactorEnabled { get; set; } + + public bool LockoutEnabled { get; set; } + + [CanBeNull] + public string[] RoleNames { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs new file mode 100644 index 0000000000..893d2ea710 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + public class IdentityUserDto : EntityDto, IMultiTenant + { + public Guid? TenantId { get; set; } + + public string UserName { get; set; } + + public string Email { get; set; } + + public bool EmailConfirmed { get; set; } + + public string PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } + + public bool TwoFactorEnabled { get; set; } + + public bool LockoutEnabled { get; set; } + + public DateTimeOffset? LockoutEnd { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs new file mode 100644 index 0000000000..3117ff8003 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateDto.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Identity +{ + public class IdentityUserUpdateDto : IdentityUserCreateOrUpdateDtoBase + { + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateRolesDto.cs b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateRolesDto.cs new file mode 100644 index 0000000000..c6f86669b4 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserUpdateRolesDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Volo.Abp.Identity +{ + public class IdentityUserUpdateRolesDto + { + [Required] + public string[] RoleNames { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json new file mode 100644 index 0000000000..bccd49461e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/en.json @@ -0,0 +1,12 @@ +{ + "culture": "en", + "texts": { + "Permission:IdentityManagement": "Idetity management", + "Permission:RoleManagement": "Role management", + "Permission:Create": "Create", + "Permission:Edit": "Edit", + "Permission:Delete": "Delete", + "Permission:ChangePermissions": "Change permissions", + "Permission:UserManagement": "User management" + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/tr.json b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/tr.json new file mode 100644 index 0000000000..c185fc1b9e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/Localization/ApplicationContracts/tr.json @@ -0,0 +1,12 @@ +{ + "culture": "tr", + "texts": { + "Permission:IdentityManagement": "Kimlik yönetimi", + "Permission:RoleManagement": "Rol yönetimi", + "Permission:Create": "Oluşturma", + "Permission:Edit": "Düzenleme", + "Permission:Delete": "Silme", + "Permission:ChangePermissions": "İzinleri değiştirme", + "Permission:UserManagement": "Kullanıcı yönetimi" + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.Application/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..e197afe6b9 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.Application")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("54592671-9cb6-48ae-9ae0-84cd016e87ff")] diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj new file mode 100644 index 0000000000..129dd9f3d3 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo.Abp.Identity.Application.csproj @@ -0,0 +1,25 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.Application + Volo.Abp.Identity.Application + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs new file mode 100644 index 0000000000..fe258bf9c1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + [DependsOn( + typeof(AbpIdentityDomainModule), + typeof(AbpIdentityApplicationContractsModule), + typeof(AbpAutoMapperModule), + typeof(AbpPermissionManagementApplicationModule) + )] + public class AbpIdentityApplicationModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.AddProfile(); + }); + + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs new file mode 100644 index 0000000000..e796a0a7ae --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs @@ -0,0 +1,13 @@ +using AutoMapper; + +namespace Volo.Abp.Identity +{ + public class AbpIdentityApplicationModuleAutoMapperProfile : Profile + { + public AbpIdentityApplicationModuleAutoMapperProfile() + { + CreateMap(); + CreateMap(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityAppServiceBase.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityAppServiceBase.cs new file mode 100644 index 0000000000..3f1919323f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityAppServiceBase.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityAppServiceBase : ApplicationService + { + protected void CheckIdentityErrors(IdentityResult identityResult) + { + if (!identityResult.Succeeded) + { + //TODO: A better exception that can be shown on UI as localized? + throw new AbpException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", ")); + } + + //identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs new file mode 100644 index 0000000000..0b59826963 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + [Authorize(IdentityPermissions.Roles.Default)] + public class IdentityRoleAppService : ApplicationService, IIdentityRoleAppService + { + private readonly IdentityRoleManager _roleManager; + private readonly IIdentityRoleRepository _roleRepository; + private readonly IPermissionAppServiceHelper _permissionAppServiceHelper; + + public IdentityRoleAppService( + IdentityRoleManager roleManager, + IIdentityRoleRepository roleRepository, + IPermissionAppServiceHelper permissionAppServiceHelper) + { + _roleManager = roleManager; + _roleRepository = roleRepository; + _permissionAppServiceHelper = permissionAppServiceHelper; + } + + public async Task GetAsync(Guid id) + { + return ObjectMapper.Map( + await _roleManager.GetByIdAsync(id) + ); + } + + public async Task> GetListAsync(GetIdentityRolesInput input) //TODO: Remove this method since it's not used + { + var count = (int) await _roleRepository.GetCountAsync(); + var list = await _roleRepository.GetListAsync(); + + return new PagedResultDto( + count, + ObjectMapper.Map, List>(list) + ); + } + + public async Task> GetAllListAsync() //TODO: Rename to GetList (however it's not possible because of the design of the IAsyncCrudAppService) + { + var list = await _roleRepository.GetListAsync(); + + return ObjectMapper.Map, List>(list); + } + + [Authorize(IdentityPermissions.Roles.ManagePermissions)] + public async Task GetPermissionsAsync(Guid id) + { + var role = await _roleRepository.GetAsync(id); + return await _permissionAppServiceHelper.GetAsync(RolePermissionValueProvider.ProviderName, role.Name); //TODO: User normalized role name instad of name? + } + + [Authorize(IdentityPermissions.Roles.ManagePermissions)] + public async Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input) + { + var role = await _roleRepository.GetAsync(id); + await _permissionAppServiceHelper.UpdateAsync(RolePermissionValueProvider.ProviderName, role.Name, input); + } + + [Authorize(IdentityPermissions.Roles.Create)] + public async Task CreateAsync(IdentityRoleCreateDto input) + { + var role = new IdentityRole(GuidGenerator.Create(), input.Name, CurrentTenant.Id); + + await _roleManager.CreateAsync(role); + await CurrentUnitOfWork.SaveChangesAsync(); + + return ObjectMapper.Map(role); + } + + [Authorize(IdentityPermissions.Roles.Update)] + public async Task UpdateAsync(Guid id, IdentityRoleUpdateDto input) + { + var role = await _roleManager.GetByIdAsync(id); + + await _roleManager.SetRoleNameAsync(role, input.Name); + + await _roleManager.UpdateAsync(role); + await CurrentUnitOfWork.SaveChangesAsync(); + + return ObjectMapper.Map(role); + } + + [Authorize(IdentityPermissions.Roles.Delete)] + public async Task DeleteAsync(Guid id) + { + var role = await _roleManager.FindByIdAsync(id.ToString()); + if (role == null) + { + return; + } + + await _roleManager.DeleteAsync(role); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs new file mode 100644 index 0000000000..5946acbd79 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + [Authorize(IdentityPermissions.Users.Default)] + public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppService + { + private readonly IdentityUserManager _userManager; + private readonly IIdentityUserRepository _userRepository; + private readonly IPermissionAppServiceHelper _permissionAppServiceHelper; + + public IdentityUserAppService( + IdentityUserManager userManager, + IIdentityUserRepository userRepository, + IPermissionAppServiceHelper permissionAppServiceHelper) + { + _userManager = userManager; + _userRepository = userRepository; + _permissionAppServiceHelper = permissionAppServiceHelper; + } + + public async Task GetAsync(Guid id) + { + return ObjectMapper.Map( + await _userManager.GetByIdAsync(id) + ); + } + + public async Task> GetListAsync(GetIdentityUsersInput input) + { + var count = await _userRepository.GetCountAsync(); //TODO: + var list = await _userRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount, input.Filter); + + return new PagedResultDto( + count, + ObjectMapper.Map, List>(list) + ); + } + + public async Task> GetRolesAsync(Guid id) + { + var roles = await _userRepository.GetRolesAsync(id); + return new ListResultDto( + ObjectMapper.Map, List>(roles) + ); + } + + [Authorize(IdentityPermissions.Users.Create)] + public async Task CreateAsync(IdentityUserCreateDto input) + { + var user = new IdentityUser(GuidGenerator.Create(), input.UserName, input.Email, CurrentTenant.Id); + + CheckIdentityErrors(await _userManager.CreateAsync(user, input.Password)); + await UpdateUserByInput(user, input); + + await CurrentUnitOfWork.SaveChangesAsync(); + + return ObjectMapper.Map(user); + } + + [Authorize(IdentityPermissions.Users.Update)] + public async Task UpdateAsync(Guid id, IdentityUserUpdateDto input) + { + var user = await _userManager.GetByIdAsync(id); + + CheckIdentityErrors(await _userManager.SetUserNameAsync(user, input.UserName)); + await UpdateUserByInput(user, input); + CheckIdentityErrors(await _userManager.UpdateAsync(user)); + await CurrentUnitOfWork.SaveChangesAsync(); + + return ObjectMapper.Map(user); + } + + [Authorize(IdentityPermissions.Users.Delete)] + public async Task DeleteAsync(Guid id) + { + var user = await _userManager.FindByIdAsync(id.ToString()); + if (user == null) + { + return; + } + + CheckIdentityErrors(await _userManager.DeleteAsync(user)); + } + + [Authorize(IdentityPermissions.Users.Update)] + public async Task UpdateRolesAsync(Guid id, IdentityUserUpdateRolesDto input) + { + var user = await _userManager.GetByIdAsync(id); + CheckIdentityErrors(await _userManager.SetRolesAsync(user, input.RoleNames)); + await _userRepository.UpdateAsync(user); + } + + [Authorize(IdentityPermissions.Users.ManagePermissions)] + public async Task GetPermissionsAsync(Guid id) + { + var user = await _userManager.GetByIdAsync(id); + var result = await _permissionAppServiceHelper.GetAsync(UserPermissionValueProvider.ProviderName, id.ToString()); + result.EntityDisplayName = user.UserName; + return result; + } + + [Authorize(IdentityPermissions.Users.ManagePermissions)] + public async Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input) + { + await _permissionAppServiceHelper.UpdateAsync(UserPermissionValueProvider.ProviderName, id.ToString(), input); + } + + private async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateDtoBase input) + { + CheckIdentityErrors(await _userManager.SetEmailAsync(user, input.Email)); + CheckIdentityErrors(await _userManager.SetPhoneNumberAsync(user, input.PhoneNumber)); + CheckIdentityErrors(await _userManager.SetTwoFactorEnabledAsync(user, input.TwoFactorEnabled)); + CheckIdentityErrors(await _userManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)); + + if (input.RoleNames != null) + { + CheckIdentityErrors(await _userManager.SetRolesAsync(user, input.RoleNames)); + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj new file mode 100644 index 0000000000..942ae82711 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo.Abp.Identity.Domain.Shared.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.Domain.Shared + Volo.Abp.Identity.Domain.Shared + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs new file mode 100644 index 0000000000..07f4224dd5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + [DependsOn(typeof(AbpUsersDomainSharedModule))] + [DependsOn(typeof(AbpLocalizationModule))] + public class AbpIdentityDomainSharedModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.Resources.Add("en"); + }); + + services.AddAssemblyOf(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs new file mode 100644 index 0000000000..f53717486f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleClaimConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityRoleClaimConsts + { + public const int MaxClaimTypeLength = IdentityUserClaimConsts.MaxClaimTypeLength; + + public const int MaxClaimValueLength = IdentityUserClaimConsts.MaxClaimValueLength; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs new file mode 100644 index 0000000000..11bde27f34 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityRoleConsts.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityRoleConsts + { + public const int MaxNameLength = 256; + public const int MaxNormalizedNameLength = MaxNameLength; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs new file mode 100644 index 0000000000..025b255f11 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserClaimConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityUserClaimConsts + { + public const int MaxClaimTypeLength = 256; + + public const int MaxClaimValueLength = 1024; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs new file mode 100644 index 0000000000..c41c63280d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserConsts.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public static class IdentityUserConsts + { + public const int MaxUserNameLength = AbpUserConsts.MaxUserNameLength; + + public const int MaxNormalizedUserNameLength = MaxUserNameLength; + + public const int MaxEmailLength = AbpUserConsts.MaxEmailLength; + + public const int MaxNormalizedEmailLength = MaxEmailLength; + + public const int MaxPhoneNumberLength = AbpUserConsts.MaxPhoneNumberLength; + + public const int MaxPasswordLength = 32; + + public const int MaxPasswordHashLength = 256; + + public const int MaxSecurityStampLength = 256; + + public const int MaxConcurrencyStampLength = 256; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs new file mode 100644 index 0000000000..0ef43bb1b1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserLoginConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityUserLoginConsts + { + public const int MaxLoginProviderLength = 64; + public const int MaxProviderKeyLength = 196; + public const int MaxProviderDisplayNameLength = 128; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs new file mode 100644 index 0000000000..92305851fb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityUserTokenConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity +{ + public static class IdentityUserTokenConsts + { + public const int MaxLoginProviderLength = 64; + + public const int MaxNameLength = 128; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/IdentityResource.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/IdentityResource.cs new file mode 100644 index 0000000000..4a3c5e85f4 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/IdentityResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Identity.Localization +{ + [LocalizationResourceName("AbpIdentity")] + public class IdentityResource //TODO: Rename to AbpIdentityResource + { + + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs new file mode 100644 index 0000000000..5a59253205 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs @@ -0,0 +1,44 @@ +using System; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Identity; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class AbpIdentityServiceCollectionExtensions + { + public static IdentityBuilder AddAbpIdentity(this IServiceCollection services) + { + return services.AddAbpIdentity(setupAction: null); + } + + public static IdentityBuilder AddAbpIdentity(this IServiceCollection services, Action setupAction) + { + //AbpRoleManager + services.TryAddScoped(); + services.TryAddScoped(typeof(RoleManager), provider => provider.GetService(typeof(IdentityRoleManager))); + + //AbpUserManager + services.TryAddScoped(); + services.TryAddScoped(typeof(UserManager), provider => provider.GetService(typeof(IdentityUserManager))); + + //AbpSecurityStampValidator + services.TryAddScoped(); + services.TryAddScoped(typeof(SecurityStampValidator), provider => provider.GetService(typeof(AbpSecurityStampValidator))); + services.TryAddScoped(typeof(ISecurityStampValidator), provider => provider.GetService(typeof(AbpSecurityStampValidator))); + + //AbpUserStore + services.TryAddScoped(); + services.TryAddScoped(typeof(IUserStore), provider => provider.GetService(typeof(IdentityUserStore))); + + //AbpRoleStore + services.TryAddScoped(); + services.TryAddScoped(typeof(IRoleStore), provider => provider.GetService(typeof(IdentityRoleStore))); + + return services.AddIdentity(setupAction) + .AddDefaultTokenProviders() + .AddClaimsPrincipalFactory(); + //return services.AddIdentityCore(setupAction); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..85b123803e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity")] +[assembly: AssemblyTrademark("")] +[assembly: InternalsVisibleTo("Volo.Abp.Identity.Application.Tests")] +[assembly: InternalsVisibleTo("Volo.Abp.Identity.TestBase.Orm")] +[assembly: InternalsVisibleTo("Volo.Abp.Identity.TestBase")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("17dbb40a-243e-41f7-a672-fa316ecb1e33")] diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj new file mode 100644 index 0000000000..6ab9c806ad --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj @@ -0,0 +1,29 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.Domain + Volo.Abp.Identity.Domain + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj.DotSettings b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityConsts.cs new file mode 100644 index 0000000000..b33a0a1d06 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityConsts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity +{ + public static class AbpIdentityConsts + { + public const string DefaultDbTablePrefix = "Abp"; + + public const string DefaultDbSchema = null; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs new file mode 100644 index 0000000000..ab10ec8412 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Domain; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Settings; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + [DependsOn(typeof(AbpPermissionManagementDomainModule))] + [DependsOn(typeof(AbpDddDomainModule))] + [DependsOn(typeof(AbpIdentityDomainSharedModule))] + [DependsOn(typeof(AbpUsersDomainModule))] + public class AbpIdentityDomainModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.ManagementProviders.Add(); + options.ManagementProviders.Add(); + }); + + services.Configure(options => + { + options.DefinitionProviders.Add(); + }); + + var identityBuilder = services.AddAbpIdentity(options => + { + options.User.RequireUniqueEmail = true; + }); + + services.ExecutePreConfiguredActions(identityBuilder); + + AddAbpIdentityOptionsFactory(services); + + services.AddAssemblyOf(); + } + + private static void AddAbpIdentityOptionsFactory(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Transient, AbpIdentityOptionsFactory>()); + services.Replace(ServiceDescriptor.Scoped, OptionsManager>()); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs new file mode 100644 index 0000000000..c02485f1bc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityOptionsFactory.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.Json; +using Volo.Abp.Options; +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity +{ + public class AbpIdentityOptionsFactory : AbpOptionsFactory + { + private readonly ISettingManager _settingManager; + private readonly IJsonSerializer _jsonSerializer; + + public AbpIdentityOptionsFactory( + IEnumerable> setups, + IEnumerable> postConfigures, + ISettingManager settingManager, + IJsonSerializer jsonSerializer) + : base(setups, postConfigures) + { + _settingManager = settingManager; + _jsonSerializer = jsonSerializer; + } + + public override IdentityOptions Create(string name) + { + var options = base.Create(name); + + SetPasswordOptions(options); + + return options; + } + + protected virtual void SetPasswordOptions(IdentityOptions options) + { + options.Password.RequiredLength = _settingManager.Get(IdentitySettingNames.Password.RequiredLength, options.Password.RequiredLength); + options.Password.RequiredUniqueChars = _settingManager.Get(IdentitySettingNames.Password.RequiredUniqueChars, options.Password.RequiredUniqueChars); + options.Password.RequireNonAlphanumeric = _settingManager.Get(IdentitySettingNames.Password.RequireNonAlphanumeric, options.Password.RequireNonAlphanumeric); + options.Password.RequireLowercase = _settingManager.Get(IdentitySettingNames.Password.RequireLowercase, options.Password.RequireLowercase); + options.Password.RequireUppercase = _settingManager.Get(IdentitySettingNames.Password.RequireUppercase, options.Password.RequireUppercase); + options.Password.RequireDigit = _settingManager.Get(IdentitySettingNames.Password.RequireDigit, options.Password.RequireDigit); + + options.Lockout.AllowedForNewUsers = _settingManager.Get(IdentitySettingNames.Lockout.AllowedForNewUsers, options.Lockout.AllowedForNewUsers); + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromSeconds(_settingManager.Get(IdentitySettingNames.Lockout.LockoutDuration, options.Lockout.DefaultLockoutTimeSpan.TotalSeconds.To())); + options.Lockout.MaxFailedAccessAttempts = _settingManager.Get(IdentitySettingNames.Lockout.MaxFailedAccessAttempts, options.Lockout.MaxFailedAccessAttempts); + + options.SignIn.RequireConfirmedEmail = _settingManager.Get(IdentitySettingNames.SignIn.RequireConfirmedEmail, options.SignIn.RequireConfirmedEmail); + options.SignIn.RequireConfirmedPhoneNumber = _settingManager.Get(IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber, options.SignIn.RequireConfirmedPhoneNumber); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs new file mode 100644 index 0000000000..34604b5418 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentitySettingDefinitionProvider.cs @@ -0,0 +1,28 @@ +using Volo.Abp.Settings; + +namespace Volo.Abp.Identity +{ + public class AbpIdentitySettingDefinitionProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + + new SettingDefinition(IdentitySettingNames.Password.RequiredLength), + new SettingDefinition(IdentitySettingNames.Password.RequiredUniqueChars), + new SettingDefinition(IdentitySettingNames.Password.RequireNonAlphanumeric), + new SettingDefinition(IdentitySettingNames.Password.RequireLowercase), + new SettingDefinition(IdentitySettingNames.Password.RequireUppercase), + new SettingDefinition(IdentitySettingNames.Password.RequireDigit), + + new SettingDefinition(IdentitySettingNames.Lockout.AllowedForNewUsers), + new SettingDefinition(IdentitySettingNames.Lockout.LockoutDuration), + new SettingDefinition(IdentitySettingNames.Lockout.MaxFailedAccessAttempts), + + new SettingDefinition(IdentitySettingNames.SignIn.RequireConfirmedEmail), + new SettingDefinition(IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber) + + ); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpSecurityStampValidator.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpSecurityStampValidator.cs new file mode 100644 index 0000000000..5259047096 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpSecurityStampValidator.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.Uow; + +namespace Volo.Abp.Identity +{ + public class AbpSecurityStampValidator : SecurityStampValidator + { + public AbpSecurityStampValidator( + IOptions options, + SignInManager signInManager, + ISystemClock systemClock) + : base( + options, + signInManager, + systemClock) + { + } + + [UnitOfWork] + public override Task ValidateAsync(CookieValidatePrincipalContext context) + { + return base.ValidateAsync(context); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs new file mode 100644 index 0000000000..8db2b95942 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs @@ -0,0 +1,40 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; +using Volo.Abp.Uow; + +namespace Volo.Abp.Identity +{ + public class AbpUserClaimsPrincipalFactory : UserClaimsPrincipalFactory, ITransientDependency + { + public AbpUserClaimsPrincipalFactory( + UserManager userManager, + RoleManager roleManager, + IOptions options) + : base( + userManager, + roleManager, + options) + { + } + + [UnitOfWork] + public override async Task CreateAsync(IdentityUser user) + { + var principal = await base.CreateAsync(user); + + if (user.TenantId.HasValue) + { + principal.Identities + .First() + .AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())); + } + + return principal; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityDataSeeder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityDataSeeder.cs new file mode 100644 index 0000000000..6c5d9d293c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityDataSeeder.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.Identity +{ + public interface IIdentityDataSeeder + { + Task SeedAsync( + string adminUserPassword, + IEnumerable adminRolePermissions = null, + Guid? tenantId = null); + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs new file mode 100644 index 0000000000..d27de75865 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityRoleRepository.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Identity +{ + public interface IIdentityRoleRepository : IBasicRepository + { + Task FindByNormalizedNameAsync( + string normalizedRoleName, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task GetCountAsync( + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs new file mode 100644 index 0000000000..98269da005 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IIdentityUserRepository.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Domain.Repositories; + +namespace Volo.Abp.Identity +{ + public interface IIdentityUserRepository : IBasicRepository + { + Task FindByNormalizedUserNameAsync( + [NotNull] string normalizedUserName, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + Task> GetRoleNamesAsync( + Guid id, + CancellationToken cancellationToken = default + ); + + Task FindByLoginAsync( + [NotNull] string loginProvider, + [NotNull] string providerKey, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + Task FindByNormalizedEmailAsync( + [NotNull] string normalizedEmail, + bool includeDetails = true, + CancellationToken cancellationToken = default + ); + + Task> GetListByClaimAsync( + Claim claim, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task> GetListByNormalizedRoleNameAsync( + string normalizedRoleName, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task> GetRolesAsync( + Guid id, + bool includeDetails = false, + CancellationToken cancellationToken = default + ); + + Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default + ); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaim.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaim.cs new file mode 100644 index 0000000000..d64218a68f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityClaim.cs @@ -0,0 +1,61 @@ +using System; +using System.Security.Claims; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityClaim : Entity, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the claim type for this claim. + /// + public virtual string ClaimType { get; protected set; } + + /// + /// Gets or sets the claim value for this claim. + /// + public virtual string ClaimValue { get; protected set; } + + protected IdentityClaim() + { + + } + + protected internal IdentityClaim(Guid id, [NotNull] Claim claim, Guid? tenantId) + : this(id, claim.Type, claim.Value, tenantId) + { + + } + + protected internal IdentityClaim(Guid id, [NotNull] string claimType, string claimValue, Guid? tenantId) + { + Check.NotNull(claimType, nameof(claimType)); + + Id = id; + ClaimType = claimType; + ClaimValue = claimValue; + TenantId = tenantId; + } + + /// + /// Creates a Claim instance from this entity. + /// + /// + public virtual Claim ToClaim() + { + return new Claim(ClaimType, ClaimValue); + } + + public virtual void SetClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + ClaimType = claim.Type; + ClaimValue = claim.Value; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDataSeeder.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDataSeeder.cs new file mode 100644 index 0000000000..02e0f320ac --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityDataSeeder.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Uow; + +namespace Volo.Abp.Identity +{ + public class IdentityDataSeeder : ITransientDependency, IIdentityDataSeeder + { + private readonly IGuidGenerator _guidGenerator; + private readonly IPermissionGrantRepository _permissionGrantRepository; + private readonly IIdentityRoleRepository _roleRepository; + private readonly IIdentityUserRepository _userRepository; + private readonly ILookupNormalizer _lookupNormalizer; + private readonly IdentityUserManager _userManager; + private readonly IdentityRoleManager _roleManager; + + public IdentityDataSeeder( + IGuidGenerator guidGenerator, + IPermissionGrantRepository permissionGrantRepository, + IIdentityRoleRepository roleRepository, + IIdentityUserRepository userRepository, + ILookupNormalizer lookupNormalizer, + IdentityUserManager userManager, + IdentityRoleManager roleManager) + { + _guidGenerator = guidGenerator; + _permissionGrantRepository = permissionGrantRepository; + _roleRepository = roleRepository; + _userRepository = userRepository; + _lookupNormalizer = lookupNormalizer; + _userManager = userManager; + _roleManager = roleManager; + } + + [UnitOfWork] + public virtual async Task SeedAsync( + string adminUserPassword, + IEnumerable adminRolePermissions = null, + Guid? tenantId = null) + { + const string adminUserName = "admin"; + const string adminRoleName = "admin"; + + //"admin" user + var adminUser = await _userRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.Normalize(adminUserName)); + if (adminUser != null) + { + return; + } + + adminUser = new IdentityUser(_guidGenerator.Create(), adminUserName, "admin@abp.io", tenantId); + CheckIdentityErrors(await _userManager.CreateAsync(adminUser, adminUserPassword)); + + //"admin" role + var adminRole = await _roleRepository.FindByNormalizedNameAsync(_lookupNormalizer.Normalize(adminRoleName)); + if (adminRole == null) + { + adminRole = new IdentityRole(_guidGenerator.Create(), adminRoleName, tenantId); + CheckIdentityErrors(await _roleManager.CreateAsync(adminRole)); + + if (adminRolePermissions != null) + { + await AddRolePermissionsAsync(adminRole, adminRolePermissions); + } + } + + CheckIdentityErrors(await _userManager.AddToRoleAsync(adminUser, adminRoleName)); + } + + protected virtual async Task AddRolePermissionsAsync(IdentityRole role, IEnumerable permissionNames) + { + foreach (var permissionName in permissionNames) + { + await AddPermissionAsync(permissionName, RolePermissionValueProvider.ProviderName, role.Name, role.TenantId); + } + } + + protected virtual Task AddPermissionAsync(string permissionName, string providerName, string providerKey, Guid? tenantId) + { + return _permissionGrantRepository.InsertAsync( + new PermissionGrant( + _guidGenerator.Create(), + permissionName, + providerName, + providerKey, + tenantId + ) + ); + } + + protected void CheckIdentityErrors(IdentityResult identityResult) //TODO: This is temporary and duplicate code! + { + if (!identityResult.Succeeded) + { + //TODO: A better exception that can be shown on UI as localized? + throw new AbpException("Operation failed: " + identityResult.Errors.Select(e => $"[{e.Code}] {e.Description}").JoinAsString(", ")); + } + + //identityResult.CheckErrors(LocalizationManager); //TODO: Get from old Abp + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs new file mode 100644 index 0000000000..7b720862b0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRole.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Claims; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + /// + /// Represents a role in the identity system + /// + public class IdentityRole : AggregateRoot, IHasConcurrencyStamp, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the name for this role. + /// + public virtual string Name { get; protected internal set; } + + /// + /// Gets or sets the normalized name for this role. + /// + public virtual string NormalizedName { get; protected internal set; } + + /// + /// Navigation property for claims in this role. + /// + public virtual ICollection Claims { get; protected set; } + + /// + /// A random value that should change whenever a role is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } + + /// + /// Initializes a new instance of . + /// + protected IdentityRole() { } + + public IdentityRole(Guid id, [NotNull] string name, Guid? tenantId = null) + { + Check.NotNull(name, nameof(name)); + + Id = id; + Name = name; + TenantId = tenantId; + NormalizedName = name.ToUpperInvariant(); + ConcurrencyStamp = Guid.NewGuid().ToString(); + + Claims = new Collection(); + } + + public virtual void AddClaim([NotNull] IGuidGenerator guidGenerator, [NotNull] Claim claim) + { + Check.NotNull(guidGenerator, nameof(guidGenerator)); + Check.NotNull(claim, nameof(claim)); + + Claims.Add(new IdentityRoleClaim(guidGenerator.Create(), Id, claim, TenantId)); + } + + public virtual void AddClaims([NotNull] IGuidGenerator guidGenerator, [NotNull] IEnumerable claims) + { + Check.NotNull(guidGenerator, nameof(guidGenerator)); + Check.NotNull(claims, nameof(claims)); + + foreach (var claim in claims) + { + AddClaim(guidGenerator, claim); + } + } + + public virtual void RemoveClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.RemoveAll(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value); + } + + public override string ToString() + { + return $"{base.ToString()}, Name = {Name}"; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleClaim.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleClaim.cs new file mode 100644 index 0000000000..70cf33b3d5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleClaim.cs @@ -0,0 +1,50 @@ +using System; +using System.Security.Claims; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + /// + /// Represents a claim that is granted to all users within a role. + /// + public class IdentityRoleClaim : IdentityClaim + { + /// + /// Gets or sets the of the primary key of the role associated with this claim. + /// + public virtual Guid RoleId { get; protected set; } + + protected IdentityRoleClaim() + { + + } + + protected internal IdentityRoleClaim( + Guid id, + Guid roleId, + [NotNull] Claim claim, + Guid? tenantId) + : base( + id, + claim, + tenantId) + { + RoleId = roleId; + } + + protected internal IdentityRoleClaim( + Guid id, + Guid roleId, + [NotNull] string claimType, + string claimValue, + Guid? tenantId) + : base( + id, + claimType, + claimValue, + tenantId) + { + RoleId = roleId; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs new file mode 100644 index 0000000000..e59a056566 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleManager.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Services; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity +{ + public class IdentityRoleManager : RoleManager, IDomainService + { + protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; + + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public IdentityRoleManager( + IdentityRoleStore store, + IEnumerable> roleValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + ILogger logger, + ICancellationTokenProvider cancellationTokenProvider) + : base( + store, + roleValidators, + keyNormalizer, + errors, + logger) + { + _cancellationTokenProvider = cancellationTokenProvider; + } + + public async Task GetByIdAsync(Guid id) + { + var role = await Store.FindByIdAsync(id.ToString(), CancellationToken); + if (role == null) + { + throw new EntityNotFoundException(typeof(IdentityRole), id); + } + + return role; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleRepositoryExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleRepositoryExtensions.cs new file mode 100644 index 0000000000..feb1cb65df --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleRepositoryExtensions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity +{ + public static class IdentityRoleRepositoryExtensions + { + public static IdentityRole FindByNormalizedName(this IIdentityRoleRepository roleRepository, string normalizedRoleName) + { + return AsyncHelper.RunSync(() => roleRepository.FindByNormalizedNameAsync(normalizedRoleName)); + } + + //TODO: Other sync extension methods + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleStore.cs new file mode 100644 index 0000000000..175b051a48 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityRoleStore.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; + +namespace Volo.Abp.Identity +{ + /// + /// Creates a new instance of a persistence store for roles. + /// + public class IdentityRoleStore : + IRoleStore, + IRoleClaimStore, + ITransientDependency + { + private readonly IIdentityRoleRepository _roleRepository; + private readonly ILogger _logger; + private readonly IGuidGenerator _guidGenerator; + + /// + /// Constructs a new instance of . + /// + public IdentityRoleStore( + IIdentityRoleRepository roleRepository, + ILogger logger, + IGuidGenerator guidGenerator, + IdentityErrorDescriber describer = null) + { + _roleRepository = roleRepository; + _logger = logger; + _guidGenerator = guidGenerator; + + ErrorDescriber = describer ?? new IdentityErrorDescriber(); + } + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + /// + /// Creates a new role in a store as an asynchronous operation. + /// + /// The role to create in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task CreateAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.InsertAsync(role, AutoSaveChanges, cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Updates a role in a store as an asynchronous operation. + /// + /// The role to update in the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task UpdateAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + try + { + await _roleRepository.UpdateAsync(role, AutoSaveChanges, cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + _logger.LogWarning(ex.ToString()); //Trigger some AbpHandledException event + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Deletes a role from the store as an asynchronous operation. + /// + /// The role to delete from the store. + /// The used to propagate notifications that the operation should be canceled. + /// A that represents the of the asynchronous query. + public virtual async Task DeleteAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + try + { + await _roleRepository.DeleteAsync(role, AutoSaveChanges, cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + _logger.LogWarning(ex.ToString()); //Trigger some AbpHandledException event + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Gets the ID for a role from the store as an asynchronous operation. + /// + /// The role whose ID should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the ID of the role. + public Task GetRoleIdAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Id.ToString()); + } + + /// + /// Gets the name of a role from the store as an asynchronous operation. + /// + /// The role whose name should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public Task GetRoleNameAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.Name); + } + + /// + /// Sets the name of a role in the store as an asynchronous operation. + /// + /// The role whose name should be set. + /// The name of the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public Task SetRoleNameAsync([NotNull] IdentityRole role, string roleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + role.Name = roleName; + return Task.CompletedTask; + } + + /// + /// Finds the role who has the specified ID as an asynchronous operation. + /// + /// The role ID to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByIdAsync(string id, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _roleRepository.FindAsync(Guid.Parse(id), cancellationToken: cancellationToken); + } + + /// + /// Finds the role who has the specified normalized name as an asynchronous operation. + /// + /// The normalized role name to look for. + /// The used to propagate notifications that the operation should be canceled. + /// A that result of the look up. + public virtual Task FindByNameAsync([NotNull] string normalizedName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(normalizedName, nameof(normalizedName)); + + return _roleRepository.FindByNormalizedNameAsync(normalizedName, cancellationToken: cancellationToken); + } + + /// + /// Get a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the name of the role. + public virtual Task GetNormalizedRoleNameAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + return Task.FromResult(role.NormalizedName); + } + + /// + /// Set a role's normalized name as an asynchronous operation. + /// + /// The role whose normalized name should be set. + /// The normalized name to set + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedRoleNameAsync([NotNull] IdentityRole role, string normalizedName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + role.NormalizedName = normalizedName; + + return Task.CompletedTask; + } + + /// + /// Dispose the stores + /// + public void Dispose() + { + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The role whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a role. + public async Task> GetClaimsAsync([NotNull] IdentityRole role, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + + await _roleRepository.EnsureCollectionLoadedAsync(role, r => r.Claims, cancellationToken); + + return role.Claims.Select(c => c.ToClaim()).ToList(); + } + + /// + /// Adds the given to the specified . + /// + /// The role to add the claim to. + /// The claim to add to the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task AddClaimAsync([NotNull] IdentityRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + await _roleRepository.EnsureCollectionLoadedAsync(role, r => r.Claims, cancellationToken); + + role.AddClaim(_guidGenerator, claim); + } + + /// + /// Removes the given from the specified . + /// + /// The role to remove the claim from. + /// The claim to remove from the role. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task RemoveClaimAsync([NotNull] IdentityRole role, [NotNull] Claim claim, CancellationToken cancellationToken = default) + { + Check.NotNull(role, nameof(role)); + Check.NotNull(claim, nameof(claim)); + + await _roleRepository.EnsureCollectionLoadedAsync(role, r => r.Claims, cancellationToken); + + role.RemoveClaim(claim); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingNames.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingNames.cs new file mode 100644 index 0000000000..aa820777a4 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentitySettingNames.cs @@ -0,0 +1,36 @@ +namespace Volo.Abp.Identity +{ + public static class IdentitySettingNames + { + private const string Prefix = "Abp.Identity"; + + public static class Password + { + private const string PasswordPrefix = Prefix + ".Password"; + + public const string RequiredLength = PasswordPrefix + ".RequiredLength"; + public const string RequiredUniqueChars = PasswordPrefix + ".RequiredUniqueChars"; + public const string RequireNonAlphanumeric = PasswordPrefix + ".RequireNonAlphanumeric"; + public const string RequireLowercase = PasswordPrefix + ".RequireLowercase"; + public const string RequireUppercase = PasswordPrefix + ".RequireUppercase"; + public const string RequireDigit = PasswordPrefix + ".RequireDigit"; + } + + public static class Lockout + { + private const string LockoutPrefix = Prefix + ".Lockout"; + + public const string AllowedForNewUsers = LockoutPrefix + ".AllowedForNewUsers"; + public const string LockoutDuration = LockoutPrefix + ".LockoutDuration"; + public const string MaxFailedAccessAttempts = LockoutPrefix + ".MaxFailedAccessAttempts"; + } + + public static class SignIn + { + private const string SignInPrefix = Prefix + ".SignIn"; + + public const string RequireConfirmedEmail = SignInPrefix + ".RequireConfirmedEmail"; + public const string RequireConfirmedPhoneNumber = SignInPrefix + ".RequireConfirmedPhoneNumber"; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs new file mode 100644 index 0000000000..f0d5662397 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Security.Claims; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Guids; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public class IdentityUser : AggregateRoot, IHasConcurrencyStamp, IUser, IHasExtraProperties + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the user name for this user. + /// + public virtual string UserName { get; protected internal set; } + + /// + /// Gets or sets the normalized user name for this user. + /// + public virtual string NormalizedUserName { get; protected internal set; } + + /// + /// Gets or sets the email address for this user. + /// + public virtual string Email { get; protected internal set; } + + /// + /// Gets or sets the normalized email address for this user. + /// + public virtual string NormalizedEmail { get; protected internal set; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their email address. + /// + /// True if the email address has been confirmed, otherwise false. + public virtual bool EmailConfirmed { get; protected internal set; } + + /// + /// Gets or sets a salted and hashed representation of the password for this user. + /// + public virtual string PasswordHash { get; protected internal set; } + + /// + /// A random value that must change whenever a users credentials change (password changed, login removed) + /// + public virtual string SecurityStamp { get; protected internal set; } + + /// + /// A random value that must change whenever a user is persisted to the store + /// + public virtual string ConcurrencyStamp { get; set; } + + /// + /// Gets or sets a telephone number for the user. + /// + public virtual string PhoneNumber { get; protected internal set; } + + /// + /// Gets or sets a flag indicating if a user has confirmed their telephone address. + /// + /// True if the telephone number has been confirmed, otherwise false. + public virtual bool PhoneNumberConfirmed { get; protected internal set; } + + /// + /// Gets or sets a flag indicating if two factor authentication is enabled for this user. + /// + /// True if 2fa is enabled, otherwise false. + public virtual bool TwoFactorEnabled { get; protected internal set; } + + /// + /// Gets or sets the date and time, in UTC, when any user lockout ends. + /// + /// + /// A value in the past means the user is not locked out. + /// + public virtual DateTimeOffset? LockoutEnd { get; protected internal set; } + + /// + /// Gets or sets a flag indicating if the user could be locked out. + /// + /// True if the user could be locked out, otherwise false. + public virtual bool LockoutEnabled { get; protected internal set; } + + /// + /// Gets or sets the number of failed login attempts for the current user. + /// + public virtual int AccessFailedCount { get; protected internal set; } + + //TODO: Can we make collections readonly collection, which will provide encapsulation. But... can work for all ORMs? + + /// + /// Navigation property for the roles this user belongs to. + /// + public virtual ICollection Roles { get; protected set; } + + /// + /// Navigation property for the claims this user possesses. + /// + public virtual ICollection Claims { get; protected set; } + + /// + /// Navigation property for this users login accounts. + /// + public virtual ICollection Logins { get; protected set; } + + /// + /// Navigation property for this users tokens. + /// + public virtual ICollection Tokens { get; protected set; } + + public Dictionary ExtraProperties { get; protected set; } + + protected IdentityUser() + { + ExtraProperties = new Dictionary(); + } + + public IdentityUser(Guid id, [NotNull] string userName, [NotNull] string email, Guid? tenantId = null) + { + Check.NotNull(userName, nameof(userName)); + + Id = id; + TenantId = tenantId; + UserName = userName; + NormalizedUserName = userName.ToUpperInvariant(); + Email = email; + NormalizedEmail = email.ToUpperInvariant(); + ConcurrencyStamp = Guid.NewGuid().ToString(); + SecurityStamp = Guid.NewGuid().ToString(); + + Roles = new Collection(); + Claims = new Collection(); + Logins = new Collection(); + Tokens = new Collection(); + + ExtraProperties = new Dictionary(); + } + + public virtual void AddRole(Guid roleId) + { + Check.NotNull(roleId, nameof(roleId)); + + if (IsInRole(roleId)) + { + return; + } + + Roles.Add(new IdentityUserRole(Id, roleId, 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); + } + + public virtual void AddClaim([NotNull] IGuidGenerator guidGenerator, [NotNull] Claim claim) + { + Check.NotNull(guidGenerator, nameof(guidGenerator)); + Check.NotNull(claim, nameof(claim)); + + Claims.Add(new IdentityUserClaim(guidGenerator.Create(), Id, claim, TenantId)); + } + + public virtual void AddClaims([NotNull] IGuidGenerator guidGenerator, [NotNull] IEnumerable claims) + { + Check.NotNull(guidGenerator, nameof(guidGenerator)); + Check.NotNull(claims, nameof(claims)); + + foreach (var claim in claims) + { + AddClaim(guidGenerator, claim); + } + } + + public virtual void ReplaceClaim([NotNull] Claim claim, [NotNull] Claim newClaim) + { + Check.NotNull(claim, nameof(claim)); + Check.NotNull(newClaim, nameof(newClaim)); + + var userClaims = Claims.Where(uc => uc.ClaimValue == claim.Value && uc.ClaimType == claim.Type); + foreach (var userClaim in userClaims) + { + userClaim.SetClaim(newClaim); + } + } + + public virtual void RemoveClaims([NotNull] IEnumerable claims) + { + Check.NotNull(claims, nameof(claims)); + + foreach (var claim in claims) + { + RemoveClaim(claim); + } + } + + public virtual void RemoveClaim([NotNull] Claim claim) + { + Check.NotNull(claim, nameof(claim)); + + Claims.RemoveAll(c => c.ClaimValue == claim.Value && c.ClaimType == claim.Type); + } + + public virtual void AddLogin([NotNull] UserLoginInfo login) + { + Check.NotNull(login, nameof(login)); + + Logins.Add(new IdentityUserLogin(Id, login, TenantId)); + } + + public virtual void RemoveLogin([NotNull] string loginProvider, [NotNull] string providerKey) + { + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + Logins.RemoveAll(userLogin => userLogin.LoginProvider == loginProvider && userLogin.ProviderKey == providerKey); + } + + [CanBeNull] + public virtual IdentityUserToken FindToken(string loginProvider, string name) + { + return Tokens.FirstOrDefault(t => t.LoginProvider == loginProvider && t.Name == name); + } + + public virtual void SetToken(string loginProvider, string name, string value) + { + var token = FindToken(loginProvider, name); + if (token == null) + { + Tokens.Add(new IdentityUserToken(Id, loginProvider, name, value, TenantId)); + } + else + { + token.Value = value; + } + } + + public virtual void RemoveToken(string loginProvider, string name) + { + Tokens.RemoveAll(t => t.LoginProvider == loginProvider && t.Name == name); + } + + public override string ToString() + { + return $"{base.ToString()}, UserName = {UserName}"; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserClaim.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserClaim.cs new file mode 100644 index 0000000000..d302beb7c2 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserClaim.cs @@ -0,0 +1,34 @@ +using System; +using System.Security.Claims; +using JetBrains.Annotations; + +namespace Volo.Abp.Identity +{ + /// + /// Represents a claim that a user possesses. + /// + public class IdentityUserClaim : IdentityClaim + { + /// + /// Gets or sets the primary key of the user associated with this claim. + /// + public virtual Guid UserId { get; protected set; } + + protected IdentityUserClaim() + { + + } + + protected internal IdentityUserClaim(Guid id, Guid userId, [NotNull] Claim claim, Guid? tenantId) + : base(id, claim, tenantId) + { + UserId = userId; + } + + protected internal IdentityUserClaim(Guid id, Guid userId, [NotNull] string claimType, string claimValue, Guid? tenantId) + : base(id, claimType, claimValue, tenantId) + { + UserId = userId; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLogin.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLogin.cs new file mode 100644 index 0000000000..f0747708e6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserLogin.cs @@ -0,0 +1,76 @@ +using System; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + /// + /// Represents a login and its associated provider for a user. + /// + public class IdentityUserLogin : Entity, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the of the primary key of the user associated with this login. + /// + public virtual Guid UserId { get; protected set; } + + /// + /// Gets or sets the login provider for the login (e.g. facebook, google) + /// + public virtual string LoginProvider { get; protected set; } + + /// + /// Gets or sets the unique provider identifier for this login. + /// + public virtual string ProviderKey { get; protected set; } + + /// + /// Gets or sets the friendly name used in a UI for this login. + /// + public virtual string ProviderDisplayName { get; protected set; } + + protected IdentityUserLogin() + { + + } + + protected internal IdentityUserLogin( + Guid userId, + [NotNull] string loginProvider, + [NotNull] string providerKey, + string providerDisplayName, + Guid? tenantId) + { + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + UserId = userId; + LoginProvider = loginProvider; + ProviderKey = providerKey; + ProviderDisplayName = providerDisplayName; + TenantId = tenantId; + } + + protected internal IdentityUserLogin( + Guid userId, + [NotNull] UserLoginInfo login, + Guid? tenantId) + : this( + userId, + login.LoginProvider, + login.ProviderKey, + login.ProviderDisplayName, + tenantId) + { + } + + public UserLoginInfo ToUserLoginInfo() + { + return new UserLoginInfo(LoginProvider, ProviderKey, ProviderDisplayName); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs new file mode 100644 index 0000000000..dc4b2fe4b2 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Services; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity +{ + public class IdentityUserManager : UserManager, IDomainService + { + protected override CancellationToken CancellationToken => _cancellationTokenProvider.Token; + + private readonly ICancellationTokenProvider _cancellationTokenProvider; + + public IdentityUserManager( + IdentityUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors, + IServiceProvider services, + ILogger logger, + ICancellationTokenProvider cancellationTokenProvider) + : base( + store, + optionsAccessor, + passwordHasher, + userValidators, + passwordValidators, + keyNormalizer, + errors, + services, + logger) + { + _cancellationTokenProvider = cancellationTokenProvider; + } + + public async Task GetByIdAsync(Guid id) + { + var user = await Store.FindByIdAsync(id.ToString(), CancellationToken); + if (user == null) + { + throw new EntityNotFoundException(typeof(IdentityUser), id); + } + + return user; + } + + public async Task SetRolesAsync([NotNull] IdentityUser user, [NotNull] IEnumerable roleNames) + { + Check.NotNull(user, nameof(user)); + Check.NotNull(roleNames, nameof(roleNames)); + + var currentRoleNames = await GetRolesAsync(user); + + var result = await RemoveFromRolesAsync(user, currentRoleNames.Except(roleNames).Distinct()); + if (!result.Succeeded) + { + return result; + } + + result = await AddToRolesAsync(user, roleNames.Except(currentRoleNames).Distinct()); + if (!result.Succeeded) + { + return result; + } + + return IdentityResult.Success; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExtensions.cs new file mode 100644 index 0000000000..9655caa0e2 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExtensions.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity +{ + public static class IdentityUserRepositoryExtensions + { + public static IdentityUser FindByNormalizedUserName(this IIdentityUserRepository repository, [NotNull] string normalizedUserName) + { + return AsyncHelper.RunSync(() => repository.FindByNormalizedUserNameAsync(normalizedUserName)); + } + + //TODO: Other sync extension methods + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs new file mode 100644 index 0000000000..30611a4a53 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRepositoryExternalUserLookupServiceProvider.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public class IdentityUserRepositoryExternalUserLookupServiceProvider : IExternalUserLookupServiceProvider, ITransientDependency + { + private readonly IIdentityUserRepository _userRepository; + private readonly ILookupNormalizer _lookupNormalizer; + + public IdentityUserRepositoryExternalUserLookupServiceProvider( + IIdentityUserRepository userRepository, + ILookupNormalizer lookupNormalizer) + { + _userRepository = userRepository; + _lookupNormalizer = lookupNormalizer; + } + + public async Task FindByIdAsync( + Guid id, + CancellationToken cancellationToken = default) + { + return ( + await _userRepository.FindAsync( + id, + includeDetails: false, + cancellationToken: cancellationToken + ) + ).ToAbpUserData(); + } + + public async Task FindByUserNameAsync( + string userName, + CancellationToken cancellationToken = default) + { + return ( + await _userRepository.FindByNormalizedUserNameAsync( + _lookupNormalizer.Normalize(userName), + includeDetails: false, + cancellationToken: cancellationToken + ) + ).ToAbpUserData(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRole.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRole.cs new file mode 100644 index 0000000000..d684bded4e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserRole.cs @@ -0,0 +1,36 @@ +using System; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + /// + /// Represents the link between a user and a role. + /// + public class IdentityUserRole : Entity, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the primary key of the user that is linked to a role. + /// + public virtual Guid UserId { get; protected set; } + + /// + /// Gets or sets the primary key of the role that is linked to the user. + /// + public virtual Guid RoleId { get; protected set; } + + protected IdentityUserRole() + { + + } + + protected internal IdentityUserRole(Guid userId, Guid roleId, Guid? tenantId) + { + UserId = userId; + RoleId = roleId; + TenantId = tenantId; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs new file mode 100644 index 0000000000..e9646d38cf --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserStore.cs @@ -0,0 +1,1108 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Guids; + +namespace Volo.Abp.Identity +{ + /// + /// Represents a new instance of a persistence store for the specified user and role types. + /// + public class IdentityUserStore : + IUserLoginStore, + IUserRoleStore, + IUserClaimStore, + IUserPasswordStore, + IUserSecurityStampStore, + IUserEmailStore, + IUserLockoutStore, + IUserPhoneNumberStore, + IUserTwoFactorStore, + IUserAuthenticationTokenStore, + IUserAuthenticatorKeyStore, + IUserTwoFactorRecoveryCodeStore, + ITransientDependency + { + private const string InternalLoginProvider = "[AspNetUserStore]"; + private const string AuthenticatorKeyTokenName = "AuthenticatorKey"; + private const string RecoveryCodeTokenName = "RecoveryCodes"; + + /// + /// Gets or sets the for any error that occurred with the current operation. + /// + public IdentityErrorDescriber ErrorDescriber { get; set; } + + /// + /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, UpdateAsync and DeleteAsync are called. + /// + /// + /// True if changes should be automatically persisted, otherwise false. + /// + public bool AutoSaveChanges { get; set; } = true; + + private readonly IIdentityRoleRepository _roleRepository; + private readonly IGuidGenerator _guidGenerator; + private readonly ILogger _logger; + private readonly IIdentityUserRepository _userRepository; + + public IdentityUserStore( + IIdentityUserRepository userRepository, + IIdentityRoleRepository roleRepository, + IGuidGenerator guidGenerator, + ILogger logger, + IdentityErrorDescriber describer = null) + { + _userRepository = userRepository; + _roleRepository = roleRepository; + _guidGenerator = guidGenerator; + _logger = logger; + + ErrorDescriber = describer ?? new IdentityErrorDescriber(); + } + + /// + /// Gets the user identifier for the specified . + /// + /// The user whose identifier should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the identifier for the specified . + public virtual Task GetUserIdAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.Id.ToString()); + } + + /// + /// Gets the user name for the specified . + /// + /// The user whose name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the name for the specified . + public virtual Task GetUserNameAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.UserName); + } + + /// + /// Sets the given for the specified . + /// + /// The user whose name should be set. + /// The user name to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetUserNameAsync([NotNull] IdentityUser user, string userName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.UserName = userName; + + return Task.CompletedTask; + } + + /// + /// Gets the normalized user name for the specified . + /// + /// The user whose normalized name should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the normalized user name for the specified . + public virtual Task GetNormalizedUserNameAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.NormalizedUserName); + } + + /// + /// Sets the given normalized name for the specified . + /// + /// The user whose name should be set. + /// The normalized name to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetNormalizedUserNameAsync([NotNull] IdentityUser user, string normalizedName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.NormalizedUserName = normalizedName; + + return Task.CompletedTask; + } + + /// + /// Creates the specified in the user store. + /// + /// The user to create. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the creation operation. + public virtual async Task CreateAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.InsertAsync(user, AutoSaveChanges, cancellationToken); + + return IdentityResult.Success; + } + + /// + /// Updates the specified in the user store. + /// + /// The user to update. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public virtual async Task UpdateAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + try + { + await _userRepository.UpdateAsync(user, AutoSaveChanges, cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + _logger.LogWarning(ex.ToString()); //Trigger some AbpHandledException event + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Deletes the specified from the user store. + /// + /// The user to delete. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the of the update operation. + public virtual async Task DeleteAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + try + { + await _userRepository.DeleteAsync(user, AutoSaveChanges, cancellationToken); + } + catch (AbpDbConcurrencyException ex) + { + _logger.LogWarning(ex.ToString()); //Trigger some AbpHandledException event + return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure()); + } + + return IdentityResult.Success; + } + + /// + /// Finds and returns a user, if any, who has the specified . + /// + /// The user ID to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public virtual Task FindByIdAsync(string userId, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _userRepository.FindAsync(Guid.Parse(userId), cancellationToken: cancellationToken); + } + + /// + /// Finds and returns a user, if any, who has the specified normalized user name. + /// + /// The normalized user name to search for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing the user matching the specified if it exists. + /// + public virtual Task FindByNameAsync([NotNull] string normalizedUserName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(normalizedUserName, nameof(normalizedUserName)); + + return _userRepository.FindByNormalizedUserNameAsync(normalizedUserName, includeDetails: false, cancellationToken: cancellationToken); + } + + /// + /// Sets the password hash for a user. + /// + /// The user to set the password hash for. + /// The password hash to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPasswordHashAsync([NotNull] IdentityUser user, string passwordHash, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.PasswordHash = passwordHash; + + return Task.CompletedTask; + } + + /// + /// Gets the password hash for a user. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the password hash for the user. + public virtual Task GetPasswordHashAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.PasswordHash); + } + + /// + /// Returns a flag indicating if the specified user has a password. + /// + /// The user to retrieve the password hash for. + /// The used to propagate notifications that the operation should be canceled. + /// A containing a flag indicating if the specified user has a password. If the + /// user has a password the returned value with be true, otherwise it will be false. + public virtual Task HasPasswordAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.PasswordHash != null); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the role to. + /// The role to add. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddToRoleAsync([NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(normalizedRoleName, nameof(normalizedRoleName)); + + 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); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the role from. + /// The role to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveFromRoleAsync([NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + 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; + } + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Roles, cancellationToken); + + user.RemoveRole(role.Id); + } + + /// + /// Retrieves the roles the specified is a member of. + /// + /// The user whose roles should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the roles the user is a member of. + public virtual async Task> GetRolesAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return await _userRepository.GetRoleNamesAsync(user.Id, cancellationToken: cancellationToken); + } + + /// + /// Returns a flag indicating if the specified user is a member of the give . + /// + /// The user whose role membership should be checked. + /// The role to check membership of + /// The used to propagate notifications that the operation should be canceled. + /// A 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. + public virtual async Task IsInRoleAsync([NotNull] IdentityUser user, [NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + 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); + + return user.IsInRole(role.Id); + } + + /// + /// Get the claims associated with the specified as an asynchronous operation. + /// + /// The user whose claims should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// A that contains the claims granted to a user. + public virtual async Task> GetClaimsAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Claims, cancellationToken); + + return user.Claims.Select(c => c.ToClaim()).ToList(); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the claim to. + /// The claim to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddClaimsAsync([NotNull] IdentityUser user, [NotNull] IEnumerable claims, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claims, nameof(claims)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Claims, cancellationToken); + + user.AddClaims(_guidGenerator, claims); + } + + /// + /// Replaces the on the specified , with the . + /// + /// The user to replace the claim on. + /// The claim replace. + /// The new claim replacing the . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task ReplaceClaimAsync([NotNull] IdentityUser user, [NotNull] Claim claim, [NotNull] Claim newClaim, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claim, nameof(claim)); + Check.NotNull(newClaim, nameof(newClaim)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Claims, cancellationToken); + + user.ReplaceClaim(claim, newClaim); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the claims from. + /// The claim to remove. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveClaimsAsync([NotNull] IdentityUser user, [NotNull] IEnumerable claims, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(claims, nameof(claims)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Claims, cancellationToken); + + user.RemoveClaims(claims); + } + + /// + /// Adds the given to the specified . + /// + /// The user to add the login to. + /// The login to add to the user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task AddLoginAsync([NotNull] IdentityUser user, [NotNull] UserLoginInfo login, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(login, nameof(login)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Logins, cancellationToken); + + user.AddLogin(login); + } + + /// + /// Removes the given from the specified . + /// + /// The user to remove the login from. + /// The login to remove from the user. + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task RemoveLoginAsync([NotNull] IdentityUser user, [NotNull] string loginProvider, [NotNull] string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Logins, cancellationToken); + + user.RemoveLogin(loginProvider, providerKey); + } + + /// + /// Retrieves the associated logins for the specified . + /// + /// The user whose associated logins to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing a list of for the specified , if any. + /// + public virtual async Task> GetLoginsAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Logins, cancellationToken); + + return user.Logins.Select(l => l.ToUserLoginInfo()).ToList(); + } + + /// + /// Retrieves the user associated with the specified login provider and login provider key.. + /// + /// The login provider who provided the . + /// The key provided by the to identify a user. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The for the asynchronous operation, containing the user, if any which matched the specified login provider and key. + /// + public virtual Task FindByLoginAsync([NotNull] string loginProvider, [NotNull] string providerKey, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(providerKey, nameof(providerKey)); + + return _userRepository.FindByLoginAsync(loginProvider, providerKey, cancellationToken: cancellationToken); + } + + /// + /// Gets a flag indicating whether the email address for the specified has been verified, true if the email address is verified otherwise + /// false. + /// + /// The user whose email confirmation status should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous operation, a flag indicating whether the email address for the specified + /// has been confirmed or not. + /// + public virtual Task GetEmailConfirmedAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.EmailConfirmed); + } + + /// + /// Sets the flag indicating whether the specified 's email address has been confirmed or not. + /// + /// The user whose email confirmation status should be set. + /// A flag indicating if the email address has been confirmed, true if the address is confirmed otherwise false. + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetEmailConfirmedAsync([NotNull] IdentityUser user, bool confirmed, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.EmailConfirmed = confirmed; + + return Task.CompletedTask; + } + + /// + /// Sets the address for a . + /// + /// The user whose email should be set. + /// The email to set. + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetEmailAsync([NotNull] IdentityUser user, string email, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.Email = email; + + return Task.CompletedTask; + } + + /// + /// Gets the email address for the specified . + /// + /// The user whose email should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// The task object containing the results of the asynchronous operation, the email address for the specified . + public virtual Task GetEmailAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.Email); + } + + /// + /// Returns the normalized email for the specified . + /// + /// The user whose email address to retrieve. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the normalized email address if any associated with the specified user. + /// + public virtual Task GetNormalizedEmailAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.NormalizedEmail); + } + + /// + /// Sets the normalized email for the specified . + /// + /// The user whose email address to set. + /// The normalized email to set for the specified . + /// The used to propagate notifications that the operation should be canceled. + /// The task object representing the asynchronous operation. + public virtual Task SetNormalizedEmailAsync([NotNull] IdentityUser user, string normalizedEmail, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.NormalizedEmail = normalizedEmail; + + return Task.CompletedTask; + } + + /// + /// Gets the user, if any, associated with the specified, normalized email address. + /// + /// The normalized email address to return the user for. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The task object containing the results of the asynchronous lookup operation, the user if any associated with the specified normalized email address. + /// + public virtual Task FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _userRepository.FindByNormalizedEmailAsync(normalizedEmail, includeDetails: false, cancellationToken: cancellationToken); + } + + /// + /// Gets the last a user's last lockout expired, if any. + /// Any time in the past should be indicates a user is not locked out. + /// + /// The user whose lockout date should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// A that represents the result of the asynchronous query, a containing the last time + /// a user's lockout expired, if any. + /// + public virtual Task GetLockoutEndDateAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.LockoutEnd); + } + + /// + /// Locks out a user until the specified end date has passed. Setting a end date in the past immediately unlocks a user. + /// + /// The user whose lockout date should be set. + /// The after which the 's lockout should end. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetLockoutEndDateAsync([NotNull] IdentityUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.LockoutEnd = lockoutEnd; + + return Task.CompletedTask; + } + + /// + /// Records that a failed access has occurred, incrementing the failed access count. + /// + /// The user whose cancellation count should be incremented. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the incremented failed access count. + public virtual Task IncrementAccessFailedCountAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.AccessFailedCount++; + + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Resets a user's failed access count. + /// + /// The user whose failed access count should be reset. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + /// This is typically called after the account is successfully accessed. + public virtual Task ResetAccessFailedCountAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.AccessFailedCount = 0; + + return Task.CompletedTask; + } + + /// + /// Retrieves the current failed access count for the specified .. + /// + /// The user whose failed access count should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the failed access count. + public virtual Task GetAccessFailedCountAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.AccessFailedCount); + } + + /// + /// Retrieves a flag indicating whether user lockout can enabled for the specified user. + /// + /// The user whose ability to be locked out should be returned. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, true if a user can be locked out, otherwise false. + /// + public virtual Task GetLockoutEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.LockoutEnabled); + } + + /// + /// Set the flag indicating if the specified can be locked out.. + /// + /// The user whose ability to be locked out should be set. + /// A flag indicating if lock out can be enabled for the specified . + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetLockoutEnabledAsync([NotNull] IdentityUser user, bool enabled, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.LockoutEnabled = enabled; + + return Task.CompletedTask; + } + + /// + /// Sets the telephone number for the specified . + /// + /// The user whose telephone number should be set. + /// The telephone number to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPhoneNumberAsync([NotNull] IdentityUser user, string phoneNumber, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.PhoneNumber = phoneNumber; + + return Task.CompletedTask; + } + + /// + /// Gets the telephone number, if any, for the specified . + /// + /// The user whose telephone number should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the user's telephone number, if any. + public virtual Task GetPhoneNumberAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.PhoneNumber); + } + + /// + /// Gets a flag indicating whether the specified 's telephone number has been confirmed. + /// + /// The user to return a flag for, indicating whether their telephone number is confirmed. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, returning true if the specified has a confirmed + /// telephone number otherwise false. + /// + public virtual Task GetPhoneNumberConfirmedAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.PhoneNumberConfirmed); + } + + /// + /// Sets a flag indicating if the specified 's phone number has been confirmed.. + /// + /// The user whose telephone number confirmation status should be set. + /// A flag indicating whether the user's telephone number has been confirmed. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetPhoneNumberConfirmedAsync([NotNull] IdentityUser user, bool confirmed, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.PhoneNumberConfirmed = confirmed; + + return Task.CompletedTask; + } + + /// + /// Sets the provided security for the specified . + /// + /// The user whose security stamp should be set. + /// The security stamp to set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetSecurityStampAsync([NotNull] IdentityUser user, string stamp, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.SecurityStamp = stamp; + + return Task.CompletedTask; + } + + /// + /// Get the security stamp for the specified . + /// + /// The user whose security stamp should be set. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation, containing the security stamp for the specified . + public virtual Task GetSecurityStampAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.SecurityStamp); + } + + /// + /// Sets a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be set. + /// A flag indicating whether the specified has two factor authentication enabled. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual Task SetTwoFactorEnabledAsync([NotNull] IdentityUser user, bool enabled, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + user.TwoFactorEnabled = enabled; + + return Task.CompletedTask; + } + + /// + /// Returns a flag indicating whether the specified has two factor authentication enabled or not, + /// as an asynchronous operation. + /// + /// The user whose two factor authentication enabled status should be set. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The that represents the asynchronous operation, containing a flag indicating whether the specified + /// has two factor authentication enabled or not. + /// + public virtual Task GetTwoFactorEnabledAsync([NotNull] IdentityUser user, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + return Task.FromResult(user.TwoFactorEnabled); + } + + /// + /// Retrieves all users with the specified claim. + /// + /// The claim whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that contain the specified claim. + /// + public virtual async Task> GetUsersForClaimAsync([NotNull] Claim claim, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(claim, nameof(claim)); + + return await _userRepository.GetListByClaimAsync(claim, cancellationToken: cancellationToken); + } + + /// + /// Retrieves all users in the specified role. + /// + /// The role whose users should be retrieved. + /// The used to propagate notifications that the operation should be canceled. + /// + /// The contains a list of users, if any, that are in the specified role. + /// + public virtual async Task> GetUsersInRoleAsync([NotNull] string normalizedRoleName, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (string.IsNullOrEmpty(normalizedRoleName)) + { + throw new ArgumentNullException(nameof(normalizedRoleName)); + } + + return await _userRepository.GetListByNormalizedRoleNameAsync(normalizedRoleName, cancellationToken: cancellationToken); + } + + /// + /// Sets the token value for a particular user. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The value of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public virtual async Task SetTokenAsync([NotNull] IdentityUser user, string loginProvider, string name, string value, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, cancellationToken); + + user.SetToken(loginProvider, name, value); + } + + /// + /// Deletes a token for a user. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task RemoveTokenAsync(IdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, cancellationToken); + + user.RemoveToken(loginProvider, name); + } + + /// + /// Returns the token value. + /// + /// The user. + /// The authentication provider for the token. + /// The name of the token. + /// The used to propagate notifications that the operation should be canceled. + /// The that represents the asynchronous operation. + public async Task GetTokenAsync(IdentityUser user, string loginProvider, string name, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + await _userRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, cancellationToken); + + return user.FindToken(loginProvider, name)?.Value; + } + + public Task SetAuthenticatorKeyAsync(IdentityUser user, string key, CancellationToken cancellationToken) + { + return SetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, key, cancellationToken); + } + + public Task GetAuthenticatorKeyAsync(IdentityUser user, CancellationToken cancellationToken) + { + return GetTokenAsync(user, InternalLoginProvider, AuthenticatorKeyTokenName, cancellationToken); + } + + /// + /// Returns how many recovery code are still valid for a user. + /// + /// The user who owns the recovery code. + /// The used to propagate notifications that the operation should be canceled. + /// The number of valid recovery codes for the user.. + public virtual async Task CountCodesAsync(IdentityUser user, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + + var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? ""; + if (mergedCodes.Length > 0) + { + return mergedCodes.Split(';').Length; + } + + return 0; + } + + /// + /// Updates the recovery codes for the user while invalidating any previous recovery codes. + /// + /// The user to store new recovery codes for. + /// The new recovery codes for the user. + /// The used to propagate notifications that the operation should be canceled. + /// The new recovery codes for the user. + public virtual Task ReplaceCodesAsync(IdentityUser user, IEnumerable recoveryCodes, CancellationToken cancellationToken) + { + var mergedCodes = string.Join(";", recoveryCodes); + return SetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, mergedCodes, cancellationToken); + } + + /// + /// Returns whether a recovery code is valid for a user. Note: recovery codes are only valid + /// once, and will be invalid after use. + /// + /// The user who owns the recovery code. + /// The recovery code to use. + /// The used to propagate notifications that the operation should be canceled. + /// True if the recovery code was found for the user. + public virtual async Task RedeemCodeAsync(IdentityUser user, string code, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + Check.NotNull(user, nameof(user)); + Check.NotNull(code, nameof(code)); + + var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken) ?? ""; + var splitCodes = mergedCodes.Split(';'); + if (splitCodes.Contains(code)) + { + var updatedCodes = new List(splitCodes.Where(s => s != code)); + await ReplaceCodesAsync(user, updatedCodes, cancellationToken); + return true; + } + return false; + } + + public void Dispose() + { + + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserToken.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserToken.cs new file mode 100644 index 0000000000..8950d47e28 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserToken.cs @@ -0,0 +1,57 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Domain.Entities; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + /// + /// Represents an authentication token for a user. + /// + public class IdentityUserToken : Entity, IMultiTenant + { + public virtual Guid? TenantId { get; protected set; } + + /// + /// Gets or sets the primary key of the user that the token belongs to. + /// + public virtual Guid UserId { get; protected set; } + + /// + /// Gets or sets the LoginProvider this token is from. + /// + public virtual string LoginProvider { get; protected set; } + + /// + /// Gets or sets the name of the token. + /// + public virtual string Name { get; protected set; } + + /// + /// Gets or sets the token value. + /// + public virtual string Value { get; set; } + + protected IdentityUserToken() + { + + } + + protected internal IdentityUserToken( + Guid userId, + [NotNull] string loginProvider, + [NotNull] string name, + string value, + Guid? tenantId) + { + Check.NotNull(loginProvider, nameof(loginProvider)); + Check.NotNull(name, nameof(name)); + + UserId = userId; + LoginProvider = loginProvider; + Name = name; + Value = value; + TenantId = tenantId; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RolePermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RolePermissionManagementProvider.cs new file mode 100644 index 0000000000..1cb505e3c7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/RolePermissionManagementProvider.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + public class RolePermissionManagementProvider : PermissionManagementProvider + { + public override string Name => RolePermissionValueProvider.ProviderName; + + private readonly IIdentityUserRepository _identityUserRepository; + + public RolePermissionManagementProvider( + IPermissionGrantRepository permissionGrantRepository, + IGuidGenerator guidGenerator, + IIdentityUserRepository identityUserRepository, + ICurrentTenant currentTenant) + : base( + permissionGrantRepository, + guidGenerator, + currentTenant) + { + _identityUserRepository = identityUserRepository; + } + + public override async Task CheckAsync(string name, string providerName, string providerKey) + { + if (providerName == Name) + { + return new PermissionValueProviderGrantInfo( + await PermissionGrantRepository.FindAsync(name, providerName, providerKey) != null, + providerKey + ); + } + + if (providerName == "User") + { + var userId = Guid.Parse(providerKey); + var roleNames = await _identityUserRepository.GetRoleNamesAsync(userId); + + foreach (var roleName in roleNames) + { + var permissionGrant = await PermissionGrantRepository.FindAsync(name, Name, roleName); + if (permissionGrant != null) + { + return new PermissionValueProviderGrantInfo(true, roleName); + } + } + } + + return PermissionValueProviderGrantInfo.NonGranted; + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserPermissionManagementProvider.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserPermissionManagementProvider.cs new file mode 100644 index 0000000000..d65c6ca760 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/UserPermissionManagementProvider.cs @@ -0,0 +1,24 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + public class UserPermissionManagementProvider : PermissionManagementProvider + { + public override string Name => UserPermissionValueProvider.ProviderName; + + public UserPermissionManagementProvider(IPermissionGrantRepository + permissionGrantRepository, + IGuidGenerator guidGenerator, + ICurrentTenant currentTenant) + : base( + permissionGrantRepository, + guidGenerator, + currentTenant) + { + + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/RolePermissionManagerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/RolePermissionManagerExtensions.cs new file mode 100644 index 0000000000..e73c9343ec --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/RolePermissionManagerExtensions.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Permissions +{ + public static class RolePermissionManagerExtensions + { + public static Task GetForRoleAsync([NotNull] this IPermissionManager permissionManager, string roleName, string permissionName) + { + Check.NotNull(permissionManager, nameof(permissionManager)); + + return permissionManager.GetAsync(permissionName, RolePermissionValueProvider.ProviderName, roleName); + } + + public static Task> GetAllForRoleAsync([NotNull] this IPermissionManager permissionManager, string roleName) + { + Check.NotNull(permissionManager, nameof(permissionManager)); + + return permissionManager.GetAllAsync(RolePermissionValueProvider.ProviderName, roleName); + } + + public static Task SetForRoleAsync([NotNull] this IPermissionManager permissionManager, string roleName, [NotNull] string permissionName, bool isGranted) + { + Check.NotNull(permissionManager, nameof(permissionManager)); + + return permissionManager.SetAsync(permissionName, RolePermissionValueProvider.ProviderName, roleName, isGranted); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/UserPermissionManagerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/UserPermissionManagerExtensions.cs new file mode 100644 index 0000000000..510332010e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Permissions/UserPermissionManagerExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Permissions +{ + public static class UserPermissionManagerExtensions + { + public static Task> GetAllForUserAsync([NotNull] this IPermissionManager permissionManager, Guid userId) + { + Check.NotNull(permissionManager, nameof(permissionManager)); + + return permissionManager.GetAllAsync(UserPermissionValueProvider.ProviderName, userId.ToString()); + } + + public static Task SetForUserAsync([NotNull] this IPermissionManager permissionManager, Guid userId, [NotNull] string name, bool isGranted) + { + Check.NotNull(permissionManager, nameof(permissionManager)); + + return permissionManager.SetAsync(name, UserPermissionValueProvider.ProviderName, userId.ToString(), isGranted); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a55409d7fd --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.EntityFrameworkCore")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("439dfc0f-1ba2-464f-900e-ea7e18c08975")] diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj new file mode 100644 index 0000000000..889056794c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.EntityFrameworkCore + Volo.Abp.Identity.EntityFrameworkCore + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj.DotSettings b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo.Abp.Identity.EntityFrameworkCore.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs new file mode 100644 index 0000000000..bb8f8c582a --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Users.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + [DependsOn( + typeof(AbpIdentityDomainModule), + typeof(AbpUsersEntityFrameworkCoreModule))] + public class AbpIdentityEntityFrameworkCoreModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAbpDbContext(options => + { + options.AddRepository(); + options.AddRepository(); + }); + + services.AddAssemblyOf(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs new file mode 100644 index 0000000000..ddce85ac1c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityRoleRepository.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class EfCoreIdentityRoleRepository : EfCoreRepository, IIdentityRoleRepository + { + public EfCoreIdentityRoleRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public virtual async Task FindByNormalizedNameAsync( + string normalizedRoleName, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken)); + } + + public virtual async Task> 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(IdentityRole.Name)) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task GetCountAsync(CancellationToken cancellationToken = default) + { + return await this.LongCountAsync(GetCancellationToken(cancellationToken)); + } + + public override IQueryable WithDetails() + { + return GetQueryable().IncludeDetails(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs new file mode 100644 index 0000000000..fed25edeb7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserRepository.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Domain.Repositories.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class EfCoreIdentityUserRepository : EfCoreRepository, IIdentityUserRepository + { + public EfCoreIdentityUserRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public virtual async Task FindByNormalizedUserNameAsync( + string normalizedUserName, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync( + u => u.NormalizedUserName == normalizedUserName, + GetCancellationToken(cancellationToken) + ); + } + + public virtual async Task> GetRoleNamesAsync( + Guid id, + CancellationToken cancellationToken = default) + { + var query = from userRole in DbContext.Set() + join role in DbContext.Roles on userRole.RoleId equals role.Id + where userRole.UserId == id + select role.Name; + + return await query.ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task FindByLoginAsync( + string loginProvider, + string providerKey, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .Where(u => u.Logins.Any(login => login.LoginProvider == loginProvider && login.ProviderKey == providerKey)) + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task FindByNormalizedEmailAsync( + string normalizedEmail, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListByClaimAsync( + Claim claim, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .Where(u => u.Claims.Any(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListByNormalizedRoleNameAsync( + string normalizedRoleName, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var role = await DbContext.Roles + .Where(x => x.NormalizedName == normalizedRoleName) + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + + if (role == null) + { + return new List(); + } + + return await DbSet + .IncludeDetails(includeDetails) + .Where(u => u.Roles.Any(r => r.RoleId == role.Id)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await DbSet + .IncludeDetails(includeDetails) + .WhereIf( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) + ) + .OrderBy(sorting ?? nameof(IdentityUser.UserName)) + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task> GetRolesAsync( + Guid id, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = from userRole in DbContext.Set() + join role in DbContext.Roles.IncludeDetails(includeDetails) on userRole.RoleId equals role.Id + where userRole.UserId == id + select role; + + return await query.ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await this.WhereIf( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) + ) + .LongCountAsync(GetCancellationToken(cancellationToken)); + } + + public override IQueryable WithDetails() + { + return GetQueryable().IncludeDetails(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs new file mode 100644 index 0000000000..5b8503f163 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IIdentityDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + [ConnectionStringName("AbpIdentity")] + public interface IIdentityDbContext : IEfCoreDbContext + { + DbSet Users { get; set; } + + DbSet Roles { get; set; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs new file mode 100644 index 0000000000..15eab5cc08 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContext.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + /// + /// Base class for the Entity Framework database context used for identity. + /// + [ConnectionStringName("AbpIdentity")] + public class IdentityDbContext : AbpDbContext, IIdentityDbContext + { + public static string TablePrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix; + + public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema; + + public DbSet Users { get; set; } + + public DbSet Roles { get; set; } + + public IdentityDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigureIdentity(options => + { + options.TablePrefix = TablePrefix; + options.Schema = Schema; + }); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs new file mode 100644 index 0000000000..d306b16714 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs @@ -0,0 +1,117 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Modeling; +using Volo.Abp.Users.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public static class IdentityDbContextModelBuilderExtensions + { + public static void ConfigureIdentity( + [NotNull] this ModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new IdentityModelBuilderConfigurationOptions(); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Users", options.Schema); + + b.ConfigureAbpUser(options); + + b.ConfigureExtraProperties(); + + b.Property(u => u.NormalizedUserName).IsRequired().HasMaxLength(IdentityUserConsts.MaxNormalizedUserNameLength).HasColumnName(nameof(IdentityUser.NormalizedUserName)); + b.Property(u => u.NormalizedEmail).HasMaxLength(IdentityUserConsts.MaxNormalizedEmailLength).HasColumnName(nameof(IdentityUser.NormalizedEmail)); + b.Property(u => u.PasswordHash).HasMaxLength(IdentityUserConsts.MaxPasswordHashLength).HasColumnName(nameof(IdentityUser.PasswordHash)); + b.Property(u => u.SecurityStamp).IsRequired().HasMaxLength(IdentityUserConsts.MaxSecurityStampLength).HasColumnName(nameof(IdentityUser.SecurityStamp)); + b.Property(u => u.ConcurrencyStamp).IsRequired().HasMaxLength(IdentityUserConsts.MaxConcurrencyStampLength).HasColumnName(nameof(IdentityUser.ConcurrencyStamp)); + b.Property(u => u.TwoFactorEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.TwoFactorEnabled)); + b.Property(u => u.LockoutEnabled).HasDefaultValue(false).HasColumnName(nameof(IdentityUser.LockoutEnabled)); + b.Property(u => u.AccessFailedCount).HasDefaultValue(0).HasColumnName(nameof(IdentityUser.AccessFailedCount)); + + b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired(); + 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.HasIndex(u => u.NormalizedUserName); + b.HasIndex(u => u.NormalizedEmail); + b.HasIndex(u => u.UserName); + b.HasIndex(u => u.Email); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "UserClaims", options.Schema); + + b.Property(uc => uc.ClaimType).HasMaxLength(IdentityUserClaimConsts.MaxClaimTypeLength).IsRequired(); + b.Property(uc => uc.ClaimValue).HasMaxLength(IdentityUserClaimConsts.MaxClaimValueLength); + + b.HasIndex(uc => uc.UserId); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "UserRoles", options.Schema); + + b.HasKey(ur => new { ur.UserId, ur.RoleId }); + + b.HasOne().WithMany().HasForeignKey(ur => ur.RoleId).IsRequired(); + b.HasOne().WithMany(u => u.Roles).HasForeignKey(ur => ur.UserId).IsRequired(); + + b.HasIndex(ur => new { ur.RoleId, ur.UserId }); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "UserLogins", options.Schema); + + b.HasKey(x => new { x.UserId, x.LoginProvider }); + + b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserLoginConsts.MaxLoginProviderLength).IsRequired(); + b.Property(ul => ul.ProviderKey).HasMaxLength(IdentityUserLoginConsts.MaxProviderKeyLength).IsRequired(); + b.Property(ul => ul.ProviderDisplayName).HasMaxLength(IdentityUserLoginConsts.MaxProviderDisplayNameLength); + + b.HasIndex(l => new { l.LoginProvider, l.ProviderKey }); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "UserTokens", options.Schema); + + b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name }); + + b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserTokenConsts.MaxLoginProviderLength).IsRequired(); + b.Property(ul => ul.LoginProvider).HasMaxLength(IdentityUserTokenConsts.MaxNameLength).IsRequired(); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "Roles", options.Schema); + + b.Property(r => r.Name).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNameLength); + b.Property(r => r.NormalizedName).IsRequired().HasMaxLength(IdentityRoleConsts.MaxNormalizedNameLength); + + b.HasMany(r => r.Claims).WithOne().HasForeignKey(rc => rc.RoleId).IsRequired(); + + b.HasIndex(r => r.NormalizedName); + }); + + builder.Entity(b => + { + b.ToTable(options.TablePrefix + "RoleClaims", options.Schema); + + b.Property(uc => uc.ClaimType).HasMaxLength(IdentityRoleClaimConsts.MaxClaimTypeLength).IsRequired(); + b.Property(uc => uc.ClaimValue).HasMaxLength(IdentityRoleClaimConsts.MaxClaimValueLength); + + b.HasIndex(uc => uc.RoleId); + }); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs new file mode 100644 index 0000000000..8cd598c673 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityEfCoreQueryableExtensions.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public static class IdentityEfCoreQueryableExtensions + { + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.Roles) + .Include(x => x.Logins) + .Include(x => x.Claims) + .Include(x => x.Tokens); + } + + public static IQueryable IncludeDetails(this IQueryable queryable, bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(x => x.Claims); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityModelBuilderConfigurationOptions.cs b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityModelBuilderConfigurationOptions.cs new file mode 100644 index 0000000000..09e1747372 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityModelBuilderConfigurationOptions.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions + { + public IdentityModelBuilderConfigurationOptions( + [NotNull] string tablePrefix = AbpIdentityConsts.DefaultDbTablePrefix, + [CanBeNull] string schema = AbpIdentityConsts.DefaultDbSchema) + : base( + tablePrefix, + schema) + { + + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d408ff5633 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.HttpApi.Client")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("00b240b6-ec44-461a-9578-ef4f1be9c688")] diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj new file mode 100644 index 0000000000..eee1eacc86 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj @@ -0,0 +1,24 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.HttpApi.Client + Volo.Abp.Identity.HttpApi.Client + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj.DotSettings b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo.Abp.Identity.HttpApi.Client.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/AbpIdentityHttpApiClientModule.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/AbpIdentityHttpApiClientModule.cs new file mode 100644 index 0000000000..b54712f521 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/AbpIdentityHttpApiClientModule.cs @@ -0,0 +1,23 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.Client; +using Volo.Abp.Modularity; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + [DependsOn( + typeof(AbpIdentityApplicationContractsModule), + typeof(AbpUsersAbstractionModule), + typeof(AbpHttpClientModule))] + public class AbpIdentityHttpApiClientModule : AbpModule + { + public const string RemoteServiceName = "AbpIdentity"; + + public override void ConfigureServices(IServiceCollection services) + { + services.AddHttpClientProxies(typeof(AbpIdentityApplicationContractsModule).Assembly, RemoteServiceName); + + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs new file mode 100644 index 0000000000..b6f58277d6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/HttpClientIdentityUserLookupService.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + [Dependency(TryRegister = true)] + public class HttpClientExternalUserLookupServiceProvider : IExternalUserLookupServiceProvider, ITransientDependency + { + private readonly IIdentityUserAppService _userAppService; + + public HttpClientExternalUserLookupServiceProvider(IIdentityUserAppService userAppService) + { + _userAppService = userAppService; + } + + public async Task FindByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + //TODO: Should return null if not found! + return (await _userAppService.GetAsync(id)).ToUserInfo(); + } + + public async Task FindByUserNameAsync(string userName, CancellationToken cancellationToken = default) + { + //TODO: Should return null if not found! + //TODO: Search by UserName, not by a general filter! + return (await _userAppService.GetListAsync(new GetIdentityUsersInput { Filter = userName })).Items.FirstOrDefault()?.ToUserInfo(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/IdentityUserExtensions.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/IdentityUserExtensions.cs new file mode 100644 index 0000000000..3535bb5904 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi.Client/Volo/Abp/Identity/IdentityUserExtensions.cs @@ -0,0 +1,20 @@ +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public static class IdentityUserDtoExtensions + { + public static IUserData ToUserInfo(this IdentityUserDto user) + { + return new UserData( + user.Id, + user.UserName, + user.Email, + user.EmailConfirmed, + user.PhoneNumber, + user.PhoneNumberConfirmed, + user.TenantId + ); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/AssemblyInfo.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0964ce2598 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.HttpApi")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("57fca6cb-9d99-411e-8abf-20acfbd61d61")] diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj new file mode 100644 index 0000000000..8278b3f7f8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo.Abp.Identity.HttpApi.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.HttpApi + Volo.Abp.Identity.HttpApi + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs new file mode 100644 index 0000000000..69891d1ccb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/AbpIdentityHttpApiModule.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity +{ + [DependsOn(typeof(AbpIdentityApplicationContractsModule), typeof(AbpAspNetCoreMvcModule))] + public class AbpIdentityHttpApiModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs new file mode 100644 index 0000000000..cae2b321d4 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityRoleController.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + [RemoteService] + [Area("identity")] + [Controller] + [ControllerName("Role")] + public class IdentityRoleController : IIdentityRoleAppService, ITransientDependency + { + private readonly IIdentityRoleAppService _roleAppService; + + public IdentityRoleController(IIdentityRoleAppService roleAppService) + { + _roleAppService = roleAppService; + } + + public virtual Task GetAsync(Guid id) + { + return _roleAppService.GetAsync(id); + } + + public virtual Task> GetListAsync(GetIdentityRolesInput input) + { + return _roleAppService.GetListAsync(input); + } + + public virtual Task CreateAsync(IdentityRoleCreateDto input) + { + return _roleAppService.CreateAsync(input); + } + + public virtual Task UpdateAsync(Guid id, IdentityRoleUpdateDto input) + { + return _roleAppService.UpdateAsync(id, input); + } + + public virtual Task DeleteAsync(Guid id) + { + return _roleAppService.DeleteAsync(id); + } + + public virtual Task> GetAllListAsync() + { + return _roleAppService.GetAllListAsync(); + } + + public virtual Task GetPermissionsAsync(Guid id) + { + return _roleAppService.GetPermissionsAsync(id); + } + + public virtual Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input) + { + return _roleAppService.UpdatePermissionsAsync(id, input); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs new file mode 100644 index 0000000000..795ba7a1b3 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.HttpApi/Volo/Abp/Identity/IdentityUserController.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.DependencyInjection; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + [RemoteService] + [Area("identity")] + [Controller] + [ControllerName("User")] + public class IdentityUserController : IIdentityUserAppService, ITransientDependency //TODO: Try to implement these type wrapper controllers automatically! + { + private readonly IIdentityUserAppService _userAppService; + + public IdentityUserController(IIdentityUserAppService userAppService) + { + _userAppService = userAppService; + } + + public virtual Task GetAsync(Guid id) + { + return _userAppService.GetAsync(id); + } + + public virtual Task> GetListAsync(GetIdentityUsersInput input) + { + return _userAppService.GetListAsync(input); + } + + public virtual Task CreateAsync(IdentityUserCreateDto input) + { + return _userAppService.CreateAsync(input); + } + + public virtual Task UpdateAsync(Guid id, IdentityUserUpdateDto input) + { + return _userAppService.UpdateAsync(id, input); + } + + public virtual Task DeleteAsync(Guid id) + { + return _userAppService.DeleteAsync(id); + } + + public virtual Task> GetRolesAsync(Guid id) + { + return _userAppService.GetRolesAsync(id); + } + + public virtual Task UpdateRolesAsync(Guid id, IdentityUserUpdateRolesDto input) + { + return _userAppService.UpdateRolesAsync(id, input); + } + + public virtual Task GetPermissionsAsync(Guid id) + { + return _userAppService.GetPermissionsAsync(id); + } + + public virtual Task UpdatePermissionsAsync(Guid id, UpdatePermissionsDto input) + { + return _userAppService.UpdatePermissionsAsync(id, input); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj new file mode 100644 index 0000000000..a1feedb35d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.MongoDB + Volo.Abp.Identity.MongoDB + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj.DotSettings b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj.DotSettings new file mode 100644 index 0000000000..58ad6c8854 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo.Abp.Identity.MongoDB.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp71 \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs new file mode 100644 index 0000000000..8036db7fc8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityBsonClassMap.cs @@ -0,0 +1,28 @@ +using MongoDB.Bson.Serialization; +using Volo.Abp.MongoDB; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity.MongoDB +{ + public static class AbpIdentityBsonClassMap + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + BsonClassMap.RegisterClassMap(map => + { + map.AutoMap(); + map.ConfigureExtraProperties(); + }); + + BsonClassMap.RegisterClassMap(map => + { + map.AutoMap(); + }); + }); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs new file mode 100644 index 0000000000..14e81278bd --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContext.cs @@ -0,0 +1,26 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + [ConnectionStringName("AbpIdentity")] + public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbContext + { + public static string CollectionPrefix { get; set; } = AbpIdentityConsts.DefaultDbTablePrefix; + + public IMongoCollection Users => Collection(); + + public IMongoCollection Roles => Collection(); + + protected override void CreateModel(IMongoModelBuilder modelBuilder) + { + base.CreateModel(modelBuilder); + + modelBuilder.ConfigureIdentity(options => + { + options.CollectionPrefix = CollectionPrefix; + }); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs new file mode 100644 index 0000000000..767ba55fc7 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbContextExtensions.cs @@ -0,0 +1,29 @@ +using System; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public static class AbpIdentityMongoDbContextExtensions + { + public static void ConfigureIdentity( + this IMongoModelBuilder builder, + Action optionsAction = null) + { + Check.NotNull(builder, nameof(builder)); + + var options = new IdentityMongoModelBuilderConfigurationOptions(); + + optionsAction?.Invoke(options); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "Users"; + }); + + builder.Entity(b => + { + b.CollectionName = options.CollectionPrefix + "Roles"; + }); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs new file mode 100644 index 0000000000..426f2c78cd --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; +using Volo.Abp.Users.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + [DependsOn( + typeof(AbpIdentityDomainModule), + typeof(AbpUsersMongoDbModule) + )] + public class AbpIdentityMongoDbModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + AbpIdentityBsonClassMap.Configure(); + + services.AddMongoDbContext(options => + { + options.AddRepository(); + options.AddRepository(); + }); + + services.AddAssemblyOf(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs new file mode 100644 index 0000000000..cb344e0fc8 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IAbpIdentityMongoDbContext.cs @@ -0,0 +1,14 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + [ConnectionStringName("AbpIdentity")] + public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext + { + IMongoCollection Users { get; } + + IMongoCollection Roles { get; } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IdentityMongoModelBuilderConfigurationOptions.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IdentityMongoModelBuilderConfigurationOptions.cs new file mode 100644 index 0000000000..60ab8ef57b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/IdentityMongoModelBuilderConfigurationOptions.cs @@ -0,0 +1,12 @@ +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityMongoModelBuilderConfigurationOptions : MongoModelBuilderConfigurationOptions + { + public IdentityMongoModelBuilderConfigurationOptions() + : base(AbpIdentityConsts.DefaultDbTablePrefix) + { + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs new file mode 100644 index 0000000000..1686579c0f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityRoleRepository.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using System.Linq.Dynamic.Core; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public class MongoIdentityRoleRepository : MongoDbRepository, IIdentityRoleRepository + { + public MongoIdentityRoleRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindByNormalizedNameAsync( + string normalizedRoleName, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable().FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName, GetCancellationToken(cancellationToken)); + } + + public async Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .OrderBy(sorting ?? nameof(IdentityRole.Name)) + .As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync(CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .LongCountAsync(GetCancellationToken(cancellationToken)); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs new file mode 100644 index 0000000000..c9187953da --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoIdentityUserRepository.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.Domain.Repositories.MongoDB; +using Volo.Abp.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + public class MongoIdentityUserRepository : MongoDbRepository, IIdentityUserRepository + { + public MongoIdentityUserRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + + } + + public async Task FindByNormalizedUserNameAsync( + string normalizedUserName, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .FirstOrDefaultAsync( + u => u.NormalizedUserName == normalizedUserName, + GetCancellationToken(cancellationToken) + ); + } + + public async Task> GetRoleNamesAsync( + Guid id, + CancellationToken cancellationToken = default) + { + var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken)); + 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)); + } + + public async Task FindByLoginAsync( + string loginProvider, + string providerKey, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .Where(u=> u.Logins.Any(login => login.LoginProvider == loginProvider && login.ProviderKey == providerKey)) + .FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + } + + public async Task FindByNormalizedEmailAsync( + string normalizedEmail, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable().FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, GetCancellationToken(cancellationToken)); + } + + public async Task> GetListByClaimAsync( + Claim claim, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .Where(u => u.Claims.Any(c => c.ClaimType == claim.Type && c.ClaimValue == claim.Value)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task> GetListByNormalizedRoleNameAsync( + string normalizedRoleName, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var role = await DbContext.Roles.AsQueryable().Where(x => x.NormalizedName == normalizedRoleName).FirstOrDefaultAsync(GetCancellationToken(cancellationToken)); + + if (role == null) + { + return new List(); + } + + return await GetMongoQueryable() + .Where(u => u.Roles.Any(r => r.RoleId == role.Id)) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task> GetListAsync( + string sorting = null, + int maxResultCount = int.MaxValue, + int skipCount = 0, + string filter = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) + ) + .OrderBy(sorting ?? nameof(IdentityUser.UserName)) + .As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task> GetRolesAsync( + Guid id, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var user = await GetAsync(id, cancellationToken: GetCancellationToken(cancellationToken)); + var roleIds = user.Roles.Select(r => r.RoleId).ToArray(); + return await DbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id)).ToListAsync(GetCancellationToken(cancellationToken)); + } + + public async Task GetCountAsync( + string filter = null, + CancellationToken cancellationToken = default) + { + return await GetMongoQueryable() + .WhereIf>( + !filter.IsNullOrWhiteSpace(), + u => + u.UserName.Contains(filter) || + u.Email.Contains(filter) + ) + .LongCountAsync(GetCancellationToken(cancellationToken)); + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs new file mode 100644 index 0000000000..c8651d8e88 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using Volo.Abp.Identity.Web.Pages.Identity.Roles; +using CreateUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel; +using EditUserModalModel = Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel; + +namespace Volo.Abp.Identity.Web +{ + public class AbpIdentityWebAutoMapperProfile : Profile + { + public AbpIdentityWebAutoMapperProfile() + { + CreateUserMappings(); + CreateRoleMappings(); + } + + private void CreateUserMappings() + { + //List + CreateMap(); + + //CreateModal + CreateMap() + .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); + + //EditModal + CreateMap() + .ForMember(dest => dest.RoleNames, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.IsAssigned, opt => opt.Ignore()); + } + + private void CreateRoleMappings() + { + //List + CreateMap(); + + //CreateModal + CreateMap(); + + //EditModal + CreateMap(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs new file mode 100644 index 0000000000..286324ec14 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -0,0 +1,72 @@ +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AutoMapper; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Identity.Web.Navigation; +using Volo.Abp.Localization; +using Volo.Abp.Localization.Resources.AbpValidation; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Web; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.Identity.Web +{ + [DependsOn(typeof(AbpIdentityHttpApiModule))] + [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))] + [DependsOn(typeof(AbpAutoMapperModule))] + [DependsOn(typeof(AbpPermissionManagementWebModule))] + public class AbpIdentityWebModule : AbpModule + { + public override void PreConfigureServices(IServiceCollection services) + { + services.PreConfigure(options => + { + options.AddAssemblyResource(typeof(IdentityResource), typeof(AbpIdentityWebModule).Assembly); + }); + } + + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.MenuContributors.Add(new AbpIdentityWebMainMenuContributor()); + }); + + services.Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.Identity.Web"); + }); + + services.Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpValidationResource), + typeof(AbpUiResource) + ).AddVirtualJson("/Localization/Resources/AbpIdentity"); + }); + + services.Configure(options => + { + options.AddProfile(validate: true); + }); + + services.Configure(options => + { + options.Conventions.AuthorizePage("/Identity/Users/Index", IdentityPermissions.Users.Default); + options.Conventions.AuthorizePage("/Identity/Users/CreateModal", IdentityPermissions.Users.Create); + options.Conventions.AuthorizePage("/Identity/Users/EditModal", IdentityPermissions.Users.Update); + options.Conventions.AuthorizePage("/Identity/Roles/Index", IdentityPermissions.Roles.Default); + options.Conventions.AuthorizePage("/Identity/Roles/CreateModal", IdentityPermissions.Roles.Create); + options.Conventions.AuthorizePage("/Identity/Roles/EditModal", IdentityPermissions.Roles.Update); + }); + + services.AddAssemblyOf(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json new file mode 100644 index 0000000000..03090d76bc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/en.json @@ -0,0 +1,22 @@ +{ + "culture": "en", + "texts": { + "Menu:IdentityManagement": "Identity management", + "Users": "Users", + "NewUser": "New user", + "UserName": "User name", + "EmailAddress": "Email address", + "PhoneNumber": "Phone number", + "UserInformations": "User informations", + "Roles": "Roles", + "Password": "Password", + "UserDeletionConfirmationMessage": "User '{0}' will be deleted. Do you confirm that?", + "RoleDeletionConfirmationMessage": "Role '{0}' will be deleted. Do you confirm that?", + "TwoFactorVerification": "Two factor verification", + "AccountLockoutOnFailedLoginAttempts": "Locking account after failed login attempts", + "NewRole": "New role", + "RoleName": "Role name", + "CreationTime": "Creation time", + "Permissions": "Permissions" + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json new file mode 100644 index 0000000000..1e78519459 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Localization/Resources/AbpIdentity/tr.json @@ -0,0 +1,22 @@ +{ + "culture": "tr", + "texts": { + "Menu:IdentityManagement": "Kimlik yönetimi", + "Users": "Kullanıcılar", + "NewUser": "Yeni kullanıcı", + "UserName": "Kullanıcı adı", + "EmailAddress": "E-posta adresi", + "PhoneNumber": "Telefon numarası", + "UserInformations": "Kullanıcı bilgileri", + "Roles": "Roller", + "Password": "Şifre", + "UserDeletionConfirmationMessage": "{0} kullanıcısı silinecektir. Onaylıyor musunuz?", + "RoleDeletionConfirmationMessage": "'{0}' rolü silinecektir. Onaylıyor musunuz?", + "TwoFactorVerification": "İki aşamalı doğrumala", + "AccountLockoutOnFailedLoginAttempts": "Başarısız giriş denemeleri sonrası hesabı kilitleme", + "NewRole": "Yeni rol", + "RoleName": "Rol adı", + "CreationTime": "Oluşturma zamanı", + "Permissions": "İzinler" + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Navigation/AbpIdentityWebMainMenuContributor.cs b/modules/identity/src/Volo.Abp.Identity.Web/Navigation/AbpIdentityWebMainMenuContributor.cs new file mode 100644 index 0000000000..9e471c9b33 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Navigation/AbpIdentityWebMainMenuContributor.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.Identity.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Identity.Web.Navigation +{ + public class AbpIdentityWebMainMenuContributor : IMenuContributor + { + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return; + } + + var authorizationService = context.ServiceProvider.GetRequiredService(); + var l = context.ServiceProvider.GetRequiredService>(); + + var identityMenuItem = new ApplicationMenuItem("Identity", l["Menu:IdentityManagement"]); + context.Menu.AddItem(identityMenuItem); + + if (await authorizationService.IsGrantedAsync(IdentityPermissions.Roles.Default)) + { + identityMenuItem.AddItem(new ApplicationMenuItem("Roles", l["Roles"], url: "/Identity/Roles")); + } + + if (await authorizationService.IsGrantedAsync(IdentityPermissions.Users.Default)) + { + identityMenuItem.AddItem(new ApplicationMenuItem("Users", l["Users"], url: "/Identity/Users")); + } + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml new file mode 100644 index 0000000000..281532a3eb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml @@ -0,0 +1,18 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Identity.Localization +@model Volo.Abp.Identity.Web.Pages.Identity.Roles.CreateModalModel +@inject IHtmlLocalizer L +@{ + Layout = null; +} +
+ + + + + + + +
diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs new file mode 100644 index 0000000000..4523719784 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/CreateModal.cshtml.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Roles +{ + public class CreateModalModel : AbpPageModel + { + [BindProperty] + public RoleInfoModel Role { get; set; } + + private readonly IIdentityRoleAppService _identityRoleAppService; + + public CreateModalModel(IIdentityRoleAppService identityRoleAppService) + { + _identityRoleAppService = identityRoleAppService; + } + + public async Task OnPostAsync() + { + ValidateModel(); + + var input = ObjectMapper.Map(Role); + await _identityRoleAppService.CreateAsync(input); + + return NoContent(); + } + + public class RoleInfoModel + { + [Required] + [StringLength(IdentityRoleConsts.MaxNameLength)] + [Display(Name = "RoleName")] + public string Name { get; set; } + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml new file mode 100644 index 0000000000..7e309aefd6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml @@ -0,0 +1,19 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Identity.Localization +@model Volo.Abp.Identity.Web.Pages.Identity.Roles.EditModalModel +@inject IHtmlLocalizer L +@{ + Layout = null; +} +
+ + + + + + + + +
\ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs new file mode 100644 index 0000000000..2977e12445 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/EditModal.cshtml.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Roles +{ + public class EditModalModel : AbpPageModel + { + [BindProperty] + public RoleInfoModel Role { get; set; } + + private readonly IIdentityRoleAppService _identityRoleAppService; + + public EditModalModel(IIdentityRoleAppService identityRoleAppService) + { + _identityRoleAppService = identityRoleAppService; + } + + public async Task OnGetAsync(Guid id) + { + Role = ObjectMapper.Map( + await _identityRoleAppService.GetAsync(id) + ); + } + + public async Task OnPostAsync() + { + ValidateModel(); + + var input = ObjectMapper.Map(Role); + await _identityRoleAppService.UpdateAsync(Role.Id, input); + + return NoContent(); + } + + public class RoleInfoModel + { + [HiddenInput] + public Guid Id { get; set; } + + [Required] + [StringLength(IdentityRoleConsts.MaxNameLength)] + [Display(Name = "RoleName")] + public string Name { get; set; } + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml new file mode 100644 index 0000000000..c4767c7386 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml @@ -0,0 +1,42 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Identity.Localization +@using Volo.Abp.Identity.Web.Pages.Identity.Roles +@model IndexModel +@inject IHtmlLocalizer L +@section styles { + + + +} +@section scripts { + + + + +} + + +
+
+

@L["Roles"]

+
+
+ +
+
+
+ + + + + + + + +
@L["Actions"]@L["RoleName"]
+
+
\ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml.cs new file mode 100644 index 0000000000..11e84ed0b0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Roles/Index.cshtml.cs @@ -0,0 +1,11 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Roles +{ + public class IndexModel : AbpPageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml new file mode 100644 index 0000000000..8a23679f0b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml @@ -0,0 +1,35 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Identity.Localization +@model Volo.Abp.Identity.Web.Pages.Identity.Users.CreateModalModel +@inject IHtmlLocalizer L +@{ + Layout = null; +} +
+ + + + + + + + + + + + + + @for (var i = 0; i < Model.Roles.Length; i++) + { + + + } + + +
+
+ +
+
\ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs new file mode 100644 index 0000000000..5a15bb40be --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/CreateModal.cshtml.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Users +{ + public class CreateModalModel : AbpPageModel + { + [BindProperty] + public UserInfoViewModel UserInfo { get; set; } + + [BindProperty] + public AssignedRoleViewModel[] Roles { get; set; } + + private readonly IIdentityUserAppService _identityUserAppService; + private readonly IIdentityRoleAppService _identityRoleAppService; + + public CreateModalModel(IIdentityUserAppService identityUserAppService, IIdentityRoleAppService identityRoleAppService) + { + _identityUserAppService = identityUserAppService; + _identityRoleAppService = identityRoleAppService; + } + + public async Task OnGetAsync() + { + UserInfo = new UserInfoViewModel(); + + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>( + await _identityRoleAppService.GetAllListAsync() + ); + } + + public async Task OnPostAsync() + { + ValidateModel(); + + var input = ObjectMapper.Map(UserInfo); + input.RoleNames = Roles.Where(r => r.IsAssigned).Select(r => r.Name).ToArray(); + + await _identityUserAppService.CreateAsync(input); + + return NoContent(); + } + + public class UserInfoViewModel + { + [Required] + [StringLength(IdentityUserConsts.MaxUserNameLength)] + [Display(Name = "UserName")] + public string UserName { get; set; } + + [Required] + [StringLength(IdentityUserConsts.MaxPasswordLength)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Required] + [EmailAddress] + [StringLength(IdentityUserConsts.MaxEmailLength)] + [Display(Name = "EmailAddress")] + public string Email { get; set; } + + [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [Display(Name = "PhoneNumber")] + public string PhoneNumber { get; set; } + + [Display(Name = "TwoFactorVerification")] + public bool TwoFactorEnabled { get; set; } = true; + + [Display(Name = "AccountLockoutOnFailedLoginAttempts")] + public bool LockoutEnabled { get; set; } = true; + } + + public class AssignedRoleViewModel + { + [Required] + [HiddenInput] + public string Name { get; set; } + + public bool IsAssigned { get; set; } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml new file mode 100644 index 0000000000..ffc6eb3e7e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml @@ -0,0 +1,35 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.Identity.Localization +@model Volo.Abp.Identity.Web.Pages.Identity.Users.EditModalModel +@inject IHtmlLocalizer L +@{ + Layout = null; +} +
+ + + + + + + + + + + + + + @for (var i = 0; i < Model.Roles.Length; i++) + { + + + } + + +
+
+ +
+
\ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs new file mode 100644 index 0000000000..73a0779691 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/EditModal.cshtml.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Users +{ + public class EditModalModel : AbpPageModel + { + [BindProperty] + public UserInfoViewModel UserInfo { get; set; } + + [BindProperty] + public AssignedRoleViewModel[] Roles { get; set; } + + private readonly IIdentityUserAppService _identityUserAppService; + private readonly IIdentityRoleAppService _identityRoleAppService; + + public EditModalModel(IIdentityUserAppService identityUserAppService, IIdentityRoleAppService identityRoleAppService) + { + _identityUserAppService = identityUserAppService; + _identityRoleAppService = identityRoleAppService; + } + + public async Task OnGetAsync(Guid id) + { + UserInfo = ObjectMapper.Map(await _identityUserAppService.GetAsync(id)); + + Roles = ObjectMapper.Map, AssignedRoleViewModel[]>( + await _identityRoleAppService.GetAllListAsync() + ); + + var userRoleNames = (await _identityUserAppService.GetRolesAsync(UserInfo.Id)).Items.Select(r => r.Name).ToList(); + foreach (var role in Roles) + { + if (userRoleNames.Contains(role.Name)) + { + role.IsAssigned = true; + } + } + } + + public async Task OnPostAsync() + { + ValidateModel(); + + var input = ObjectMapper.Map(UserInfo); + input.RoleNames = Roles.Where(r => r.IsAssigned).Select(r => r.Name).ToArray(); + await _identityUserAppService.UpdateAsync(UserInfo.Id, input); + + return NoContent(); + } + + public class UserInfoViewModel + { + [HiddenInput] + public Guid Id { get; set; } + + [Required] + [StringLength(IdentityUserConsts.MaxUserNameLength)] + [Display(Name = "UserName")] + public string UserName { get; set; } + + [Required] + [EmailAddress] + [StringLength(IdentityUserConsts.MaxEmailLength)] + [Display(Name = "EmailAddress")] + public string Email { get; set; } + + [StringLength(IdentityUserConsts.MaxPhoneNumberLength)] + [Display(Name = "PhoneNumber")] + public string PhoneNumber { get; set; } + + [Display(Name = "TwoFactorVerification")] + public bool TwoFactorEnabled { get; set; } + + [Display(Name = "AccountLockoutOnFailedLoginAttempts")] + public bool LockoutEnabled { get; set; } + } + + public class AssignedRoleViewModel + { + [Required] + [HiddenInput] + public string Name { get; set; } + + public bool IsAssigned { get; set; } + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml new file mode 100644 index 0000000000..36e658362e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml @@ -0,0 +1,52 @@ +@page +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Identity +@using Volo.Abp.Identity.Localization +@using Volo.Abp.Identity.Web.Pages.Identity.Users +@model IndexModel +@inject IHtmlLocalizer L +@inject IAuthorizationService Authorization +@section styles { + + + +} + +@section scripts { + + + + +} + + + +
+
+

@L["Users"]

+
+
+ @if (await Authorization.IsGrantedAsync(IdentityPermissions.Users.Create)) + { + + } +
+
+
+ + + + + + + + + + +
@L["Actions"]@L["UserName"]@L["EmailAddress"]@L["PhoneNumber"]
+
+
\ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml.cs b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml.cs new file mode 100644 index 0000000000..fa1e8e16af --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/Users/Index.cshtml.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; + +namespace Volo.Abp.Identity.Web.Pages.Identity.Users +{ + public class IndexModel : AbpPageModel + { + public void OnGet() + { + + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/_ViewImports.cshtml b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/_ViewImports.cshtml new file mode 100644 index 0000000000..225780c2c2 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Pages/Identity/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Permissions/IdentityPermissionAppServiceGateway.cs b/modules/identity/src/Volo.Abp.Identity.Web/Permissions/IdentityPermissionAppServiceGateway.cs new file mode 100644 index 0000000000..00762f8c11 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Permissions/IdentityPermissionAppServiceGateway.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.PermissionManagement; +using Volo.Abp.PermissionManagement.Web.Pages.AbpPermissionManagement; + +namespace Volo.Abp.Identity.Web.Permissions +{ + //TODO: Instead of creating such a gateway/adapter, we can implement a common interface for app services, like IHasPermissionManagementApi and manage it dynamically! + + public class IdentityPermissionAppServiceGateway : IPermissionAppServiceGateway, ITransientDependency + { + private readonly IIdentityUserAppService _userAppService; + private readonly IIdentityRoleAppService _roleAppService; + + public IdentityPermissionAppServiceGateway(IIdentityUserAppService userAppService, IIdentityRoleAppService roleAppService) + { + _userAppService = userAppService; + _roleAppService = roleAppService; + } + + public virtual async Task GetAsync(string providerName, string providerKey) + { + switch (providerName) + { + case UserPermissionValueProvider.ProviderName: + return await _userAppService.GetPermissionsAsync(Guid.Parse(providerKey)); + case RolePermissionValueProvider.ProviderName: + return await _roleAppService.GetPermissionsAsync(Guid.Parse(providerKey)); + default: + throw new NotImplementedException(); + } + } + + public virtual async Task UpdateAsync(string providerName, string providerKey, UpdatePermissionsDto input) + { + switch (providerName) + { + case UserPermissionValueProvider.ProviderName: + await _userAppService.UpdatePermissionsAsync(Guid.Parse(providerKey), input); + break; + case RolePermissionValueProvider.ProviderName: + await _roleAppService.UpdatePermissionsAsync(Guid.Parse(providerKey), input); + break; + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json b/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json new file mode 100644 index 0000000000..9a8b023b55 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:55588/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Volo.Abp.Identity.Web": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:55590/" + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj new file mode 100644 index 0000000000..ed4c6cc947 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/Volo.Abp.Identity.Web.csproj @@ -0,0 +1,32 @@ + + + + + + netstandard2.0 + Volo.Abp.Identity.Web + Volo.Abp.Identity.Web + true + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + Library + + + + + + + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json b/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json new file mode 100644 index 0000000000..3f5b37134d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json @@ -0,0 +1,10 @@ +[ + { + "outputFile": "wwwroot/pages/identity/users/index.css", + "inputFile": "wwwroot/pages/identity/users/index.less" + }, + { + "outputFile": "wwwroot/pages/identity/users/index.es5.js", + "inputFile": "wwwroot/pages/identity/users/index.js" + } +] \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json.defaults b/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json.defaults new file mode 100644 index 0000000000..c75eb7d519 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/compilerconfig.json.defaults @@ -0,0 +1,49 @@ +{ + "compilers": { + "less": { + "autoPrefix": "", + "cssComb": "none", + "ieCompat": true, + "strictMath": false, + "strictUnits": false, + "relativeUrls": true, + "rootPath": "", + "sourceMapRoot": "", + "sourceMapBasePath": "", + "sourceMap": false + }, + "sass": { + "includePath": "", + "indentType": "space", + "indentWidth": 2, + "outputStyle": "nested", + "Precision": 5, + "relativeUrls": true, + "sourceMapRoot": "", + "sourceMap": false + }, + "stylus": { + "sourceMap": false + }, + "babel": { + "sourceMap": false + }, + "coffeescript": { + "bare": false, + "runtimeMode": "node", + "sourceMap": false + } + }, + "minifiers": { + "css": { + "enabled": true, + "termSemicolons": true, + "gzip": false + }, + "javascript": { + "enabled": true, + "termSemicolons": true, + "gzip": false + } + } +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.css b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.css new file mode 100644 index 0000000000..41fbd4eaf0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.css @@ -0,0 +1,21 @@ +.dataTable { + width: 100% !important; + border-spacing: 0 !important; +} +.table td, +.table th { + padding: 8px 10px; +} +.dataTable tbody tr td button { + cursor: pointer; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li { + cursor: pointer; + padding: 5px; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li a { + display: block; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover { + background: #f4f5f8; +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.js new file mode 100644 index 0000000000..bc6107abbc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.js @@ -0,0 +1,6 @@ +'use strict'; + +$(function () { + $('#IdentityUsersTable').DataTable(); +}); + diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.min.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.min.js new file mode 100644 index 0000000000..9a2c19267e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.es5.min.js @@ -0,0 +1 @@ +"use strict";$(function(){$("#IdentityUsersTable").DataTable()}); \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.js new file mode 100644 index 0000000000..22f7d9cf8e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.js @@ -0,0 +1,90 @@ +(function ($) { + + var l = abp.localization.getResource('AbpIdentity'); + + var _identityRoleAppService = volo.abp.identity.identityRole; + var _permissionsModal = new abp.ModalManager(abp.appPath + 'AbpPermissionManagement/PermissionManagementModal'); + var _editModal = new abp.ModalManager(abp.appPath + 'Identity/Roles/EditModal'); + var _createModal = new abp.ModalManager(abp.appPath + 'Identity/Roles/CreateModal'); + + $(function () { + + var _$wrapper = $('#IdentityRolesWrapper'); + var _$table = _$wrapper.find('table'); + + var _dataTable = _$table.DataTable({ + order: [[1, "asc"]], + ajax: abp.libs.datatables.createAjax(_identityRoleAppService.getList), + columnDefs: [ + { + targets: 0, + data: null, + orderable: false, + autoWidth: false, + defaultContent: '', + rowAction: { + text: ' ' + l('Actions') + ' ', + items: + [ + { + text: l('Edit'), + visible: function () { + return true; + }, + action: function (data) { + _editModal.open({ + id: data.record.id + }); + } + }, + { + text: l('Permissions'), + visible: function () { + return true; + }, + action: function (data) { + _permissionsModal.open({ + providerName: 'Role', + providerKey: data.record.id + }); + } + }, + { + text: l('Delete'), + visible: function () { + return true; + }, + confirmMessage: function (data) { return l('RoleDeletionConfirmationMessage', data.record.name)}, + action: function (data) { + _identityRoleAppService + .delete(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + targets: 1, + data: "name" + } + ] + }); + + _createModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _editModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _$wrapper.find('button[name=CreateRole]').click(function (e) { + e.preventDefault(); + _createModal.open(); + }); + }); + +})(jQuery); diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.less b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.less new file mode 100644 index 0000000000..7534888421 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.less @@ -0,0 +1,38 @@ +.dataTable { + width: 100% !important; + border-spacing: 0 !important; +} + +.table td, .table th { + padding: 8px 10px; +} + +.dataTable { + tbody { + tr { + td { + + button { + cursor: pointer; + } + + div.dropdown { + ul.dropdown-menu { + li { + cursor: pointer; + padding: 5px; + + a { + display: block; + } + } + + li:hover { + background: #f4f5f8; + } + } + } + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.min.css b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.min.css new file mode 100644 index 0000000000..e70b9e330b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/roles/index.min.css @@ -0,0 +1 @@ +.dataTable{width:100% !important;border-spacing:0 !important;}.table td,.table th{padding:8px 10px;}.dataTable tbody tr td button{cursor:pointer;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li{cursor:pointer;padding:5px;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li a{display:block;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover{background:#f4f5f8;} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.css b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.css new file mode 100644 index 0000000000..41fbd4eaf0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.css @@ -0,0 +1,21 @@ +.dataTable { + width: 100% !important; + border-spacing: 0 !important; +} +.table td, +.table th { + padding: 8px 10px; +} +.dataTable tbody tr td button { + cursor: pointer; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li { + cursor: pointer; + padding: 5px; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li a { + display: block; +} +.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover { + background: #f4f5f8; +} \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.js new file mode 100644 index 0000000000..216ec8285e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.js @@ -0,0 +1,92 @@ +'use strict'; + +(function ($) { + + var l = abp.localization.getResource('AbpIdentity'); + + var _identityUserAppService = volo.abp.identity.identityUser; + var _editModal = new abp.ModalManager(abp.appPath + 'Identity/Users/EditModal'); + var _createModal = new abp.ModalManager(abp.appPath + 'Identity/Users/CreateModal'); + var _permissionsModal = new abp.ModalManager(abp.appPath + 'AbpPermissionManagement/PermissionManagementModal'); + + $(function () { + + var _$wrapper = $('#IdentityUsersWrapper'); + var _$table = _$wrapper.find('table'); + var _dataTable = _$table.DataTable({ + order: [[1, "asc"]], + ajax: abp.libs.datatables.createAjax(_identityUserAppService.getList), + columnDefs: [{ + //TODO: Can we eleminate targets, data, orderable, autoWidth, defaultContent fields or make these values default + targets: 0, + data: null, + orderable: false, + autoWidth: false, + defaultContent: '', + rowAction: { + text: ' ' + l('Actions') + ' ', //TODO: Add icon option and set text as only l('Actions') + items: [{ + //TODO: Allow to add icon + text: l('Edit'), + visible: function visible() { + //TODO: Allow visible to be a boolean for simple cases (and true by default) + return true; + }, + action: function action(data) { + _editModal.open({ + id: data.record.id + }); + } + }, { + text: l('Permissions'), + visible: function visible() { + return true; + }, + action: function action(data) { + _permissionsModal.open({ + providerName: 'User', + providerKey: data.record.id + }); + } + }, { + text: l('Delete'), + visible: function visible() { + return true; + }, + confirmMessage: function confirmMessage(data) { + return l('UserDeletionConfirmationMessage', data.record.userName); + }, + action: function action(data) { + _identityUserAppService['delete'](data.record.id).then(function () { + _dataTable.ajax.reload(); + }); + } + }] + } + }, { + targets: 1, + data: "userName" + }, { + targets: 2, + data: "email" + }, { + targets: 3, + data: "phoneNumber" + }] + }); + + _createModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _editModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _$wrapper.find('button[name=CreateUser]').click(function (e) { + e.preventDefault(); + _createModal.open(); + }); + }); +})(jQuery); + diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.min.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.min.js new file mode 100644 index 0000000000..cacfd8391f --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.es5.min.js @@ -0,0 +1 @@ +"use strict";(function(n){var t=abp.localization.getResource("AbpIdentity"),i=volo.abp.identity.identityUser,r=new abp.ModalManager(abp.appPath+"Identity/Users/EditModal"),u=new abp.ModalManager(abp.appPath+"Identity/Users/CreateModal"),f=new abp.ModalManager(abp.appPath+"AbpPermissionManagement/PermissionManagementModal");n(function(){var o=n("#IdentityUsersWrapper"),s=o.find("table"),e=s.DataTable({order:[[1,"asc"]],ajax:abp.libs.datatables.createAjax(i.getList),columnDefs:[{targets:0,data:null,orderable:!1,autoWidth:!1,defaultContent:"",rowAction:{text:'<\/i> '+t("Actions")+' <\/span>',items:[{text:t("Edit"),visible:function(){return!0},action:function(n){r.open({id:n.record.id})}},{text:t("Permissions"),visible:function(){return!0},action:function(n){f.open({providerName:"User",providerKey:n.record.id})}},{text:t("Delete"),visible:function(){return!0},confirmMessage:function(n){return t("UserDeletionConfirmationMessage",n.record.userName)},action:function(n){i["delete"](n.record.id).then(function(){e.ajax.reload()})}}]}},{targets:1,data:"userName"},{targets:2,data:"email"},{targets:3,data:"phoneNumber"}]});u.onResult(function(){e.ajax.reload()});r.onResult(function(){e.ajax.reload()});o.find("button[name=CreateUser]").click(function(n){n.preventDefault();u.open()})})})(jQuery); \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.js b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.js new file mode 100644 index 0000000000..9194e61c47 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.js @@ -0,0 +1,99 @@ +(function ($) { + + var l = abp.localization.getResource('AbpIdentity'); + + var _identityUserAppService = volo.abp.identity.identityUser; + var _editModal = new abp.ModalManager(abp.appPath + 'Identity/Users/EditModal'); + var _createModal = new abp.ModalManager(abp.appPath + 'Identity/Users/CreateModal'); + var _permissionsModal = new abp.ModalManager(abp.appPath + 'AbpPermissionManagement/PermissionManagementModal'); + + $(function () { + + var _$wrapper = $('#IdentityUsersWrapper'); + var _$table = _$wrapper.find('table'); + var _dataTable = _$table.DataTable({ + order: [[1, "asc"]], + ajax: abp.libs.datatables.createAjax(_identityUserAppService.getList), + columnDefs: [ + { + //TODO: Can we eleminate targets, data, orderable, autoWidth, defaultContent fields or make these values default + targets: 0, + data: null, + orderable: false, + autoWidth: false, + defaultContent: '', + rowAction: { + text: ' ' + l('Actions') + ' ', //TODO: Add icon option and set text as only l('Actions') + items: + [ + { + //TODO: Allow to add icon + text: l('Edit'), + visible: function () { //TODO: Allow visible to be a boolean for simple cases (and true by default) + return true; + }, + action: function (data) { + _editModal.open({ + id: data.record.id + }); + } + }, + { + text: l('Permissions'), + visible: function () { + return true; + }, + action: function (data) { + _permissionsModal.open({ + providerName: 'User', + providerKey: data.record.id + }); + } + }, + { + text: l('Delete'), + visible: function () { + return true; + }, + confirmMessage: function (data) { return l('UserDeletionConfirmationMessage', data.record.userName)}, + action: function (data) { + _identityUserAppService + .delete(data.record.id) + .then(function () { + _dataTable.ajax.reload(); + }); + } + } + ] + } + }, + { + targets: 1, + data: "userName" + }, + { + targets: 2, + data: "email" + }, + { + targets: 3, + data: "phoneNumber" + } + ] + }); + + _createModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _editModal.onResult(function () { + _dataTable.ajax.reload(); + }); + + _$wrapper.find('button[name=CreateUser]').click(function (e) { + e.preventDefault(); + _createModal.open(); + }); + }); + +})(jQuery); diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.less b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.less new file mode 100644 index 0000000000..21adf56097 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.less @@ -0,0 +1,39 @@ +//TODO: This code is duplicated for other pages too. Unify them. +.dataTable { + width: 100% !important; + border-spacing: 0 !important; +} + +.table td, .table th { + padding: 8px 10px; +} + +.dataTable { + tbody { + tr { + td { + + button { + cursor: pointer; + } + + div.dropdown { + ul.dropdown-menu { + li { + cursor: pointer; + padding: 5px; + + a { + display: block; + } + } + + li:hover { + background: #f4f5f8; + } + } + } + } + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.min.css b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.min.css new file mode 100644 index 0000000000..e70b9e330b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Web/wwwroot/pages/identity/users/index.min.css @@ -0,0 +1 @@ +.dataTable{width:100% !important;border-spacing:0 !important;}.table td,.table th{padding:8px 10px;}.dataTable tbody tr td button{cursor:pointer;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li{cursor:pointer;padding:5px;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li a{display:block;}.dataTable tbody tr td div.dropdown ul.dropdown-menu li:hover{background:#f4f5f8;} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo.Abp.Identity.Application.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo.Abp.Identity.Application.Tests.csproj new file mode 100644 index 0000000000..1fbe8ed5df --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo.Abp.Identity.Application.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + Volo.Abp.Identity.Application.Tests + Volo.Abp.Identity.Application.Tests + true + false + false + false + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestBase.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestBase.cs new file mode 100644 index 0000000000..dd3594e81d --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity +{ + public class AbpIdentityApplicationTestBase : AbpIdentityExtendedTestBase + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs new file mode 100644 index 0000000000..48da243a39 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity +{ + [DependsOn( + typeof(AbpIdentityApplicationModule), + typeof(AbpIdentityDomainTestModule) + )] + public class AbpIdentityApplicationTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs new file mode 100644 index 0000000000..8801f7073a --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityRoleAppService_Tests.cs @@ -0,0 +1,123 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Shouldly; + +namespace Volo.Abp.Identity +{ + public class IdentityRoleAppService_Tests : AbpIdentityApplicationTestBase + { + private readonly IIdentityRoleAppService _roleAppService; + private readonly IIdentityRoleRepository _roleRepository; + + public IdentityRoleAppService_Tests() + { + _roleAppService = GetRequiredService(); + _roleRepository = GetRequiredService(); + } + + [Fact] + public async Task GetAsync() + { + //Arrange + + var moderator = await GetRoleAsync("moderator"); + + //Act + + var result = await _roleAppService.GetAsync(moderator.Id); + + //Assert + + result.Id.ShouldBe(moderator.Id); + } + + [Fact] + public async Task GetListAsync() + { + //Act + + var result = await _roleAppService.GetListAsync(new GetIdentityRolesInput()); + + //Assert + + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.Count.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task CreateAsync() + { + //Arrange + + var input = new IdentityRoleCreateDto + { + Name = Guid.NewGuid().ToString("N").Left(8) + }; + + //Act + + var result = await _roleAppService.CreateAsync(input); + + //Assert + + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe(input.Name); + + var role = await _roleRepository.GetAsync(result.Id); + role.Name.ShouldBe(input.Name); + } + + [Fact] + public async Task UpdateAsync() + { + //Arrange + + var moderator = await GetRoleAsync("moderator"); + + var input = new IdentityRoleUpdateDto + { + Name = Guid.NewGuid().ToString("N").Left(8) + }; + + //Act + + var result = await _roleAppService.UpdateAsync(moderator.Id, input); + + //Assert + + result.Id.ShouldBe(moderator.Id); + result.Name.ShouldBe(input.Name); + + var updatedRole = await _roleRepository.GetAsync(moderator.Id); + updatedRole.Name.ShouldBe(input.Name); + } + + [Fact] + public async Task DeleteAsync() + { + //Arrange + + var moderator = await GetRoleAsync("moderator"); + + //Act + + await _roleAppService.DeleteAsync(moderator.Id); + + //Assert + + (await FindRoleAsync("moderator")).ShouldBeNull(); + } + + private async Task GetRoleAsync(string roleName) + { + return (await _roleRepository.GetListAsync()).First(u => u.Name == roleName); + } + + private async Task FindRoleAsync(string roleName) + { + return (await _roleRepository.GetListAsync()).FirstOrDefault(u => u.Name == roleName); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs new file mode 100644 index 0000000000..14864ce7da --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/IdentityUserAppService_Tests.cs @@ -0,0 +1,194 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Identity +{ + public class IdentityUserAppService_Tests : AbpIdentityApplicationTestBase + { + private readonly IIdentityUserAppService _userAppService; + private readonly IIdentityUserRepository _userRepository; + + public IdentityUserAppService_Tests() + { + _userAppService = GetRequiredService(); + _userRepository = GetRequiredService(); + } + + [Fact] + public async Task GetAsync() + { + //Arrange + + var johnNash = GetUser("john.nash"); + + //Act + + var result = await _userAppService.GetAsync(johnNash.Id); + + //Assert + + result.Id.ShouldBe(johnNash.Id); + result.UserName.ShouldBe(johnNash.UserName); + result.Email.ShouldBe(johnNash.Email); + result.LockoutEnabled.ShouldBe(johnNash.LockoutEnabled); + result.PhoneNumber.ShouldBe(johnNash.PhoneNumber); + } + + [Fact] + public async Task GetListAsync() + { + //Act + + var result = await _userAppService.GetListAsync(new GetIdentityUsersInput()); + + //Assert + + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.Count.ShouldBeGreaterThan(0); + } + + [Fact] + public async Task CreateAsync() + { + //Arrange + + var input = new IdentityUserCreateDto + { + UserName = Guid.NewGuid().ToString(), + Email = CreateRandomEmail(), + LockoutEnabled = true, + PhoneNumber = CreateRandomPhoneNumber(), + Password = "123qwE4r*", + RoleNames = new[] { "moderator" } + }; + + //Act + + var result = await _userAppService.CreateAsync(input); + + //Assert + + result.Id.ShouldNotBe(Guid.Empty); + result.UserName.ShouldBe(input.UserName); + result.Email.ShouldBe(input.Email); + result.LockoutEnabled.ShouldBe(input.LockoutEnabled); + result.PhoneNumber.ShouldBe(input.PhoneNumber); + + var user = await _userRepository.GetAsync(result.Id); + user.Id.ShouldBe(result.Id); + user.UserName.ShouldBe(input.UserName); + user.Email.ShouldBe(input.Email); + user.LockoutEnabled.ShouldBe(input.LockoutEnabled); + user.PhoneNumber.ShouldBe(input.PhoneNumber); + } + + [Fact] + public async Task UpdateAsync() + { + //Arrange + + var johnNash = GetUser("john.nash"); + + var input = new IdentityUserUpdateDto + { + UserName = johnNash.UserName, + LockoutEnabled = true, + TwoFactorEnabled = true, + PhoneNumber = CreateRandomPhoneNumber(), + Email = CreateRandomEmail(), + RoleNames = new[] { "admin", "moderator" } + }; + + //Act + + var result = await _userAppService.UpdateAsync(johnNash.Id, input); + + //Assert + + result.Id.ShouldBe(johnNash.Id); + result.UserName.ShouldBe(input.UserName); + result.Email.ShouldBe(input.Email); + result.LockoutEnabled.ShouldBe(input.LockoutEnabled); + result.PhoneNumber.ShouldBe(input.PhoneNumber); + + var user = await _userRepository.GetAsync(result.Id); + user.Id.ShouldBe(result.Id); + user.UserName.ShouldBe(input.UserName); + user.Email.ShouldBe(input.Email); + user.LockoutEnabled.ShouldBe(input.LockoutEnabled); + user.PhoneNumber.ShouldBe(input.PhoneNumber); + user.Roles.Count.ShouldBe(2); + } + + [Fact] + public async Task DeleteAsync() + { + //Arrange + + var johnNash = GetUser("john.nash"); + + //Act + + await _userAppService.DeleteAsync(johnNash.Id); + + //Assert + + FindUser("john.nash").ShouldBeNull(); + } + + [Fact] + public async Task GetRolesAsync() + { + //Arrange + + var johnNash = GetUser("john.nash"); + + //Act + + var result = await _userAppService.GetRolesAsync(johnNash.Id); + + //Assert + + result.Items.Count.ShouldBe(2); + result.Items.ShouldContain(r => r.Name == "moderator"); + result.Items.ShouldContain(r => r.Name == "supporter"); + } + + [Fact] + public async Task UpdateRolesAsync() + { + //Arrange + + var johnNash = GetUser("john.nash"); + + //Act + + await _userAppService.UpdateRolesAsync( + johnNash.Id, + new IdentityUserUpdateRolesDto + { + RoleNames = new[] { "admin", "moderator" } + } + ); + + //Assert + + var roleNames = await _userRepository.GetRoleNamesAsync(johnNash.Id); + roleNames.Count.ShouldBe(2); + roleNames.ShouldContain("admin"); + roleNames.ShouldContain("moderator"); + } + + private static string CreateRandomEmail() + { + return Guid.NewGuid().ToString("N").Left(16) + "@abp.io"; + } + + private static string CreateRandomPhoneNumber() + { + return RandomHelper.GetRandom(10000000, 100000000).ToString(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Properties/AssemblyInfo.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9f9974d329 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Volo.Abp.Identity.Tests")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4ab91077-82dc-4335-9274-bce017bd9c8b")] diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo.Abp.Identity.Domain.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo.Abp.Identity.Domain.Tests.csproj new file mode 100644 index 0000000000..aaaa120455 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo.Abp.Identity.Domain.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + Volo.Abp.Identity.Domain.Tests + Volo.Abp.Identity.Domain.Tests + true + false + false + false + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs new file mode 100644 index 0000000000..310f6e7107 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestBase.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity +{ + public abstract class AbpIdentityDomainTestBase : AbpIdentityExtendedTestBase + { + + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs new file mode 100644 index 0000000000..ded3308fbb --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityDomainTestModule.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity +{ + [DependsOn(typeof(AbpIdentityEntityFrameworkCoreTestModule))] + public class AbpIdentityDomainTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + options.DefinitionProviders.Add(); + }); + + services.AddAssemblyOf(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + SeedTestData(context); + } + + private static void SeedTestData(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .Build(); + } + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs new file mode 100644 index 0000000000..4a89feaa8c --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityExtendedTestBase.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity +{ + public abstract class AbpIdentityExtendedTestBase : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected virtual IdentityUser GetUser(string userName) + { + var user = UsingDbContext(context => context.Users.FirstOrDefault(u => u.UserName == userName)); + if (user == null) + { + throw new EntityNotFoundException(); + } + + return user; + } + + protected virtual IdentityUser FindUser(string userName) + { + return UsingDbContext(context => context.Users.FirstOrDefault(u => u.UserName == userName)); + } + + protected virtual void UsingDbContext(Action action) + { + using (var dbContext = GetRequiredService()) + { + action.Invoke(dbContext); + } + } + + protected virtual T UsingDbContext(Func action) + { + using (var dbContext = GetRequiredService()) + { + return action.Invoke(dbContext); + } + } + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs new file mode 100644 index 0000000000..4ea482f7b4 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityOptions_Tests.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using NSubstitute; +using Shouldly; +using Volo.Abp.Settings; +using Xunit; + +namespace Volo.Abp.Identity +{ + public class IdentityOptions_Tests : AbpIdentityDomainTestBase + { + private ISettingManager _settingManager; + + protected override void AfterAddApplication(IServiceCollection services) + { + _settingManager = Substitute.For(); + _settingManager.GetOrNullAsync(Arg.Any()).Returns((string) null); + services.Replace(ServiceDescriptor.Singleton(_settingManager)); + } + + [Fact] + public void Should_Resolve_AbpIdentityOptionsFactory() + { + GetRequiredService>().ShouldBeOfType(typeof(AbpIdentityOptionsFactory)); + } + + [Fact] + public void Should_Get_Options_From_Custom_Settings_If_Available() + { + using (var scope1 = ServiceProvider.CreateScope()) + { + var options = scope1.ServiceProvider.GetRequiredService>().Value; + options.Password.RequiredLength.ShouldBe(6); //Default value + options.Password.RequiredUniqueChars.ShouldBe(1); //Default value + } + + _settingManager.GetOrNullAsync(IdentitySettingNames.Password.RequiredLength).Returns(Task.FromResult("42")); + + using (var scope2 = ServiceProvider.CreateScope()) + { + var options = scope2.ServiceProvider.GetRequiredService>().Value; + options.Password.RequiredLength.ShouldBe(42); //Setting value + options.Password.RequiredUniqueChars.ShouldBe(1); //Default value + } + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityTestPermissionDefinitionProvider.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityTestPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..323758cd9f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityTestPermissionDefinitionProvider.cs @@ -0,0 +1,17 @@ +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.Identity +{ + public class IdentityTestPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var testGroup = context.AddGroup(TestPermissionNames.Groups.TestGroup); + + testGroup.AddPermission(TestPermissionNames.MyPermission1); + + var myPermission2 = testGroup.AddPermission(TestPermissionNames.MyPermission2); + myPermission2.AddChild(TestPermissionNames.MyPermission2_ChildPermission1); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/PermissionManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/PermissionManager_Tests.cs new file mode 100644 index 0000000000..374351fd3c --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/PermissionManager_Tests.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Permissions; +using Xunit; + +namespace Volo.Abp.Identity +{ + public class PermissionManager_Tests : AbpIdentityDomainTestBase + { + private readonly IPermissionManager _permissionManager; + private readonly IPermissionStore _permissionStore; + + public PermissionManager_Tests() + { + _permissionManager = GetRequiredService(); + _permissionStore = GetRequiredService(); + } + + [Fact] + public async Task Roles_Should_Have_Configured_Permissions() + { + //admin + var grantInfos = await _permissionManager.GetAllForRoleAsync("admin"); + RoleShouldHavePermission(grantInfos, "admin", TestPermissionNames.MyPermission1); + RoleShouldHavePermission(grantInfos, "admin", TestPermissionNames.MyPermission2); + RoleShouldHavePermission(grantInfos, "admin", TestPermissionNames.MyPermission2_ChildPermission1); + + //moderator + grantInfos = await _permissionManager.GetAllForRoleAsync("moderator"); + RoleShouldHavePermission(grantInfos, "moderator", TestPermissionNames.MyPermission1); + RoleShouldHavePermission(grantInfos, "moderator", TestPermissionNames.MyPermission2); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2_ChildPermission1); + + //supporter + grantInfos = await _permissionManager.GetAllForRoleAsync("supporter"); + RoleShouldHavePermission(grantInfos, "supporter", TestPermissionNames.MyPermission1); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2_ChildPermission1); + } + + [Fact] + public async Task Should_Grant_Permission_To_Role() + { + (await _permissionManager.GetForRoleAsync("supporter", TestPermissionNames.MyPermission2)).IsGranted.ShouldBeFalse(); + (await _permissionStore.IsGrantedAsync(TestPermissionNames.MyPermission2, RolePermissionValueProvider.ProviderName, "supporter")).ShouldBeFalse(); + + await _permissionManager.SetForRoleAsync("supporter", TestPermissionNames.MyPermission2, true); + + (await _permissionManager.GetForRoleAsync("supporter", TestPermissionNames.MyPermission2)).IsGranted.ShouldBeTrue(); + (await _permissionStore.IsGrantedAsync(TestPermissionNames.MyPermission2, RolePermissionValueProvider.ProviderName, "supporter")).ShouldBeTrue(); + } + + [Fact] + public async Task Should_Revoke_Permission_From_Role() + { + (await _permissionManager.GetForRoleAsync("moderator", TestPermissionNames.MyPermission1)).IsGranted.ShouldBeTrue(); + await _permissionManager.SetForRoleAsync("moderator", TestPermissionNames.MyPermission1, false); + (await _permissionManager.GetForRoleAsync("moderator", TestPermissionNames.MyPermission1)).IsGranted.ShouldBeFalse(); + } + + [Fact] + public async Task Users_Should_Have_Configured_Values() + { + //administrator + var user = GetUser("administrator"); + var grantInfos = await _permissionManager.GetAllForUserAsync(user.Id); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission1, "admin"); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission2, "admin"); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission2_ChildPermission1, "admin"); + + //john.nash + user = GetUser("john.nash"); + grantInfos = await _permissionManager.GetAllForUserAsync(user.Id); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission1, "moderator", "supporter"); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission2, "moderator"); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2_ChildPermission1); + + //john.nash + user = GetUser("david"); + grantInfos = await _permissionManager.GetAllForUserAsync(user.Id); + UserShouldHavePermission(grantInfos, user.Id, TestPermissionNames.MyPermission1); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2); + ShouldNotHavePermission(grantInfos, TestPermissionNames.MyPermission2_ChildPermission1); + } + + private static void RoleShouldHavePermission(List grantInfos, string roleName, string permissionName) + { + grantInfos.ShouldContain( + p => p.Name == permissionName && + p.IsGranted && + p.Providers.Count == 1 && + p.Providers.Any( + pr => pr.Name == RolePermissionValueProvider.ProviderName && + pr.Key == roleName + ) + ); + } + + private static void UserShouldHavePermission(List grantInfos, Guid userId, string permissionName, params string[] inheritedRolesForThisPermission) + { + grantInfos.ShouldContain( + p => p.Name == permissionName && + p.IsGranted + ); + } + + private static void ShouldNotHavePermission(List grantInfos, string permissionName) + { + grantInfos.ShouldContain( + p => p.Name == permissionName && + !p.IsGranted && + p.Providers.Count == 0 + ); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionDataBuilder.cs new file mode 100644 index 0000000000..b5272ed0e7 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionDataBuilder.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.PermissionManagement; + +namespace Volo.Abp.Identity +{ + public class TestPermissionDataBuilder : ITransientDependency + { + private readonly IGuidGenerator _guidGenerator; + private readonly IIdentityUserRepository _userRepository; + private readonly IPermissionGrantRepository _permissionGrantRepository; + private readonly ILookupNormalizer _lookupNormalizer; + + public TestPermissionDataBuilder( + IGuidGenerator guidGenerator, + IIdentityUserRepository userRepository, + IPermissionGrantRepository permissionGrantRepository, + ILookupNormalizer lookupNormalizer) + { + _guidGenerator = guidGenerator; + _userRepository = userRepository; + _permissionGrantRepository = permissionGrantRepository; + _lookupNormalizer = lookupNormalizer; + } + + public void Build() + { + AddRolePermissions(); + AddUserPermissions(); + } + + private void AddRolePermissions() + { + AddPermission(TestPermissionNames.MyPermission1, RolePermissionValueProvider.ProviderName, "admin"); + AddPermission(TestPermissionNames.MyPermission2, RolePermissionValueProvider.ProviderName, "admin"); + AddPermission(TestPermissionNames.MyPermission2_ChildPermission1, RolePermissionValueProvider.ProviderName, "admin"); + + AddPermission(TestPermissionNames.MyPermission1, RolePermissionValueProvider.ProviderName, "moderator"); + AddPermission(TestPermissionNames.MyPermission2, RolePermissionValueProvider.ProviderName, "moderator"); + + AddPermission(TestPermissionNames.MyPermission1, RolePermissionValueProvider.ProviderName, "supporter"); + } + + private void AddUserPermissions() + { + var david = _userRepository.FindByNormalizedUserName(_lookupNormalizer.Normalize("david")); + AddPermission(TestPermissionNames.MyPermission1, UserPermissionValueProvider.ProviderName, david.Id.ToString()); + } + + private void AddPermission(string permissionName, string providerName, string providerKey) + { + _permissionGrantRepository.Insert( + new PermissionGrant( + _guidGenerator.Create(), + permissionName, + providerName, + providerKey + ) + ); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionNames.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionNames.cs new file mode 100644 index 0000000000..1e65171eae --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/TestPermissionNames.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Identity +{ + public static class TestPermissionNames + { + public static class Groups + { + public const string TestGroup = "TestGroup"; + } + + public const string MyPermission1 = "MyPermission1"; + public const string MyPermission2 = "MyPermission2"; + public const string MyPermission2_ChildPermission1 = "MyPermission2.ChildPermission1"; + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000000..3bf000944e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo.Abp.Identity.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.0 + Volo.Abp.Identity.EntityFrameworkCore.Tests + Volo.Abp.Identity.EntityFrameworkCore.Tests + true + false + false + false + + + + + + + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs new file mode 100644 index 0000000000..6ee1a35a3d --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs @@ -0,0 +1,50 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + [DependsOn( + typeof(AbpIdentityTestBaseModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule) + )] + public class AbpIdentityEntityFrameworkCoreTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + var sqliteConnection = CreateDatabaseAndGetConnection(); + + services.Configure(options => + { + options.Configure(context => + { + context.DbContextOptions.UseSqlite(sqliteConnection); + }); + }); + + services.AddAssemblyOf(); + } + + private static SqliteConnection CreateDatabaseAndGetConnection() + { + var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + + new IdentityDbContext( + new DbContextOptionsBuilder().UseSqlite(connection).Options + ).GetService().CreateTables(); + + new PermissionManagementDbContext( + new DbContextOptionsBuilder().UseSqlite(connection).Options + ).GetService().CreateTables(); + + return connection; + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityDataSeeder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityDataSeeder_Tests.cs new file mode 100644 index 0000000000..8cc434b2d0 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityDataSeeder_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class IdentityDataSeeder_Tests : IdentityDataSeeder_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityRoleRepository_Tests.cs new file mode 100644 index 0000000000..ebd435c98d --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityRoleRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class IdentityRoleRepository_Tests : IdentityRoleRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserRepository_Tests.cs new file mode 100644 index 0000000000..5c164dea44 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/IdentityUserRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class IdentityUserRepository_Tests : IdentityUserRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/Identity_Repository_Resolve_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/Identity_Repository_Resolve_Tests.cs new file mode 100644 index 0000000000..c05bdf6437 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/Identity_Repository_Resolve_Tests.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class Identity_Repository_Resolve_Tests : Identity_Repository_Resolve_Tests + { + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/LazyLoading_Tests.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/LazyLoading_Tests.cs new file mode 100644 index 0000000000..ddc85f86cf --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/LazyLoading_Tests.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Identity.EntityFrameworkCore +{ + public class LazyLoading_Tests : LazyLoading_Tests + { + protected override void BeforeAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.PreConfigure(context => + { + context.DbContextOptions.UseLazyLoadingProxies(); + }); + }); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj new file mode 100644 index 0000000000..bc87ae597b --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo.Abp.Identity.MongoDB.Tests.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.0 + Volo.Abp.Identity.MongoDB.Tests + Volo.Abp.Identity.MongoDB.Tests + true + false + false + false + + + + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs new file mode 100644 index 0000000000..8ac4ec3466 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentityMongoDbTestModule.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; +using Mongo2Go; +using Volo.Abp.Data; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.MongoDB; + +namespace Volo.Abp.Identity.MongoDB +{ + [DependsOn( + typeof(AbpIdentityTestBaseModule), + typeof(AbpPermissionManagementMongoDbModule), + typeof(AbpIdentityMongoDbModule) + )] + public class AbpIdentityMongoDbTestModule : AbpModule + { + private MongoDbRunner _mongoDbRunner; + + public override void ConfigureServices(IServiceCollection services) + { + _mongoDbRunner = MongoDbRunner.Start(); + + services.Configure(options => + { + options.ConnectionStrings.Default = _mongoDbRunner.ConnectionString; + }); + + services.AddAssemblyOf(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + _mongoDbRunner.Dispose(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs new file mode 100644 index 0000000000..e97269c7e2 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityDataSeeder_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityDataSeeder_Tests : IdentityDataSeeder_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs new file mode 100644 index 0000000000..4873aa7298 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityRoleRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityRoleRepository_Tests : IdentityRoleRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs new file mode 100644 index 0000000000..214d8f6986 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserRepository_Tests.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Identity.MongoDB +{ + public class IdentityUserRepository_Tests : IdentityUserRepository_Tests + { + + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs new file mode 100644 index 0000000000..5d6742adef --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/Identity_Repository_Resolve_Tests.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Identity.MongoDB +{ + public class Identity_Repository_Resolve_Tests : Identity_Repository_Resolve_Tests + { + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo.Abp.Identity.TestBase.csproj b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo.Abp.Identity.TestBase.csproj new file mode 100644 index 0000000000..dbf131e35f --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo.Abp.Identity.TestBase.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.0 + Volo.Abp.Identity.TestBase + Volo.Abp.Identity.TestBase + true + false + false + false + + + + + + + + + + + + + + + + + + + diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBase.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBase.cs new file mode 100644 index 0000000000..b603612525 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBase.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.Identity +{ + public abstract class AbpIdentityTestBase : AbpIntegratedTest + where TStartupModule : IAbpModule + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBaseModule.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBaseModule.cs new file mode 100644 index 0000000000..398b9ed6df --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestBaseModule.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; +using Volo.Abp.Threading; + +namespace Volo.Abp.Identity +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpTestBaseModule), + typeof(AbpIdentityDomainModule) + )] + public class AbpIdentityTestBaseModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAlwaysAllowPermissionChecker(); + + services.AddAssemblyOf(); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + SeedTestData(context); + } + + private static void SeedTestData(ApplicationInitializationContext context) + { + using (var scope = context.ServiceProvider.CreateScope()) + { + var dataSeeder = scope.ServiceProvider.GetRequiredService(); + AsyncHelper.RunSync(() => dataSeeder.SeedAsync("1q2w3E*")); + + scope.ServiceProvider + .GetRequiredService() + .Build(); + } + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs new file mode 100644 index 0000000000..448dfe1c61 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/AbpIdentityTestDataBuilder.cs @@ -0,0 +1,77 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; + +namespace Volo.Abp.Identity +{ + public class AbpIdentityTestDataBuilder : ITransientDependency + { + private readonly IGuidGenerator _guidGenerator; + private readonly IIdentityUserRepository _userRepository; + private readonly IIdentityRoleRepository _roleRepository; + private readonly ILookupNormalizer _lookupNormalizer; + private readonly IdentityTestData _testData; + + private IdentityRole _adminRole; + private IdentityRole _moderator; + private IdentityRole _supporterRole; + + public AbpIdentityTestDataBuilder( + IGuidGenerator guidGenerator, + IIdentityUserRepository userRepository, + IIdentityRoleRepository roleRepository, + ILookupNormalizer lookupNormalizer, + IdentityTestData testData) + { + _guidGenerator = guidGenerator; + _userRepository = userRepository; + _roleRepository = roleRepository; + _lookupNormalizer = lookupNormalizer; + _testData = testData; + } + + public void Build() + { + AddRoles(); + AddUsers(); + } + + private void AddRoles() + { + _adminRole = _roleRepository.FindByNormalizedName(_lookupNormalizer.Normalize("admin")); + + _moderator = new IdentityRole(_testData.RoleModeratorId, "moderator"); + _moderator.AddClaim(_guidGenerator, new Claim("test-claim", "test-value")); + _roleRepository.Insert(_moderator); + + _supporterRole = new IdentityRole(_guidGenerator.Create(), "supporter"); + _roleRepository.Insert(_supporterRole); + } + + private void AddUsers() + { + var adminUser = new IdentityUser(_guidGenerator.Create(), "administrator", "admin@abp.io"); + adminUser.AddRole(_adminRole.Id); + adminUser.AddClaim(_guidGenerator, new Claim("TestClaimType", "42")); + _userRepository.Insert(adminUser); + + var john = new IdentityUser(_testData.UserJohnId, "john.nash", "john.nash@abp.io"); + john.AddRole(_moderator.Id); + john.AddRole(_supporterRole.Id); + john.AddLogin(new UserLoginInfo("github", "john", "John Nash")); + john.AddLogin(new UserLoginInfo("twitter", "johnx", "John Nash")); + john.AddClaim(_guidGenerator, new Claim("TestClaimType", "42")); + john.SetToken("test-provider", "test-name", "test-value"); + _userRepository.Insert(john); + + var david = new IdentityUser(_testData.UserDavidId, "david", "david@abp.io"); + _userRepository.Insert(david); + + var neo = new IdentityUser(_testData.UserNeoId, "neo", "neo@abp.io"); + neo.AddRole(_supporterRole.Id); + neo.AddClaim(_guidGenerator, new Claim("TestClaimType", "43")); + _userRepository.Insert(neo); + } + } +} \ No newline at end of file diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityDataSeeder_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityDataSeeder_Tests.cs new file mode 100644 index 0000000000..515b335fc8 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityDataSeeder_Tests.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityDataSeeder_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + private readonly IIdentityDataSeeder _identityDataSeeder; + private readonly IIdentityUserRepository _userRepository; + private readonly IIdentityRoleRepository _roleRepository; + private readonly ILookupNormalizer _lookupNormalizer; + + protected IdentityDataSeeder_Tests() + { + _identityDataSeeder = GetRequiredService(); + _userRepository = GetRequiredService(); + _roleRepository = GetRequiredService(); + _lookupNormalizer = GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Admin_User_And_Role() + { + await _identityDataSeeder.SeedAsync("1q2w3E*"); + + (await _userRepository.FindByNormalizedUserNameAsync(_lookupNormalizer.Normalize("admin"))).ShouldNotBeNull(); + (await _roleRepository.FindByNormalizedNameAsync(_lookupNormalizer.Normalize("admin"))).ShouldNotBeNull(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs new file mode 100644 index 0000000000..5b06b4ae73 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityRoleRepository_Tests.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityRoleRepository_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected IIdentityRoleRepository RoleRepository { get; } + protected ILookupNormalizer LookupNormalizer { get; } + + protected IdentityRoleRepository_Tests() + { + RoleRepository = ServiceProvider.GetRequiredService(); + LookupNormalizer = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindByNormalizedNameAsync() + { + (await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.Normalize("admin"))).ShouldNotBeNull(); + (await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.Normalize("undefined-role"))).ShouldBeNull(); + } + + [Fact] + public async Task GetListAsync() + { + var roles = await RoleRepository.GetListAsync(); + roles.ShouldContain(r => r.Name == "admin"); + roles.ShouldContain(r => r.Name == "moderator"); + roles.ShouldContain(r => r.Name == "supporter"); + } + + [Fact] + public async Task GetCountAsync() + { + (await RoleRepository.GetCountAsync()).ShouldBeGreaterThan(0); + } + + [Fact] + public async Task Should_Eager_Load_Role_Collections() + { + var role = await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.Normalize("moderator")); + role.Claims.ShouldNotBeNull(); + role.Claims.Any().ShouldBeTrue(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs new file mode 100644 index 0000000000..b8b99a2453 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityTestData.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Identity +{ + public class IdentityTestData : ISingletonDependency + { + public Guid RoleModeratorId { get; } = Guid.NewGuid(); + + public Guid UserJohnId { get; } = Guid.NewGuid(); + public Guid UserDavidId { get; } = Guid.NewGuid(); + public Guid UserNeoId { get; } = Guid.NewGuid(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs new file mode 100644 index 0000000000..7ad01100e8 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserRepository_Tests.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class IdentityUserRepository_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected IIdentityUserRepository UserRepository { get; } + protected ILookupNormalizer LookupNormalizer { get; } + + protected IdentityUserRepository_Tests() + { + UserRepository = ServiceProvider.GetRequiredService(); + LookupNormalizer = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task FindByNormalizedUserNameAsync() + { + (await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("john.nash"))).ShouldNotBeNull(); + (await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("undefined-user"))).ShouldBeNull(); + } + + [Fact] + public async Task FindByNormalizedEmailAsync() + { + (await UserRepository.FindByNormalizedEmailAsync(LookupNormalizer.Normalize("john.nash@abp.io"))).ShouldNotBeNull(); + (await UserRepository.FindByNormalizedEmailAsync(LookupNormalizer.Normalize("david@abp.io"))).ShouldNotBeNull(); + (await UserRepository.FindByNormalizedEmailAsync(LookupNormalizer.Normalize("undefined-user@abp.io"))).ShouldBeNull(); + } + + [Fact] + public async Task GetRoleNamesAsync() + { + var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("john.nash")); + var roles = await UserRepository.GetRoleNamesAsync(john.Id); + roles.Count.ShouldBe(2); + roles.ShouldContain("moderator"); + roles.ShouldContain("supporter"); + } + + [Fact] + public async Task FindByLoginAsync() + { + var user = await UserRepository.FindByLoginAsync("github", "john"); + user.ShouldNotBeNull(); + user.UserName.ShouldBe("john.nash"); + + user = await UserRepository.FindByLoginAsync("twitter", "johnx"); + user.ShouldNotBeNull(); + user.UserName.ShouldBe("john.nash"); + + (await UserRepository.FindByLoginAsync("github", "undefinedid")).ShouldBeNull(); + } + + [Fact] + public async Task GetListByClaimAsync() + { + var users = await UserRepository.GetListByClaimAsync(new Claim("TestClaimType", "42")); + users.Count.ShouldBe(2); + users.ShouldContain(u => u.UserName == "administrator"); + users.ShouldContain(u => u.UserName == "john.nash"); + + users = await UserRepository.GetListByClaimAsync(new Claim("TestClaimType", "43")); + users.Count.ShouldBe(1); + users.ShouldContain(u => u.UserName == "neo"); + + users = await UserRepository.GetListByClaimAsync(new Claim("TestClaimType", "undefined")); + users.Count.ShouldBe(0); + } + + [Fact] + public async Task GetListByNormalizedRoleNameAsync() + { + var users = await UserRepository.GetListByNormalizedRoleNameAsync(LookupNormalizer.Normalize("supporter")); + users.Count.ShouldBe(2); + users.ShouldContain(u => u.UserName == "john.nash"); + users.ShouldContain(u => u.UserName == "neo"); + } + + [Fact] + public async Task GetListAsync() + { + var users = await UserRepository.GetListAsync("UserName DESC", 5, 0, "n"); + + users.Count.ShouldBeGreaterThan(1); + users.Count.ShouldBeLessThanOrEqualTo(5); + + //Filter check + users.ShouldAllBe(u => u.UserName.Contains("n") || 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 UserRepository.GetListAsync(null, int.MaxValue, 0, "undefined-username"); + users.Count.ShouldBe(0); + } + + [Fact] + public async Task GetRolesAsync() + { + var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("john.nash")); + var roles = await UserRepository.GetRolesAsync(john.Id); + roles.Count.ShouldBe(2); + roles.ShouldContain(r => r.Name == "moderator"); + roles.ShouldContain(r => r.Name == "supporter"); + } + + [Fact] + public async Task GetCountAsync() + { + (await UserRepository.GetCountAsync("n")).ShouldBeGreaterThan(1); + (await UserRepository.GetCountAsync("undefined-username")).ShouldBe(0); + } + + [Fact] + public async Task Should_Eager_Load_User_Collections() + { + var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("john.nash")); + + john.Roles.ShouldNotBeNull(); + john.Roles.Any().ShouldBeTrue(); + + john.Logins.ShouldNotBeNull(); + john.Logins.Any().ShouldBeTrue(); + + john.Claims.ShouldNotBeNull(); + john.Claims.Any().ShouldBeTrue(); + + john.Tokens.ShouldNotBeNull(); + john.Tokens.Any().ShouldBeTrue(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs new file mode 100644 index 0000000000..8b748f10b7 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/Identity_Repository_Resolve_Tests.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class Identity_Repository_Resolve_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + [Fact] //Move this test to Volo.Abp.EntityFrameworkCore.Tests since it's actually testing the EF Core repository registration! + public void Should_Resolve_Repositories() + { + ServiceProvider.GetService>().ShouldNotBeNull(); + ServiceProvider.GetService>().ShouldNotBeNull(); + ServiceProvider.GetService().ShouldNotBeNull(); + + ServiceProvider.GetService>().ShouldNotBeNull(); + ServiceProvider.GetService>().ShouldNotBeNull(); + ServiceProvider.GetService().ShouldNotBeNull(); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs new file mode 100644 index 0000000000..2a8f7c81a3 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/LazyLoading_Tests.cs @@ -0,0 +1,62 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity +{ + public abstract class LazyLoading_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule + { + protected IIdentityUserRepository UserRepository { get; } + protected IIdentityRoleRepository RoleRepository { get; } + protected ILookupNormalizer LookupNormalizer { get; } + + protected LazyLoading_Tests() + { + UserRepository = ServiceProvider.GetRequiredService(); + RoleRepository = ServiceProvider.GetRequiredService(); + LookupNormalizer = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task Should_Lazy_Load_Role_Collections() + { + using (var uow = GetRequiredService().Begin()) + { + var role = await RoleRepository.FindByNormalizedNameAsync(LookupNormalizer.Normalize("moderator"), includeDetails: false); + role.Claims.ShouldNotBeNull(); + role.Claims.Any().ShouldBeTrue(); + + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Should_Lazy_Load_User_Collections() + { + using (var uow = GetRequiredService().Begin()) + { + var john = await UserRepository.FindByNormalizedUserNameAsync(LookupNormalizer.Normalize("john.nash"), includeDetails: false); + + john.Roles.ShouldNotBeNull(); + john.Roles.Any().ShouldBeTrue(); + + john.Logins.ShouldNotBeNull(); + john.Logins.Any().ShouldBeTrue(); + + john.Claims.ShouldNotBeNull(); + john.Claims.Any().ShouldBeTrue(); + + john.Tokens.ShouldNotBeNull(); + john.Tokens.Any().ShouldBeTrue(); + + await uow.CompleteAsync(); + } + } + } +}