diff --git a/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/CacheKey.cs b/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/CacheKey.cs
index 6d63a7cb7..bb20f953f 100644
--- a/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/CacheKey.cs
+++ b/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/CacheKey.cs
@@ -32,6 +32,7 @@ public static class CacheKey
const string EMAIL_UPDATE_SEND_PRE = "email_update_send:";
public const string STAFF_DEFAULT_PASSWORD = "staff_default_password";
const string MSG_VERIFIY_CODE_SEND_EXPIRED = "msg_verifiy_code_send_expired:";
+ const string IMPERSONATION_USER= "impersonation_user:";
public static string AllPermissionKey()
{
@@ -162,4 +163,9 @@ public static string MsgVerifiyCodeSendExpired(string key)
{
return $"{MSG_VERIFIY_CODE_SEND_EXPIRED}{key}";
}
+
+ public static string ImpersonationUserKey(string impersonationToken)
+ {
+ return $"{IMPERSONATION_USER}{impersonationToken}";
+ }
}
diff --git a/src/Contracts/Masa.Auth.Contracts.Admin/Masa.Auth.Contracts.Admin.csproj b/src/Contracts/Masa.Auth.Contracts.Admin/Masa.Auth.Contracts.Admin.csproj
index b7640bde4..700dad09c 100644
--- a/src/Contracts/Masa.Auth.Contracts.Admin/Masa.Auth.Contracts.Admin.csproj
+++ b/src/Contracts/Masa.Auth.Contracts.Admin/Masa.Auth.Contracts.Admin.csproj
@@ -12,12 +12,12 @@
-
+
-
+
diff --git a/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateInput.cs b/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateInput.cs
new file mode 100644
index 000000000..b6946728b
--- /dev/null
+++ b/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateInput.cs
@@ -0,0 +1,9 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Contracts.Admin.Subjects;
+
+public class ImpersonateInput
+{
+ public Guid UserId { get; set; }
+}
diff --git a/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateOutput.cs b/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateOutput.cs
new file mode 100644
index 000000000..7e08fc418
--- /dev/null
+++ b/src/Contracts/Masa.Auth.Contracts.Admin/Subjects/ImpersonateOutput.cs
@@ -0,0 +1,9 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Contracts.Admin.Subjects;
+
+public class ImpersonateOutput
+{
+ public string ImpersonationToken { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/CommandHandler.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/CommandHandler.cs
index 85bbb4dcd..52346b9de 100644
--- a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/CommandHandler.cs
+++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/CommandHandler.cs
@@ -22,6 +22,7 @@ public class CommandHandler
private readonly IUnitOfWork _unitOfWork;
private readonly LdapDomainService _ldapDomainService;
private readonly RoleDomainService _roleDomainService;
+ private readonly IUserContext _userContext;
public CommandHandler(
IUserRepository userRepository,
@@ -40,7 +41,8 @@ public CommandHandler(
IMasaConfiguration masaConfiguration,
IUnitOfWork unitOfWork,
LdapDomainService ldapDomainService,
- RoleDomainService roleDomainService)
+ RoleDomainService roleDomainService,
+ IUserContext userContext)
{
_userRepository = userRepository;
_autoCompleteClient = autoCompleteClient;
@@ -59,6 +61,7 @@ public CommandHandler(
_unitOfWork = unitOfWork;
_ldapDomainService = ldapDomainService;
_roleDomainService = roleDomainService;
+ _userContext = userContext;
}
#region User
@@ -615,4 +618,31 @@ public async Task SaveUserClaimValuesAsync(SaveUserClaimValuesCommand saveUserCl
await _userDomainService.UpdateAsync(user);
}
+
+ [EventHandler]
+ public async Task ImpersonateAsync(ImpersonateUserCommand command)
+ {
+ var userId = _userContext.GetUserId();
+ if (userId == default)
+ {
+ throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.USER_NOT_EXIST);
+ }
+ var cacheItem = new ImpersonationCacheItem(
+ command.UserId,
+ command.IsBackToImpersonator
+ );
+
+ if (!command.IsBackToImpersonator)
+ {
+ cacheItem.ImpersonatorUserId = userId;
+ }
+
+ var token = Guid.NewGuid().ToString();
+ var key = CacheKey.ImpersonationUserKey(token);
+ await _distributedCacheClient.SetAsync(key, cacheItem, TimeSpan.FromMinutes(1));
+
+ command.Result = new ImpersonateOutput {
+ ImpersonationToken = token
+ };
+ }
}
diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/ImpersonateUserCommand.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/ImpersonateUserCommand.cs
new file mode 100644
index 000000000..e1965f186
--- /dev/null
+++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/ImpersonateUserCommand.cs
@@ -0,0 +1,9 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Service.Admin.Application.Subjects.Commands;
+
+public record ImpersonateUserCommand(Guid UserId, bool IsBackToImpersonator) : Command
+{
+ public ImpersonateOutput Result { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Queries/ImpersonatedUserQuery.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Queries/ImpersonatedUserQuery.cs
new file mode 100644
index 000000000..f7cf3ebe6
--- /dev/null
+++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Queries/ImpersonatedUserQuery.cs
@@ -0,0 +1,9 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Service.Admin.Application.Subjects.Queries;
+
+public record ImpersonatedUserQuery(string ImpersonationToken) : Query
+{
+ public override ImpersonationCacheItem Result { get; set; } = new();
+}
diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/QueryHandler.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/QueryHandler.cs
index 99ee76bf9..88e10fab0 100644
--- a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/QueryHandler.cs
+++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/QueryHandler.cs
@@ -775,4 +775,19 @@ public async Task UserClaimValuesQueryAsync(UserClaimValuesQuery userClaimValues
userClaimValuesQuery.Result.TryAdd("userName", user.DisplayName);
}
}
+
+ [EventHandler]
+ public async Task GetImpersonatedUserAsync(ImpersonatedUserQuery query)
+ {
+ var key = CacheKey.ImpersonationUserKey(query.ImpersonationToken);
+ var cacheItem = await _distributedCacheClient.GetAsync(key);
+ if (cacheItem == null)
+ {
+ throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.IMPERSONATION_TOKEN_ERROR_MESSAGE);
+ }
+
+ query.Result = cacheItem;
+
+ await _distributedCacheClient.RemoveAsync(key);
+ }
}
diff --git a/src/Services/Masa.Auth.Service.Admin/Infrastructure/CacheModels/ImpersonationCacheItem.cs b/src/Services/Masa.Auth.Service.Admin/Infrastructure/CacheModels/ImpersonationCacheItem.cs
new file mode 100644
index 000000000..3d8cc693d
--- /dev/null
+++ b/src/Services/Masa.Auth.Service.Admin/Infrastructure/CacheModels/ImpersonationCacheItem.cs
@@ -0,0 +1,25 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Service.Admin.Infrastructure.CacheModels;
+
+[Serializable]
+public class ImpersonationCacheItem
+{
+ public Guid ImpersonatorUserId { get; set; }
+
+ public Guid TargetUserId { get; set; }
+
+ public bool IsBackToImpersonator { get; set; }
+
+ public ImpersonationCacheItem()
+ {
+
+ }
+
+ public ImpersonationCacheItem(Guid targetUserId, bool isBackToImpersonator)
+ {
+ TargetUserId = targetUserId;
+ IsBackToImpersonator = isBackToImpersonator;
+ }
+}
\ No newline at end of file
diff --git a/src/Services/Masa.Auth.Service.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs b/src/Services/Masa.Auth.Service.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs
index a33174835..37126f041 100644
--- a/src/Services/Masa.Auth.Service.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs
+++ b/src/Services/Masa.Auth.Service.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs
@@ -91,4 +91,5 @@ public static class UserFriendlyExceptionCodes
public const string IDENTITY_SOURCE_NAME_EXIST = "IdentitySourceNameExist";
public const string THIRDPARTYUSER_BIND_EXIST = "ThirdPartyUserBindExist";
public const string WEBHOOK_NOT_EXIST = "WebhookNotExist";
+ public const string IMPERSONATION_TOKEN_ERROR_MESSAGE = "ImpersonationTokenErrorMessage";
}
diff --git a/src/Services/Masa.Auth.Service.Admin/Services/UserService.cs b/src/Services/Masa.Auth.Service.Admin/Services/UserService.cs
index 87b24cfd5..9fb2f4b8d 100644
--- a/src/Services/Masa.Auth.Service.Admin/Services/UserService.cs
+++ b/src/Services/Masa.Auth.Service.Admin/Services/UserService.cs
@@ -1,6 +1,8 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+using Nest;
+
namespace Masa.Auth.Service.Admin.Services;
public class UserService : ServiceBase
@@ -381,4 +383,21 @@ public async Task SaveClaimValuesAsync(IEventBus eventBus, UserClaimValuesDto us
var command = new SaveUserClaimValuesCommand(userClaimValues.UserId, userClaimValues.ClaimValues);
await eventBus.PublishAsync(command);
}
+
+ [RoutePattern("impersonate", StartWithBaseUri = true, HttpMethod = "Post")]
+ public async Task ImpersonateAsync(IEventBus eventBus, [FromBody] ImpersonateInput input)
+ {
+ var command = new ImpersonateUserCommand(input.UserId, false);
+ await eventBus.PublishAsync(command);
+ return command.Result;
+ }
+
+ [AllowAnonymous]
+ [RoutePattern("impersonate", StartWithBaseUri = true, HttpMethod = "Get")]
+ public async Task GetImpersonatedUserAsync([FromServices] IEventBus eventBus, [FromQuery] string impersonationToken)
+ {
+ var query = new ImpersonatedUserQuery(impersonationToken);
+ await eventBus.PublishAsync(query);
+ return query.Result;
+ }
}
diff --git a/src/Web/Masa.Auth.Security.OAuth.Providers/Masa.Auth.Security.OAuth.Providers.csproj b/src/Web/Masa.Auth.Security.OAuth.Providers/Masa.Auth.Security.OAuth.Providers.csproj
index d2509e9b8..c32f9f9b5 100644
--- a/src/Web/Masa.Auth.Security.OAuth.Providers/Masa.Auth.Security.OAuth.Providers.csproj
+++ b/src/Web/Masa.Auth.Security.OAuth.Providers/Masa.Auth.Security.OAuth.Providers.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Web/Masa.Auth.Web.Admin.Rcl/Pages/Subjects/Users/User.razor b/src/Web/Masa.Auth.Web.Admin.Rcl/Pages/Subjects/Users/User.razor
index 22c6753bf..168bcbf87 100644
--- a/src/Web/Masa.Auth.Web.Admin.Rcl/Pages/Subjects/Users/User.razor
+++ b/src/Web/Masa.Auth.Web.Admin.Rcl/Pages/Subjects/Users/User.razor
@@ -67,12 +67,15 @@
@IconConstants.Update
-
+
@IconConstants.Authorize
-
+
@IconConstants.Claim
+
+ mdi-login-variant
+
break;
default:
diff --git a/src/Web/Masa.Auth.Web.Admin.Rcl/wwwroot/i18n/zh-CN.json b/src/Web/Masa.Auth.Web.Admin.Rcl/wwwroot/i18n/zh-CN.json
index d5470acf2..f06d144cd 100644
--- a/src/Web/Masa.Auth.Web.Admin.Rcl/wwwroot/i18n/zh-CN.json
+++ b/src/Web/Masa.Auth.Web.Admin.Rcl/wwwroot/i18n/zh-CN.json
@@ -663,5 +663,6 @@
"Cancel Filter": "取消筛选",
"RepeatAdd": "重复添加",
"Add Claim": "添加声明",
- "Claim": "声明"
+ "Claim": "声明",
+ "ImpersonationLogin": "模拟登录"
}
diff --git a/src/Web/Masa.Auth.Web.Admin.Server/Properties/launchSettings.json b/src/Web/Masa.Auth.Web.Admin.Server/Properties/launchSettings.json
index 3a001d8cc..2c579ff28 100644
--- a/src/Web/Masa.Auth.Web.Admin.Server/Properties/launchSettings.json
+++ b/src/Web/Masa.Auth.Web.Admin.Server/Properties/launchSettings.json
@@ -22,8 +22,8 @@
"TLS_NAME": "",
"MASA_CLUSTER": "Default",
"OTLP_URL": "https://otel-collector.masastack:9013",
- "REDIS": "{\"RedisHost\": \"10.130.0.235\", \"RedisPort\": 24615, \"RedisDb\": 0,\"RedisPassword\": \"Hzss@123\"}",
- "CONNECTIONSTRING": "{\"Server\": \"10.130.0.235\", \"Port\": 24878,\"Database\":\"pm-dev\",\"UserId\": \"ss\",\"Password\":\"Hzss@123\"}",
+ "REDIS": "{\"RedisHost\": \"10.130.0.19\", \"RedisPort\": 2135, \"RedisDb\": 3,\"RedisPassword\": \"Hzss@123\"}",
+ "CONNECTIONSTRING": "{\"Server\": \"10.130.0.19\", \"Port\": 2415,\"Database\":\"pm-dev\",\"UserId\": \"ss\",\"Password\":\"Hzss@123\"}",
"MASA_STACK": "[{\"id\":\"pm\",\"service\":{\"id\":\"pm-service-dev\",\"domain\":\"http://pm-service-dev.masastack.com\"},\"web\":{\"id\":\"pm-web-dev\",\"domain\":\"https://pm-dev.masastack.com\"}},{\"id\":\"dcc\",\"service\":{\"id\":\"dcc-service-dev\",\"domain\":\"http://dcc-service-dev.masastack.com\"},\"web\":{\"id\":\"dcc-web-dev\",\"domain\":\"https://dcc-dev.masastack.com\"}},{\"id\":\"tsc\",\"service\":{\"id\":\"tsc-service-dev\",\"domain\":\"http://tsc-service-dev.masastack.com\"},\"web\":{\"id\":\"tsc-web-dev\",\"domain\":\"https://tsc-dev.masastack.com\"}},{\"id\":\"alert\",\"service\":{\"id\":\"alert-service-dev\",\"domain\":\"http://alert-service-dev.masastack.com\"},\"web\":{\"id\":\"alert-web-dev\",\"domain\":\"https://alert-dev.masastack.com\"}},{\"id\":\"scheduler\",\"service\":{\"id\":\"scheduler-service-dev\",\"domain\":\"http://scheduler-service-dev.masastack.com\"},\"worker\":{\"id\":\"scheduler-worker-dev\",\"domain\":\"http://scheduler-worker-dev.masastack.com\"},\"web\":{\"id\":\"scheduler-web-dev\",\"domain\":\"https://scheduler-dev.masastack.com\"}},{\"id\":\"mc\",\"service\":{\"id\":\"mc-service-dev\",\"domain\":\"http://mc-service-dev.masastack.com\"},\"web\":{\"id\":\"mc-web-dev\",\"domain\":\"https://mc-dev.masastack.com\"}},{\"id\":\"auth\",\"service\":{\"id\":\"auth-service-dev\",\"domain\":\"http://auth-service-dev.masastack.com\"},\"web\":{\"id\":\"auth-web-dev\",\"domain\":\"https://auth-dev.masastack.com\"},\"sso\":{\"id\":\"auth-sso-dev\",\"domain\":\"https://auth-sso-dev.masastack.com\"}}]",
"ELASTIC": "{\"Nodes\": [\"http://es-ydy-new.lonsid.cn:9200\"],\"Index\": \"auth_user_development\"}",
"MASA_ENVIRONMENT": "Development",
diff --git a/src/Web/Masa.Auth.Web.Admin.Server/appsettings.Development.json b/src/Web/Masa.Auth.Web.Admin.Server/appsettings.Development.json
index 015d90b09..6a113e32e 100644
--- a/src/Web/Masa.Auth.Web.Admin.Server/appsettings.Development.json
+++ b/src/Web/Masa.Auth.Web.Admin.Server/appsettings.Development.json
@@ -11,12 +11,12 @@
"RedisOptions": {
"Servers": [
{
- "Host": "10.175.207.202",
- "Port": "30260"
+ "Host": "10.130.0.19",
+ "Port": "2135"
}
],
- "DefaultDatabase": 0,
- "Password": "Hzss@123redis"
+ "DefaultDatabase": 3,
+ "Password": "Hzss@123"
}
}
}
diff --git a/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationCacheItem.cs b/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationCacheItem.cs
new file mode 100644
index 000000000..8aa97d0ff
--- /dev/null
+++ b/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationCacheItem.cs
@@ -0,0 +1,32 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Web.Sso.Infrastructure.Validations;
+
+[Serializable]
+public class ImpersonationCacheItem
+{
+ public const string CACHE_NAME = "AppImpersonationCache";
+
+ public int? ImpersonatorTenantId { get; set; }
+
+ public long ImpersonatorUserId { get; set; }
+
+ public int? TargetTenantId { get; set; }
+
+ public long TargetUserId { get; set; }
+
+ public bool IsBackToImpersonator { get; set; }
+
+ public ImpersonationCacheItem()
+ {
+
+ }
+
+ public ImpersonationCacheItem(int? targetTenantId, long targetUserId, bool isBackToImpersonator)
+ {
+ TargetTenantId = targetTenantId;
+ TargetUserId = targetUserId;
+ IsBackToImpersonator = isBackToImpersonator;
+ }
+}
\ No newline at end of file
diff --git a/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationGrantValidator.cs b/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationGrantValidator.cs
new file mode 100644
index 000000000..d84b0e911
--- /dev/null
+++ b/src/Web/Masa.Auth.Web.Sso/Infrastructure/Validations/ImpersonationGrantValidator.cs
@@ -0,0 +1,53 @@
+// Copyright (c) MASA Stack All rights reserved.
+// Licensed under the Apache License. See LICENSE.txt in the project root for license information.
+
+namespace Masa.Auth.Web.Sso.Infrastructure.Validations;
+
+public class ImpersonationGrantValidator : IExtensionGrantValidator
+{
+ IAuthClient _authClient;
+ public string GrantType { get; } = "impersonation";
+
+ const string IMPERSONATOR_USER_ID = "http://Lonsid.org/identity/claims/impersonatorUserId";
+
+ public ImpersonationGrantValidator(IAuthClient authClient)
+ {
+ _authClient = authClient;
+ }
+
+ public async Task ValidateAsync(ExtensionGrantValidationContext context)
+ {
+ var impersonationToken = context.Request.Raw["impersonationToken"];
+ if (string.IsNullOrEmpty(impersonationToken))
+ {
+ context.Result = new GrantValidationResult
+ {
+ IsError = true,
+ Error = "Must provide impersonationToken",
+ ErrorDescription = "Must provide impersonationToken"
+ };
+ return;
+ }
+
+ var cacheItem = await _authClient.UserService.GetImpersonatedUserAsync(impersonationToken);
+ if (cacheItem is null)
+ {
+ context.Result = new GrantValidationResult
+ {
+ IsError = true,
+ Error = "Impersonated user does not exist",
+ ErrorDescription = "Impersonated user does not exist",
+ };
+ return;
+ }
+
+ var claims = new List();
+
+ if (!cacheItem.IsBackToImpersonator)
+ {
+ claims.Add(new Claim(IMPERSONATOR_USER_ID, cacheItem.ImpersonatorUserId.ToString()));
+ }
+
+ context.Result = new GrantValidationResult(cacheItem.TargetUserId.ToString(), "impersonation", claims);
+ }
+}
diff --git a/src/Web/Masa.Auth.Web.Sso/Masa.Auth.Web.Sso.csproj b/src/Web/Masa.Auth.Web.Sso/Masa.Auth.Web.Sso.csproj
index 942690175..39fc31c38 100644
--- a/src/Web/Masa.Auth.Web.Sso/Masa.Auth.Web.Sso.csproj
+++ b/src/Web/Masa.Auth.Web.Sso/Masa.Auth.Web.Sso.csproj
@@ -24,7 +24,7 @@
-
+