Skip to content

Commit

Permalink
feat: Add ImpersonationGrantValidator
Browse files Browse the repository at this point in the history
  • Loading branch information
wzh425 committed Mar 12, 2024
1 parent 4056654 commit aa5b611
Show file tree
Hide file tree
Showing 19 changed files with 235 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
<PackageReference Include="Magicodes.IE.Csv" Version="2.6.4" />
<PackageReference Include="Masa.BuildingBlocks.Authentication.OpenIdConnect.Domain" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.BuildingBlocks.Authentication.OpenIdConnect.Models" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.BuildingBlocks.StackSdks.Auth.Contracts" Version="1.0.1-preview.12" />
<PackageReference Include="Masa.BuildingBlocks.StackSdks.Auth.Contracts" Version="1.0.1-preview.10.1" />
<!--<PackageReference Include="Masa.BuildingBlocks.StackSdks.Auth.Contracts" Version="$(MasaFrameworkPackageVersion)" />-->
<PackageReference Include="Masa.Contrib.Configuration.ConfigurationApi.Dcc" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.Caching.Distributed.StackExchangeRedis" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.SearchEngine.AutoComplete.ElasticSearch" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Auth" Version="1.0.1-preview.12" />
<PackageReference Include="Masa.Contrib.StackSdks.Auth" Version="1.0.1-preview.10.1" />
<PackageReference Include="Masa.Utils.Extensions.Enums" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Utils.Extensions.Validations.FluentValidation" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,7 +41,8 @@ public CommandHandler(
IMasaConfiguration masaConfiguration,
IUnitOfWork unitOfWork,
LdapDomainService ldapDomainService,
RoleDomainService roleDomainService)
RoleDomainService roleDomainService,
IUserContext userContext)
{
_userRepository = userRepository;
_autoCompleteClient = autoCompleteClient;
Expand All @@ -59,6 +61,7 @@ public CommandHandler(
_unitOfWork = unitOfWork;
_ldapDomainService = ldapDomainService;
_roleDomainService = roleDomainService;
_userContext = userContext;
}

#region User
Expand Down Expand Up @@ -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<Guid>();
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
};
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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<ImpersonationCacheItem>
{
public override ImpersonationCacheItem Result { get; set; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImpersonationCacheItem>(key);
if (cacheItem == null)
{
throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.IMPERSONATION_TOKEN_ERROR_MESSAGE);
}

query.Result = cacheItem;

await _distributedCacheClient.RemoveAsync(key);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
19 changes: 19 additions & 0 deletions src/Services/Masa.Auth.Service.Admin/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<ImpersonateOutput> 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<ImpersonationCacheItem> GetImpersonatedUserAsync([FromServices] IEventBus eventBus, [FromQuery] string impersonationToken)
{
var query = new ImpersonatedUserQuery(impersonationToken);
await eventBus.PublishAsync(query);
return query.Result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageReference Include="AspNet.Security.OAuth.GitHub" Version="6.0.10" />
<PackageReference Include="AspNet.Security.OAuth.Weixin" Version="6.0.11" />
<PackageReference Include="Masa.BuildingBlocks.Authentication.OpenIdConnect.Models" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.BuildingBlocks.StackSdks.Auth.Contracts" Version="1.0.1-preview.11" />
<PackageReference Include="Masa.BuildingBlocks.StackSdks.Auth.Contracts" Version="1.0.1-preview.10.1" />
<PackageReference Include="Masa.Contrib.Exceptions" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.Caching.Distributed.StackExchangeRedis" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Utils.Extensions.DependencyInjection" Version="$(MasaFrameworkPackageVersion)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,15 @@
<MButton Icon OnClick="()=> OpenUpdateUserDialog(context.Item)">
<SIcon Tooltip="@T("Edit")">@IconConstants.Update</SIcon>
</MButton>
<MButton Class="mx-4" Icon OnClick="async ()=> await _userAuthorizeDialog.ShowAsync(context.Item.Id)">
<MButton Class="ml-4" Icon OnClick="async ()=> await _userAuthorizeDialog.ShowAsync(context.Item.Id)">
<SIcon Tooltip="@T("Authorize")">@IconConstants.Authorize</SIcon>
</MButton>
<MButton Icon OnClick="async ()=> await _userClaimsDialog.ShowAsync(context.Item.Id)">
<MButton Class="ml-4" Icon OnClick="async ()=> await _userClaimsDialog.ShowAsync(context.Item.Id)">
<SIcon Tooltip="@T("Claim")">@IconConstants.Claim</SIcon>
</MButton>
<MButton Class="ml-4" Icon OnClick="async ()=> await _userClaimsDialog.ShowAsync(context.Item.Id)">
<SIcon Tooltip="@T("ImpersonationLogin")">mdi-login-variant</SIcon>
</MButton>
</div>
break;
default:
Expand Down
3 changes: 2 additions & 1 deletion src/Web/Masa.Auth.Web.Admin.Rcl/wwwroot/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -663,5 +663,6 @@
"Cancel Filter": "取消筛选",
"RepeatAdd": "重复添加",
"Add Claim": "添加声明",
"Claim": "声明"
"Claim": "声明",
"ImpersonationLogin": "模拟登录"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Claim>();

if (!cacheItem.IsBackToImpersonator)
{
claims.Add(new Claim(IMPERSONATOR_USER_ID, cacheItem.ImpersonatorUserId.ToString()));
}

context.Result = new GrantValidationResult(cacheItem.TargetUserId.ToString(), "impersonation", claims);
}
}
2 changes: 1 addition & 1 deletion src/Web/Masa.Auth.Web.Sso/Masa.Auth.Web.Sso.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<PackageReference Include="Masa.Contrib.Development.DaprStarter.AspNetCore" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Caller" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Config" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Auth" Version="1.0.1-preview.12" />
<PackageReference Include="Masa.Contrib.StackSdks.Auth" Version="1.0.1-preview.10.1" />
<PackageReference Include="Masa.Contrib.StackSdks.Mc" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Pm" Version="$(MasaFrameworkPackageVersion)" />
<PackageReference Include="Masa.Contrib.StackSdks.Tsc.OpenTelemetry" Version="$(MasaFrameworkPackageVersion)" />
Expand Down

0 comments on commit aa5b611

Please sign in to comment.