diff --git a/modules/account/README.md b/modules/account/README.md index 11b367a..781dab9 100644 --- a/modules/account/README.md +++ b/modules/account/README.md @@ -1,3 +1,12 @@ -# Account +# Account -Account enhancements, such as 2fa, external login, account settings. +an abp module that provider account service, such as login, 2fa, account link, impersonation, settings + +- Local Login +- Externa Login +- MFA Login +- 2FA Management +- Account Link +- Account User Impersonation +- Account User Delegation +- Account Security Logs diff --git a/modules/account/common.props b/modules/account/common.props index 8b1a412..96c1e8d 100644 --- a/modules/account/common.props +++ b/modules/account/common.props @@ -3,10 +3,10 @@ Passingwind.Abp.Account - an abp module that provider account service, such as login, 2fa, account settings. + an abp module that provider account service, such as login, 2fa, account link, impersonation, settings. abp-module 0.2.0 - 0.2.9 + 0.2.10 diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountLinkTokenValidationRequestDto.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountLinkTokenValidationRequestDto.cs index 89c4883..804d599 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountLinkTokenValidationRequestDto.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountLinkTokenValidationRequestDto.cs @@ -5,6 +5,7 @@ namespace Passingwind.Abp.Identity; public class AccountLinkTokenValidationRequestDto { + [Required] public Guid UserId { get; set; } [Required] public string Token { get; set; } = null!; diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUnlinkDto.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUnlinkDto.cs index ac43a0c..10bca62 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUnlinkDto.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUnlinkDto.cs @@ -1,9 +1,11 @@ using System; +using System.ComponentModel.DataAnnotations; namespace Passingwind.Abp.Identity; public class AccountUnlinkDto { + [Required] public Guid UserId { get; set; } - public Guid TenantId { get; set; } + public Guid? TenantId { get; set; } } diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationCreateDto.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationCreateDto.cs new file mode 100644 index 0000000..c2ab69b --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationCreateDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Passingwind.Abp.Account; + +public class AccountUserDelegationCreateDto +{ + public Guid UserId { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } +} diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationDto.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationDto.cs new file mode 100644 index 0000000..3a61507 --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/AccountUserDelegationDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace Passingwind.Abp.Account; + +public class AccountUserDelegationDto +{ + public Guid Id { get; set; } + public Guid UserId { get; set; } + public string? UserName { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public bool IsActive { get; set; } +} diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountImpersonationAppService.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountImpersonationAppService.cs index 5de129c..a3fe798 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountImpersonationAppService.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountImpersonationAppService.cs @@ -6,6 +6,26 @@ namespace Passingwind.Abp.Account; public interface IAccountImpersonationAppService : IApplicationService { + /// + /// Logout current user and return to impersonator user + /// + Task LogoutAsync(); + + /// + /// Login to specified user with id + /// + /// Task LoginAsync(Guid userId); + + /// + /// Link login to specified user with id + /// + /// Task LinkLoginAsync(Guid userId); + + /// + /// delegation login + /// + /// + Task DelegationLoginAsync(Guid userId); } diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountUserDelegationAppService.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountUserDelegationAppService.cs new file mode 100644 index 0000000..b7cefe9 --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/IAccountUserDelegationAppService.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Passingwind.Abp.Account; + +public interface IAccountUserDelegationAppService : IApplicationService +{ + Task> GetMyDelegationListAsync(); + + Task> GetDelegatedListAsync(); + + Task CreateAsync(AccountUserDelegationCreateDto input); + + Task DeleteAsync(Guid id); + + Task> UserLookupAsync(string? filter = null); +} diff --git a/modules/account/src/Passingwind.Abp.Account.Application.Contracts/UserBasicDto.cs b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/UserBasicDto.cs new file mode 100644 index 0000000..d67e467 --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.Application.Contracts/UserBasicDto.cs @@ -0,0 +1,12 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Passingwind.Abp.Account; + +public class UserBasicDto : EntityDto +{ + public string UserName { get; set; } = null!; + public string? Name { get; set; } + public string? SurName { get; set; } + public string? Email { get; set; } +} diff --git a/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationAutoMapperProfile.cs b/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationAutoMapperProfile.cs index f950c1d..96d234f 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationAutoMapperProfile.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationAutoMapperProfile.cs @@ -16,5 +16,6 @@ public AccountApplicationAutoMapperProfile() CreateMap().ReverseMap(); CreateMap().ReverseMap(); CreateMap().ReverseMap(); + CreateMap(); } } diff --git a/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationModule.cs b/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationModule.cs index f03e6c3..1116f63 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationModule.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application/AccountApplicationModule.cs @@ -6,6 +6,7 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; namespace Passingwind.Abp.Account; @@ -17,6 +18,7 @@ namespace Passingwind.Abp.Account; typeof(AbpAccountApplicationModule), typeof(AbpIdentityAspNetCoreModule), typeof(AbpDddApplicationModule), + typeof(AbpMultiTenancyAbstractionsModule), typeof(AbpAutoMapperModule) )] public class AccountApplicationModule : AbpModule diff --git a/modules/account/src/Passingwind.Abp.Account.Application/AccountImpersonationAppService.cs b/modules/account/src/Passingwind.Abp.Account.Application/AccountImpersonationAppService.cs index 2c56069..60d509a 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application/AccountImpersonationAppService.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application/AccountImpersonationAppService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -26,6 +27,7 @@ public class AccountImpersonationAppService : AccountAppBaseService, IAccountImp protected IdentityUserManager UserManager { get; } protected IdentitySecurityLogManager SecurityLogManager { get; } protected IdentityLinkUserManager LinkUserManager { get; } + protected IdentityUserDelegationManager UserDelegationManager { get; } protected IOptions IdentityOptions { get; } public AccountImpersonationAppService( @@ -33,13 +35,39 @@ public AccountImpersonationAppService( IdentityUserManager userManager, IdentitySecurityLogManager securityLogManager, IdentityLinkUserManager linkUserManager, - IOptions identityOptions) + IOptions identityOptions, + IdentityUserDelegationManager userDelegationManager) { SignInManager = signInManager; UserManager = userManager; SecurityLogManager = securityLogManager; LinkUserManager = linkUserManager; IdentityOptions = identityOptions; + UserDelegationManager = userDelegationManager; + } + + public virtual async Task LogoutAsync() + { + await IdentityOptions.SetAsync(); + + var userId = CurrentUser.FindImpersonatorUserId(); + var tenantId = CurrentUser.FindImpersonatorTenantId(); + + if (!userId.HasValue) + { + throw new AbpAuthorizationException(); + } + + if (tenantId.HasValue) + { + CurrentTenant.Change(tenantId.Value); + } + + await IdentityOptions.SetAsync(); + + var user = await UserManager.FindByIdAsync(userId.Value.ToString()); + + await SignInManager.SignInAsync(user!, false); } [Authorize(IdentityPermissionNamesV2.Users.Impersonation)] @@ -63,19 +91,24 @@ public virtual async Task LinkLoginAsync(Guid userId) var user = await UserManager.GetByIdAsync(userId); - var source = new IdentityLinkUserInfo(CurrentUser.Id!.Value, CurrentUser.TenantId); + var source = new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentUser.TenantId); var target = new IdentityLinkUserInfo(userId, user.TenantId); + if (user.TenantId.HasValue && user.TenantId != CurrentUser.TenantId) + { + CurrentTenant.Change(user.TenantId.Value); + } + if (await LinkUserManager.IsLinkedAsync(source, target, true)) { - await SignInManager.SignInWithClaimsAsync(user, false, new Claim[0]); + await ImpersonateLoginAsync(user); Logger.LogInformation("User with id '{0}' has been link login by user id '{1}'", user.Id, source.UserId); await SecurityLogManager.SaveAsync(new IdentitySecurityLogContext() { Identity = IdentitySecurityLogIdentityConsts.Identity, - Action = "ImpersonationLogin", + Action = "LinkLogin", UserName = user.UserName, ExtraProperties = { { "SourceUserId", source.UserId } } }); @@ -86,6 +119,30 @@ await SecurityLogManager.SaveAsync(new IdentitySecurityLogContext() } } + public virtual async Task DelegationLoginAsync(Guid userId) + { + var delegations = await UserDelegationManager.GetActiveDelegationsAsync(CurrentUser.GetId()); + + if (!delegations.Any(x => x.SourceUserId == userId)) + { + throw new BusinessException(AccountErrorCodes.UserNotDelegated); + } + + var user = await UserManager.GetByIdAsync(userId); + + await ImpersonateLoginAsync(user); + + Logger.LogInformation("User with id '{0}' has been delegation login by user id '{1}'", user.Id, CurrentUser.GetId()); + + await SecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = "DelegationLogin", + UserName = user.UserName, + ExtraProperties = { { "SourceUserId", CurrentUser.GetId() } } + }); + } + protected virtual async Task ImpersonateLoginAsync(IdentityUser user) { IList cliams = new List() { diff --git a/modules/account/src/Passingwind.Abp.Account.Application/AccountLinkUserAppService.cs b/modules/account/src/Passingwind.Abp.Account.Application/AccountLinkUserAppService.cs index 35e0baf..a17c0cc 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application/AccountLinkUserAppService.cs +++ b/modules/account/src/Passingwind.Abp.Account.Application/AccountLinkUserAppService.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -6,6 +8,8 @@ using Passingwind.Abp.Identity; using Volo.Abp.Application.Dtos; using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Users; using IdentityUserManager = Passingwind.Abp.Identity.IdentityUserManager; namespace Passingwind.Abp.Account; @@ -18,61 +22,94 @@ public class AccountLinkUserAppService : AccountAppBaseService, IAccountLinkUser protected IdentityLinkUserManager LinkUserManager { get; } protected IdentitySecurityLogManager SecurityLogManager { get; } protected IOptions IdentityOptions { get; } + protected ITenantStore TenantStore { get; } public AccountLinkUserAppService( IdentityUserManager userManager, IIdentityUserRepository userRepository, IdentityLinkUserManager linkUserManager, IdentitySecurityLogManager securityLogManager, - IOptions identityOptions) + IOptions identityOptions, + ITenantStore tenantStore) { UserManager = userManager; UserRepository = userRepository; LinkUserManager = linkUserManager; SecurityLogManager = securityLogManager; IdentityOptions = identityOptions; + TenantStore = tenantStore; } public virtual async Task> GetListAsync(AccountLinkUserListRequestDto input) { - var list = await LinkUserManager.GetListAsync(new IdentityLinkUserInfo(CurrentUser.Id!.Value, CurrentUser.TenantId), input.IncludeIndirect); + var list = await LinkUserManager.GetListAsync(new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentUser.TenantId), input.IncludeIndirect); - var userIds = list.Select(x => x.TargetUserId).Distinct(); + var userIds = list.Select(x => x.TargetUserId).Concat(list.Select(x => x.SourceUserId)).Distinct(); - var users = await UserRepository.GetListByIdsAsync(userIds); + var allUsers = await UserRepository.GetListByIdsAsync(userIds); - return new ListResultDto(list.ConvertAll(x => new AccountLinkUserDto() + var results = new List(); + + var tenantCache = new Dictionary(); + + foreach (var item in list) { - DirectlyLinked = x.SourceUserId == CurrentUser.Id || x.TargetUserId == CurrentUser.Id, - TargetUserId = x.TargetUserId, - TargetUserName = users.Find(u => u.Id == x.TargetUserId)?.Name, - TargetTenantId = x.TargetTenantId, - TargetTenantName = "" // TODO - })); + bool isDirectLink = item.TargetUserId == CurrentUser.GetId() && item.TargetTenantId == CurrentUser.TenantId; + + string? tenantName = string.Empty; + if (item.SourceTenantId.HasValue) + { + if (tenantCache.ContainsKey(item.SourceTenantId.Value)) + { + tenantName = tenantCache[item.SourceTenantId.Value]; + } + else + { + tenantName = (await TenantStore.FindAsync(item.SourceTenantId.Value))?.Name; + tenantCache[item.SourceTenantId.Value] = tenantName; + } + } + + if (!results.Any(x => x.TargetUserId == item.SourceUserId && x.TargetTenantId == item.SourceTenantId)) + { + results.Add(new AccountLinkUserDto() + { + DirectlyLinked = isDirectLink, + TargetUserId = item.SourceUserId, + TargetUserName = allUsers.Find(u => u.Id == item.SourceUserId)?.UserName, + TargetTenantId = item.SourceTenantId, + TargetTenantName = tenantName, + }); + } + } + + return new ListResultDto(results); } public virtual async Task UnlinkAsync(AccountUnlinkDto input) { - await LinkUserManager.UnlinkAsync(new IdentityLinkUserInfo(CurrentUser.Id!.Value, CurrentUser.TenantId), new IdentityLinkUserInfo(input.UserId, input.TenantId)); + var source = new IdentityLinkUserInfo(input.UserId, input.TenantId); + var target = new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentUser.TenantId); + await LinkUserManager.UnlinkAsync(source, target); await SecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = "Unlink", UserName = CurrentUser.UserName, - ExtraProperties = { ["TargetUserId"] = input.UserId } + ExtraProperties = { ["SourceUserId"] = input.UserId } }); } public virtual async Task CreateLinkTokenAsync() { - var source = new IdentityLinkUserInfo(CurrentUser.Id!.Value, CurrentUser.TenantId); - var token = await LinkUserManager.GenerateLinkTokenAsync(source, source.UserId.ToString()); + var target = new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentUser.TenantId); + var token = await LinkUserManager.GenerateLinkTokenAsync(target, target.UserId.ToString()); return new AccountLinkDto { Token = token, - UserId = CurrentUser.Id!.Value, + UserId = CurrentUser.GetId(), }; } @@ -80,35 +117,35 @@ public virtual async Task VerifyLinkTokenAs { var user = await UserManager.GetByIdAsync(input.UserId); - var source = new IdentityLinkUserInfo(input.UserId, user.TenantId); - var target = new IdentityLinkUserInfo(CurrentUser.Id!.Value, CurrentUser.TenantId); + var target = new IdentityLinkUserInfo(input.UserId, user.TenantId); + var source = new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentUser.TenantId); - var tokenVerified = await LinkUserManager.VerifyLinkTokenAsync(source, input.Token, source.UserId.ToString()); + var tokenVerified = await LinkUserManager.VerifyLinkTokenAsync(target, input.Token, target.UserId.ToString()); - if (tokenVerified) + if (!tokenVerified) { - if (!await LinkUserManager.IsLinkedAsync(source, target)) + return new AccountLinkTokenValidationResultDto { - await LinkUserManager.LinkAsync(source, target); - } + Verified = false, + }; + } + + if (!await LinkUserManager.IsLinkedAsync(source, target)) + { + await LinkUserManager.LinkAsync(source, target); await SecurityLogManager.SaveAsync(new IdentitySecurityLogContext { Identity = IdentitySecurityLogIdentityConsts.Identity, Action = "LinkUser", UserName = CurrentUser.UserName, - ExtraProperties = { ["SourceUserId"] = source.UserId } + ExtraProperties = { ["TargetUserId"] = target.UserId } }); - - return new AccountLinkTokenValidationResultDto - { - Verified = true, - }; } return new AccountLinkTokenValidationResultDto { - Verified = false, + Verified = true, }; } } diff --git a/modules/account/src/Passingwind.Abp.Account.Application/AccountUserDelegationAppService.cs b/modules/account/src/Passingwind.Abp.Account.Application/AccountUserDelegationAppService.cs new file mode 100644 index 0000000..e619ec9 --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.Application/AccountUserDelegationAppService.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Identity; +using Volo.Abp.Users; + +namespace Passingwind.Abp.Account; + +[Authorize] +public class AccountUserDelegationAppService : AccountAppBaseService, IAccountUserDelegationAppService +{ + protected IIdentityUserRepository UserRepository { get; } + protected IdentityUserDelegationManager UserDelegationManager { get; } + + public AccountUserDelegationAppService(IIdentityUserRepository userRepository, IdentityUserDelegationManager userDelegationManager) + { + UserRepository = userRepository; + UserDelegationManager = userDelegationManager; + } + + public virtual async Task CreateAsync(AccountUserDelegationCreateDto input) + { + if (CurrentUser.FindImpersonatorUserId().HasValue) + { + throw new BusinessException(AccountErrorCodes.UserActionDisabledInDelegatedMode); + } + + var user = await UserRepository.FindAsync(input.UserId); + + if (user == null) + { + throw new BusinessException(AccountErrorCodes.UserNotFound); + } + + await UserDelegationManager.DelegateNewUserAsync( + sourceUserId: CurrentUser.GetId(), + targetUserId: input.UserId, + startTime: input.StartTime, + endTime: input.EndTime); + } + + public virtual async Task> GetDelegatedListAsync() + { + var list = await UserDelegationManager.GetListAsync(targetUserId: CurrentUser.GetId()); + + var users = await UserRepository.GetListByIdsAsync(list.Select(x => x.SourceUserId).Distinct()); + + return new ListResultDto(list.ConvertAll(x => new AccountUserDelegationDto + { + Id = x.Id, + UserId = x.SourceUserId, + UserName = users.Find(u => u.Id == x.SourceUserId)?.UserName, + EndTime = x.EndTime, + StartTime = x.StartTime, + IsActive = Clock.Now >= x.StartTime && Clock.Now <= x.EndTime, + })); + } + + public virtual async Task> GetMyDelegationListAsync() + { + var list = await UserDelegationManager.GetListAsync(sourceUserId: CurrentUser.GetId()); + + var users = await UserRepository.GetListByIdsAsync(list.Select(x => x.TargetUserId).Distinct()); + + return new ListResultDto(list.ConvertAll(x => new AccountUserDelegationDto + { + Id = x.Id, + UserId = x.TargetUserId, + UserName = users.Find(u => u.Id == x.TargetUserId)?.UserName, + EndTime = x.EndTime, + StartTime = x.StartTime, + IsActive = Clock.Now >= x.StartTime && Clock.Now <= x.EndTime, + })); + } + + public virtual async Task DeleteAsync(Guid id) + { + if (CurrentUser.FindImpersonatorUserId().HasValue) + { + throw new BusinessException(AccountErrorCodes.UserActionDisabledInDelegatedMode); + } + + await UserDelegationManager.DeleteDelegationAsync(id, CurrentUser.GetId()); + } + + public virtual async Task> UserLookupAsync(string? filter = null) + { + var list = await UserRepository.GetListAsync(sorting: nameof(IdentityUser.UserName), maxResultCount: 10, filter: filter); + + return new ListResultDto(ObjectMapper.Map, List>(list)); + } +} diff --git a/modules/account/src/Passingwind.Abp.Account.Application/Passingwind.Abp.Account.Application.csproj b/modules/account/src/Passingwind.Abp.Account.Application/Passingwind.Abp.Account.Application.csproj index 80d10ce..3fbf08a 100644 --- a/modules/account/src/Passingwind.Abp.Account.Application/Passingwind.Abp.Account.Application.csproj +++ b/modules/account/src/Passingwind.Abp.Account.Application/Passingwind.Abp.Account.Application.csproj @@ -13,6 +13,7 @@ + diff --git a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/AccountErrorCodes.cs b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/AccountErrorCodes.cs index 3797e0e..cd6f0d1 100644 --- a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/AccountErrorCodes.cs +++ b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/AccountErrorCodes.cs @@ -24,6 +24,8 @@ public static class AccountErrorCodes public const string UserRequireConfirmedPhoneNumber = "Account:Error:0305"; public const string UserNotLink = "Account:Error:0401"; + public const string UserNotDelegated = "Account:Error:0402"; + public const string UserActionDisabledInDelegatedMode = "Account:Error:0403"; public const string ExternalLoginRemoteError = "Account:Error:0501"; public const string ExternalLoginInfoAvailable = "Account:Error:0502"; diff --git a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/en.json b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/en.json index 85b2e93..f6154d5 100644 --- a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/en.json +++ b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/en.json @@ -17,6 +17,8 @@ "Account:Error:0304": "The user email is requies confirmed", "Account:Error:0305": "The user phone number is requies confirmed", "Account:Error:0401": "The user not link for current user", + "Account:Error:0402": "The user is not delegated for current user", + "Account:Error:0403": "User delegation is not available for impersonated users!", "Account:Error:0501": "A remote error occurred during external login", "Account:Error:0502": "External login is not available now", "Account:Error:0503": "The user was not found in external login", diff --git a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/zh-Hans.json b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/zh-Hans.json index dc916ac..0fccd83 100644 --- a/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/zh-Hans.json +++ b/modules/account/src/Passingwind.Abp.Account.Domain.Shared/Localization/Account/zh-Hans.json @@ -17,6 +17,8 @@ "Account:Error:0304": "用户邮箱需要确认", "Account:Error:0305": "用户电话号码需要确认", "Account:Error:0401": "用户没有链接到当前账号", + "Account:Error:0402": "用户未被委托到当前账号", + "Account:Error:0403": "用户委托不适用于模拟用户", "Account:Error:0501": "外部登录出现远程错误", "Account:Error:0502": "外部登录现在不可用", "Account:Error:0503": "外部登录未找到该用户", diff --git a/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountImpersonationController.cs b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountImpersonationController.cs index 217fad6..46fe08f 100644 --- a/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountImpersonationController.cs +++ b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountImpersonationController.cs @@ -7,7 +7,7 @@ namespace Passingwind.Abp.Account; [RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] [Area(AccountRemoteServiceConsts.ModuleName)] -[Route("api/account/impersonations")] +[Route("api/account/impersonation")] public class AccountImpersonationController : AccountBaseController, IAccountImpersonationAppService { private readonly IAccountImpersonationAppService _service; @@ -30,4 +30,18 @@ public virtual Task LinkLoginAsync(Guid userId) { return _service.LinkLoginAsync(userId); } + + /// + [HttpPost("logout")] + public virtual Task LogoutAsync() + { + return _service.LogoutAsync(); + } + + /// + [HttpPost("{userId}/delegation-login")] + public virtual Task DelegationLoginAsync(Guid userId) + { + return _service.DelegationLoginAsync(userId); + } } diff --git a/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountLinkUserController.cs b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountLinkUserController.cs index b8d4a47..108f35a 100644 --- a/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountLinkUserController.cs +++ b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountLinkUserController.cs @@ -40,7 +40,7 @@ public virtual Task VerifyLinkTokenAsync(Ac } /// - [HttpPost("unlink")] + [HttpDelete("unlink")] public virtual Task UnlinkAsync(AccountUnlinkDto input) { return _service.UnlinkAsync(input); diff --git a/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountUserDelegationController.cs b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountUserDelegationController.cs new file mode 100644 index 0000000..0656baf --- /dev/null +++ b/modules/account/src/Passingwind.Abp.Account.HttpApi/AccountUserDelegationController.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.Application.Dtos; + +namespace Passingwind.Abp.Account; + +[RemoteService(Name = AccountRemoteServiceConsts.RemoteServiceName)] +[Area(AccountRemoteServiceConsts.ModuleName)] +[Route("api/account/user-delegation")] +public class AccountUserDelegationController : AccountBaseController, IAccountUserDelegationAppService +{ + private readonly IAccountUserDelegationAppService _service; + + public AccountUserDelegationController(IAccountUserDelegationAppService service) + { + _service = service; + } + + /// + [HttpGet("my-delegations")] + public virtual Task> GetMyDelegationListAsync() + { + return _service.GetMyDelegationListAsync(); + } + + /// + [HttpGet("delegateds")] + public virtual Task> GetDelegatedListAsync() + { + return _service.GetDelegatedListAsync(); + } + + /// + [HttpPost] + public virtual Task CreateAsync(AccountUserDelegationCreateDto input) + { + return _service.CreateAsync(input); + } + + /// + [HttpDelete("{id}")] + public virtual Task DeleteAsync(Guid id) + { + return _service.DeleteAsync(id); + } + + /// + [HttpGet("user-lookup")] + public virtual Task> UserLookupAsync(string? filter = null) + { + return _service.UserLookupAsync(filter); + } +}