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);
+ }
+}