Skip to content

Commit

Permalink
feat: support impersonate login and thirdPartyUser claimData (#1271)
Browse files Browse the repository at this point in the history
* feat: Add ThirdPartyUserClaim

* chore: Dockerfile

* feat: Add PssoPhoneNumberGrantValidator

* refactor:GetProfileDataAsync Claim padding adjustment

* feat: Add ImpersonationGrantValidator

* feat:  ImpersonatedNavigation

* feat: PssoPhoneCodeGrantValidator

* chore : Update package

* feat: Add PssoService

* reactor: Remove PSSO code

* reactor: Adjusting Ldap Authenticate

* chore: Update package

* chore:Restore Dockerfile

* style: global using

* chore:Upgrade Package

* style: Useless using

* style: global using

* refactor:Delete useless code and formatting
  • Loading branch information
wzh425 authored Apr 1, 2024
1 parent f6b8173 commit 0ec83eb
Show file tree
Hide file tree
Showing 33 changed files with 3,971 additions and 66 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<WarningsAsErrors>
$(WarningsAsErrors);CS8600;CS8601;CS8602;CS8603;CS8604;CS8609;CS8610;CS8614;CS8616;CS8618;CS8619;CS8622;CS8625
</WarningsAsErrors>
<MasaFrameworkPackageVersion>1.0.1-preview.10</MasaFrameworkPackageVersion>
<MasaFrameworkPackageVersion>1.0.1-preview.16</MasaFrameworkPackageVersion>
</PropertyGroup>
</Project>
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
@@ -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 @@ -619,4 +622,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(10));

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
@@ -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 ThirdPartyUserByUserIdQuery(Guid UserId, Guid ThirdPartyIdpId) : Query<UserModel?>
{
public override UserModel? Result { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,12 +422,29 @@ public async Task GetThirdPartyUserAsync(ThirdPartyUserQuery query)
.Include(tpu => tpu.User.Roles)
.FirstOrDefaultAsync(tpu => tpu.ThridPartyIdentity == query.ThridPartyIdentity);
var userModel = tpUser?.User?.Adapt<UserModel>();

if (tpUser != null && tpUser.User != null && userModel != null)
{
var staff = tpUser.User.Staff;
userModel.StaffId = (staff == null || !staff.Enabled) ? Guid.Empty : staff.Id;
userModel.CurrentTeamId = staff?.CurrentTeamId;
userModel.ClaimData = tpUser.ClaimData;
}

query.Result = userModel;
}

[EventHandler]
public async Task GetThirdPartyUserByUserIdAsync(ThirdPartyUserByUserIdQuery query)
{
var tpUser = await _authDbContext.Set<ThirdPartyUser>()
.Include(tpu => tpu.User)
.FirstOrDefaultAsync(tpu => tpu.ThirdPartyIdpId == query.ThirdPartyIdpId && tpu.UserId == query.UserId);
var userModel = tpUser?.User?.Adapt<UserModel>();

if (tpUser != null && tpUser.User != null && userModel != null)
{
userModel.ClaimData = tpUser.ClaimData;
}

query.Result = userModel;
Expand Down Expand Up @@ -758,4 +775,18 @@ 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,69 @@
// 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.Domain.Subjects.Aggregates;

public class PasswordType : Enumeration
{
public static PasswordType Default = new PasswordType();

public static PasswordType MD5 = new MD5PasswordType();

public static PasswordType HashPassword = new HashPasswordType();

public PasswordType() : base(0, "") { }

public PasswordType(int id, string name) : base(id, name)
{
}

public virtual string EncryptPassword(User user, string password)
{
return MD5.EncryptPassword(user, password);
}

public virtual bool VerifyPassword(User user, string encryptPassword, string providedPassword)
{
return MD5.VerifyPassword(user, encryptPassword, providedPassword);
}

public static PasswordType StartNew(string type) => type switch
{
nameof(MD5) => new MD5PasswordType(),
nameof(HashPassword) => new HashPasswordType(),
_ => new PasswordType()
};

private class MD5PasswordType : PasswordType
{
public MD5PasswordType() : base(1, nameof(MD5)) { }

public override string EncryptPassword(User user, string password)
{
return MD5Utils.EncryptRepeat(password);
}

public override bool VerifyPassword(User user, string encryptPassword, string providedPassword)
{
return encryptPassword == MD5Utils.EncryptRepeat(providedPassword ?? "");
}
}

private class HashPasswordType : PasswordType
{
public HashPasswordType() : base(2, nameof(HashPassword)) { }

public override string EncryptPassword(User user, string password)
{
var hasher = new PasswordHasher<User>();
return hasher.HashPassword(user, password);
}

public override bool VerifyPassword(User user, string encryptPassword, string providedPassword)
{
var hasher = new PasswordHasher<User>();
var result = hasher.VerifyHashedPassword(user, encryptPassword, providedPassword);
return result != PasswordVerificationResult.Failed;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public string ExtendedData

public IdentityProvider IdentityProvider => _identityProvider;

public Dictionary<string, string> ClaimData { get; private set; } = new();

public ThirdPartyUser(Guid thirdPartyIdpId, string thridPartyIdentity, string extendedData)
{
ThirdPartyIdpId = thirdPartyIdpId;
Expand Down Expand Up @@ -83,6 +85,11 @@ public void Update(string thridPartyIdentity, string extendedData)
ExtendedData = extendedData;
}

public void UpdateClaimData(Dictionary<string, string> claimData)
{
ClaimData = claimData;
}

public static implicit operator ThirdPartyUserDetailDto(ThirdPartyUser tpu)
{
return new ThirdPartyUserDetailDto(tpu.Id, tpu.Enabled, tpu.IdentityProvider.Adapt<IdentityProviderDetailDto>(), tpu.User, tpu.CreationTime, tpu.ModificationTime, tpu.CreateUser?.Name ?? "", tpu.ModifyUser?.Name ?? "");
Expand Down
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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public void Configure(EntityTypeBuilder<ThirdPartyUser> builder)
builder.HasOne(tpu => tpu.IdentityProvider).WithMany().HasForeignKey(tpu => tpu.ThirdPartyIdpId);
builder.HasIndex(u => new { u.CreationTime, u.ModificationTime });//.IsDescending(); supported 7.0
builder.Navigation(tpu => tpu.IdentityProvider).AutoInclude();
builder.Property(tpu => tpu.ClaimData).HasConversion(new JsonValueConverter<Dictionary<string, string>>());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.ValueConverters;

public class JsonValueConverter<T> : ValueConverter<T, string> where T : class, new()
{
public JsonValueConverter()
: base(x => SerializeObject(x), x => DeserializeObject(x))
{

}

private static string SerializeObject(T obj)
{
return JsonSerializer.Serialize(obj);
}

private static T DeserializeObject(string json)
{
if (string.IsNullOrEmpty(json))
{
return new T();
}

return JsonSerializer.Deserialize<T>(json)!;
}
}
Loading

0 comments on commit 0ec83eb

Please sign in to comment.