20 changed files with 708 additions and 149 deletions
@ -1,2 +0,0 @@ |
|||||
#Payment__PayPal__ClientId=PAYPAL_CLIENT_ID |
|
||||
#Payment__PayPal__Secret=PAYPAL_SECRET |
|
||||
@ -1,45 +1,55 @@ |
|||||
using System; |
using System; |
||||
using System.Linq; |
using System.Linq; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using EShopOnAbp.IdentityService.Keycloak; |
using EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
using Keycloak.Net.Models.Roles; |
||||
using Microsoft.Extensions.Logging; |
using Microsoft.Extensions.Logging; |
||||
using Volo.Abp.BackgroundJobs; |
using Volo.Abp.BackgroundJobs; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
|
||||
namespace EShopOnAbp.IdentityService.BackgroundJobs.Roles; |
namespace EShopOnAbp.IdentityService.BackgroundJobs.Roles; |
||||
|
|
||||
public class KeycloakRoleUpdatingJob : AsyncBackgroundJob<IdentityRoleUpdatingArgs>, ITransientDependency |
public class KeycloakRoleUpdatingJob : AsyncBackgroundJob<IdentityRoleUpdatingArgs>, ITransientDependency |
||||
{ |
{ |
||||
private readonly KeycloakService _keycloakService; |
private readonly IKeycloakService _keycloakService; |
||||
private readonly ILogger<KeycloakRoleUpdatingJob> _logger; |
private readonly ILogger<KeycloakRoleUpdatingJob> _logger; |
||||
|
private readonly IObjectMapper _objectMapper; |
||||
|
|
||||
public KeycloakRoleUpdatingJob(KeycloakService keycloakService, ILogger<KeycloakRoleUpdatingJob> logger) |
public KeycloakRoleUpdatingJob(IKeycloakService keycloakService, ILogger<KeycloakRoleUpdatingJob> logger, |
||||
|
IObjectMapper objectMapper) |
||||
{ |
{ |
||||
_keycloakService = keycloakService; |
_keycloakService = keycloakService; |
||||
_logger = logger; |
_logger = logger; |
||||
|
_objectMapper = objectMapper; |
||||
} |
} |
||||
|
|
||||
public override async Task ExecuteAsync(IdentityRoleUpdatingArgs args) |
public override async Task ExecuteAsync(IdentityRoleUpdatingArgs args) |
||||
{ |
{ |
||||
try |
try |
||||
{ |
{ |
||||
var existingRole = (await _keycloakService.GetRolesAsync()).FirstOrDefault(q => q.Name == args.Name); |
var existingRole = (await _keycloakService.GetRolesAsync()).FirstOrDefault(q => q.Name == args.OldName); |
||||
if (existingRole == null) |
if (existingRole == null) |
||||
{ |
{ |
||||
_logger.LogWarning($"Role with the name:{args.Name} couldn't be found to update!"); |
_logger.LogWarning($"Role with the name:{args.OldName} couldn't be found to update!"); |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
existingRole.Name = args.Name; |
if (args.OldName != args.NewName) |
||||
|
{ |
||||
|
existingRole.Name = args.NewName; |
||||
|
|
||||
await _keycloakService.UpdateRoleAsync(existingRole.Id, existingRole); |
await _keycloakService.UpdateRoleAsync(existingRole.Id, |
||||
|
_objectMapper.Map<CachedKeycloakRole, Role>(existingRole) |
||||
|
); |
||||
|
} |
||||
} |
} |
||||
catch (Exception e) |
catch (Exception e) |
||||
{ |
{ |
||||
_logger.LogWarning($"Could not delete the role with the name:{args.Name} from Keycloak server!"); |
_logger.LogWarning($"Could not update the role with the name:{args.OldName} from Keycloak server!"); |
||||
throw; |
throw; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
public record IdentityRoleUpdatingArgs(string Name); |
public record IdentityRoleUpdatingArgs(string OldName, string NewName); |
||||
@ -0,0 +1,21 @@ |
|||||
|
using AutoMapper; |
||||
|
using EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
using Keycloak.Net.Models.Roles; |
||||
|
using Keycloak.Net.Models.Users; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService; |
||||
|
|
||||
|
public class EShopOnAbpIdentityServiceAutoMapperProfile : Profile |
||||
|
{ |
||||
|
public EShopOnAbpIdentityServiceAutoMapperProfile() |
||||
|
{ |
||||
|
CreateMap<User, CachedKeycloakUser>().ReverseMap(); |
||||
|
CreateMap<UserAccess, CachedUserAccess>().ReverseMap(); |
||||
|
CreateMap<UserConsent, CachedUserConsent>().ReverseMap(); |
||||
|
CreateMap<Credentials, CachedCredentials>().ReverseMap(); |
||||
|
CreateMap<FederatedIdentity, CachedFederatedIdentity>(); |
||||
|
|
||||
|
CreateMap<Role, CachedKeycloakRole>().ReverseMap(); |
||||
|
CreateMap<RoleComposite, CachedRoleComposite>().ReverseMap(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// using System;
|
||||
|
// using System.Linq;
|
||||
|
// using System.Threading.Tasks;
|
||||
|
// using EShopOnAbp.IdentityService.Keycloak;
|
||||
|
// using Microsoft.Extensions.Logging;
|
||||
|
// using Volo.Abp.DependencyInjection;
|
||||
|
// using Volo.Abp.Domain.Entities.Events.Distributed;
|
||||
|
// using Volo.Abp.EventBus.Distributed;
|
||||
|
// using Volo.Abp.Identity;
|
||||
|
//
|
||||
|
// namespace EShopOnAbp.IdentityService.EventHandlers.Roles;
|
||||
|
//
|
||||
|
// public class KeycloakRolesEventHandler : IDistributedEventHandler<EntityCreatedEto<IdentityRoleEto>>,
|
||||
|
// ITransientDependency
|
||||
|
// {
|
||||
|
// private readonly KeycloakService _keycloakService;
|
||||
|
// private readonly ILogger<KeycloakRolesEventHandler> _logger;
|
||||
|
//
|
||||
|
// public KeycloakRolesEventHandler(KeycloakService keycloakService, ILogger<KeycloakRolesEventHandler> logger)
|
||||
|
// {
|
||||
|
// _keycloakService = keycloakService;
|
||||
|
// _logger = logger;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task HandleEventAsync(EntityCreatedEto<IdentityRoleEto> eventData)
|
||||
|
// {
|
||||
|
// try
|
||||
|
// {
|
||||
|
// var existingRole = (await _keycloakService.GetRolesAsync()).FirstOrDefault(q => q.Name == eventData.Entity.Name);
|
||||
|
// if (existingRole != null)
|
||||
|
// {
|
||||
|
// return;
|
||||
|
// }
|
||||
|
//
|
||||
|
// var isSuccess = await _keycloakService.CreateRoleAsync(eventData.Entity.Name);
|
||||
|
// if (isSuccess)
|
||||
|
// {
|
||||
|
// _logger.LogInformation($"Role created:{eventData.Entity.Name}");
|
||||
|
// }
|
||||
|
// }
|
||||
|
// catch (Exception e)
|
||||
|
// {
|
||||
|
// _logger.LogError($"Keycloak role creation with the name:{eventData.Entity.Name} failed!");
|
||||
|
// throw;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// }
|
||||
@ -1,85 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Keycloak.Net; |
|
||||
using Keycloak.Net.Models.Roles; |
|
||||
using Keycloak.Net.Models.Users; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace EShopOnAbp.IdentityService.Keycloak; |
|
||||
|
|
||||
public class KeycloakService : ITransientDependency |
|
||||
{ |
|
||||
private readonly KeycloakClient _keycloakClient; |
|
||||
private readonly KeycloakClientOptions _keycloakOptions; |
|
||||
|
|
||||
public KeycloakService(IOptions<KeycloakClientOptions> keycloakOptions) |
|
||||
{ |
|
||||
_keycloakOptions = keycloakOptions.Value; |
|
||||
|
|
||||
_keycloakClient = new KeycloakClient( |
|
||||
_keycloakOptions.Url, |
|
||||
_keycloakOptions.AdminUserName, |
|
||||
_keycloakOptions.AdminPassword |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
public Task<IEnumerable<User>> GetUsersAsync(string search = null, string username = null, string email = null, |
|
||||
CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, search: search, username: username, |
|
||||
email: email, cancellationToken: cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<User> GetUserAsync(string userId, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.GetUserAsync(_keycloakOptions.RealmName, userId, cancellationToken: cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> CreateUserAsync(User user, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.CreateUserAsync(_keycloakOptions.RealmName, user, cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> UpdateUserAsync(string userId, User user, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, userId, user, cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.DeleteUserAsync(_keycloakOptions.RealmName, userId, cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<IEnumerable<Role>> GetRolesAsync(CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.GetRolesAsync(_keycloakOptions.RealmName, cancellationToken: cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> AddRolesToUserAsync(string userId, IEnumerable<Role> roles, |
|
||||
CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.AddRealmRoleMappingsToUserAsync(_keycloakOptions.RealmName, userId, roles, |
|
||||
cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> CreateRoleAsync(string name, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
Role role = new Role |
|
||||
{ |
|
||||
Name = name |
|
||||
}; |
|
||||
return _keycloakClient.CreateRoleAsync(_keycloakOptions.RealmName, role, cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> DeleteRoleByIdAsync(string id, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.DeleteRoleByIdAsync(_keycloakOptions.RealmName, id, cancellationToken); |
|
||||
} |
|
||||
|
|
||||
public Task<bool> UpdateRoleAsync(string id, Role role, CancellationToken cancellationToken = default) |
|
||||
{ |
|
||||
return _keycloakClient.UpdateRoleByIdAsync(_keycloakOptions.RealmName, id, role, cancellationToken); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,23 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Caching; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
|
||||
|
[CacheName("KeycloakRole")] |
||||
|
public class CachedKeycloakRole |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
public string Name { get; set; } |
||||
|
public string Description { get; set; } |
||||
|
public bool? Composite { get; set; } |
||||
|
public CachedRoleComposite Composites { get; set; } |
||||
|
public bool? ClientRole { get; set; } |
||||
|
public string ContainerId { get; set; } |
||||
|
public IDictionary<string, object> Attributes { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CachedRoleComposite |
||||
|
{ |
||||
|
public IDictionary<string, string> Client { get; set; } |
||||
|
public IEnumerable<string> Realm { get; set; } |
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
// using System.Collections.Generic;
|
||||
|
// using System.Linq;
|
||||
|
// using System.Threading;
|
||||
|
// using System.Threading.Tasks;
|
||||
|
// using Keycloak.Net.Models.Roles;
|
||||
|
// using Keycloak.Net.Models.Users;
|
||||
|
// using Volo.Abp.Caching;
|
||||
|
// using Volo.Abp.DependencyInjection;
|
||||
|
//
|
||||
|
// namespace EShopOnAbp.IdentityService.Keycloak.Service;
|
||||
|
//
|
||||
|
// [ExposeServices(typeof(CachedKeycloakService))]
|
||||
|
// public class CachedKeycloakService : IKeycloakService
|
||||
|
// {
|
||||
|
// protected const string UsersCacheKey = "KeycloakUsers";
|
||||
|
// protected const string RolesCacheKey = "KeycloakRoles";
|
||||
|
// private readonly IKeycloakService _keycloakService;
|
||||
|
// private readonly IDistributedCache<List<User>> _keycloakUsersCache;
|
||||
|
// private readonly IDistributedCache<List<Role>> _keycloakRolesCache;
|
||||
|
//
|
||||
|
// public CachedKeycloakService(IKeycloakService keycloakService,
|
||||
|
// IDistributedCache<List<User>> keycloakUsersCache,
|
||||
|
// IDistributedCache<List<Role>> keycloakRolesCache)
|
||||
|
// {
|
||||
|
// _keycloakService = keycloakService;
|
||||
|
// _keycloakUsersCache = keycloakUsersCache;
|
||||
|
// _keycloakRolesCache = keycloakRolesCache;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<IEnumerable<User>> GetUsersAsync(string search = null, string username = null,
|
||||
|
// string email = null,
|
||||
|
// CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var users = await _keycloakUsersCache.GetAsync(UsersCacheKey, token: cancellationToken);
|
||||
|
// if (users == null)
|
||||
|
// {
|
||||
|
// users = (await _keycloakService.GetUsersAsync(search, username, email, cancellationToken)).ToList();
|
||||
|
// await _keycloakUsersCache.SetAsync(UsersCacheKey, users, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return users;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> CreateUserAsync(User user, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.CreateUserAsync(user, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> UpdateUserAsync(string userId, User user, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.UpdateUserAsync(userId, user, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.DeleteUserAsync(userId, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<IEnumerable<Role>> GetRolesAsync(CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var roles = await _keycloakRolesCache.GetAsync(RolesCacheKey, token: cancellationToken);
|
||||
|
// if (roles == null)
|
||||
|
// {
|
||||
|
// roles = (await _keycloakService.GetRolesAsync(cancellationToken: cancellationToken)).ToList();
|
||||
|
// await _keycloakRolesCache.SetAsync(RolesCacheKey, roles, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return roles;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public Task<bool> AddRolesToUserAsync(string userId, IEnumerable<Role> roles,
|
||||
|
// CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// return _keycloakService.AddRolesToUserAsync(userId, roles, cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> CreateRoleAsync(string name, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.CreateRoleAsync(name, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> DeleteRoleByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.DeleteRoleByIdAsync(id, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
//
|
||||
|
// public async Task<bool> UpdateRoleAsync(string id, Role role, CancellationToken cancellationToken = default)
|
||||
|
// {
|
||||
|
// var result = await _keycloakService.UpdateRoleAsync(id, role, cancellationToken);
|
||||
|
// if (result)
|
||||
|
// {
|
||||
|
// await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken);
|
||||
|
// }
|
||||
|
//
|
||||
|
// return result;
|
||||
|
// }
|
||||
|
// }
|
||||
@ -0,0 +1,75 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using Volo.Abp.Caching; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
|
||||
|
[CacheName("KeycloakUser")] |
||||
|
public class CachedKeycloakUser |
||||
|
{ |
||||
|
public string Id { get; set; } |
||||
|
public long CreatedTimestamp { get; set; } |
||||
|
public string UserName { get; set; } |
||||
|
public bool? Enabled { get; set; } |
||||
|
public bool? Totp { get; set; } |
||||
|
public bool? EmailVerified { get; set; } |
||||
|
public string FirstName { get; set; } |
||||
|
public string LastName { get; set; } |
||||
|
public string Email { get; set; } |
||||
|
public Collection<string> DisableableCredentialTypes { get; set; } |
||||
|
public Collection<string> RequiredActions { get; set; } |
||||
|
public int? NotBefore { get; set; } |
||||
|
public Dictionary<string, IEnumerable<string>> Attributes { get; set; } |
||||
|
public IDictionary<string, object> ClientRoles { get; set; } |
||||
|
public string FederationLink { get; set; } |
||||
|
public IEnumerable<string> Groups { get; set; } |
||||
|
public string Origin { get; set; } |
||||
|
public string[] RealmRoles { get; set; } |
||||
|
public string Self { get; set; } |
||||
|
public string ServiceAccountClientId { get; set; } |
||||
|
public CachedUserAccess Access { get; set; } |
||||
|
public IEnumerable<CachedUserConsent> ClientConsents { get; set; } |
||||
|
public IEnumerable<CachedCredentials> Credentials { get; set; } |
||||
|
public IEnumerable<CachedFederatedIdentity> FederatedIdentities { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CachedUserConsent |
||||
|
{ |
||||
|
public string ClientId { get; set; } |
||||
|
public IEnumerable<string> GrantedClientScopes { get; set; } |
||||
|
public long? CreatedDate { get; set; } |
||||
|
public long? LastUpdatedDate { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CachedUserAccess |
||||
|
{ |
||||
|
public bool? ManageGroupMembership { get; set; } |
||||
|
public bool? View { get; set; } |
||||
|
public bool? MapRoles { get; set; } |
||||
|
public bool? Impersonate { get; set; } |
||||
|
public bool? Manage { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CachedCredentials |
||||
|
{ |
||||
|
public string Algorithm { get; set; } |
||||
|
public IDictionary<string, string> Config { get; set; } |
||||
|
public int? Counter { get; set; } |
||||
|
public long? CreatedDate { get; set; } |
||||
|
public string Device { get; set; } |
||||
|
public int? Digits { get; set; } |
||||
|
public int? HashIterations { get; set; } |
||||
|
public string HashSaltedValue { get; set; } |
||||
|
public int? Period { get; set; } |
||||
|
public string Salt { get; set; } |
||||
|
public bool? Temporary { get; set; } |
||||
|
public string Type { get; set; } |
||||
|
public string Value { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class CachedFederatedIdentity |
||||
|
{ |
||||
|
public string IdentityProvider { get; set; } |
||||
|
public string UserId { get; set; } |
||||
|
public string UserName { get; set; } |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Keycloak.Net.Models.Roles; |
||||
|
using Keycloak.Net.Models.Users; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
|
||||
|
/* |
||||
|
* This will be an external service from the Keycloak package. |
||||
|
* Keeping it under Application layer because the Keycloak.Net.Core package requires .Net6 target framework. |
||||
|
* Application.Contracts targets netstandard2.0 |
||||
|
*/ |
||||
|
public interface IKeycloakService : ITransientDependency |
||||
|
{ |
||||
|
Task<List<CachedKeycloakUser>> GetUsersAsync(string search = null, string username = null, string email = null, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> CreateUserAsync(User user, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> UpdateUserAsync(string userId, User user, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<CachedKeycloakRole>> GetRolesAsync(CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> AddRealmRolesToUserAsync(string userId, IEnumerable<Role> roles, CancellationToken cancellationToken = default); |
||||
|
public Task<IEnumerable<CachedKeycloakRole>> GetRealmRolesOfUserAsync(string userId, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task RemoveRealmRolesFromUserAsync(string userId, IEnumerable<Role> roles, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> CreateRoleAsync(string name, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> DeleteRoleByIdAsync(string id, CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<bool> UpdateRoleAsync(string id, Role role, CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
namespace EShopOnAbp.IdentityService.Keycloak; |
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
public class KeycloakClientOptions |
public class KeycloakClientOptions |
||||
{ |
{ |
||||
public string Url { get; set; } |
public string Url { get; set; } |
||||
@ -0,0 +1,182 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Keycloak.Net; |
||||
|
using Keycloak.Net.Models.Roles; |
||||
|
using Keycloak.Net.Models.Users; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Caching; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.ObjectMapping; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
|
||||
|
/* |
||||
|
* This will be an external service from the Keycloak package |
||||
|
*/ |
||||
|
[ExposeServices(typeof(IKeycloakService), typeof(KeycloakService))] |
||||
|
public class KeycloakService : IKeycloakService |
||||
|
{ |
||||
|
protected const string UsersCacheKey = "KeycloakUsers"; |
||||
|
protected const string RolesCacheKey = "KeycloakRoles"; |
||||
|
private readonly IObjectMapper _objectMapper; |
||||
|
private readonly IDistributedCache<List<CachedKeycloakUser>, string> _keycloakUsersCache; |
||||
|
private readonly IDistributedCache<List<CachedKeycloakRole>, string> _keycloakRolesCache; |
||||
|
private readonly IDistributedCache<List<CachedKeycloakRole>, string> _userRolesCache; |
||||
|
|
||||
|
private readonly KeycloakClient _keycloakClient; |
||||
|
private readonly KeycloakClientOptions _keycloakOptions; |
||||
|
|
||||
|
public KeycloakService( |
||||
|
IOptions<KeycloakClientOptions> keycloakOptions, |
||||
|
IObjectMapper objectMapper, |
||||
|
IDistributedCache<List<CachedKeycloakUser>, string> keycloakUsersCache, |
||||
|
IDistributedCache<List<CachedKeycloakRole>, string> keycloakRolesCache, |
||||
|
IDistributedCache<List<CachedKeycloakRole>, string> userRolesCache) |
||||
|
{ |
||||
|
_objectMapper = objectMapper; |
||||
|
_keycloakUsersCache = keycloakUsersCache; |
||||
|
_keycloakRolesCache = keycloakRolesCache; |
||||
|
_userRolesCache = userRolesCache; |
||||
|
|
||||
|
_keycloakOptions = keycloakOptions.Value; |
||||
|
_keycloakClient = new KeycloakClient( |
||||
|
_keycloakOptions.Url, |
||||
|
_keycloakOptions.AdminUserName, |
||||
|
_keycloakOptions.AdminPassword |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public async Task<List<CachedKeycloakUser>> GetUsersAsync(string search = null, string username = null, |
||||
|
string email = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var users = await _keycloakUsersCache.GetAsync(UsersCacheKey, token: cancellationToken); |
||||
|
if (users == null) |
||||
|
{ |
||||
|
var result = await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, search: search, |
||||
|
username: username, |
||||
|
email: email, cancellationToken: cancellationToken); |
||||
|
users = _objectMapper.Map<List<User>, List<CachedKeycloakUser>>(result.ToList()); |
||||
|
await _keycloakUsersCache.SetAsync(UsersCacheKey, users, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return users; |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> CreateUserAsync(User user, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.CreateUserAsync(_keycloakOptions.RealmName, user, cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> UpdateUserAsync(string userId, User user, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, userId, user, cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
// _keycloakClient.DeleteRealmRoleMappingsFromUserAsync()
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> DeleteUserAsync(string userId, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.DeleteUserAsync(_keycloakOptions.RealmName, userId, cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakUsersCache.RemoveAsync(UsersCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<List<CachedKeycloakRole>> GetRolesAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var roles = await _keycloakRolesCache.GetAsync(RolesCacheKey, token: cancellationToken); |
||||
|
if (roles == null) |
||||
|
{ |
||||
|
var result = |
||||
|
(await _keycloakClient.GetRolesAsync(_keycloakOptions.RealmName, cancellationToken: cancellationToken)) |
||||
|
.ToList(); |
||||
|
roles = _objectMapper.Map<List<Role>, List<CachedKeycloakRole>>(result.ToList()); |
||||
|
|
||||
|
await _keycloakRolesCache.SetAsync(RolesCacheKey, roles, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return roles; |
||||
|
} |
||||
|
|
||||
|
public Task<bool> AddRealmRolesToUserAsync(string userId, IEnumerable<Role> roles, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return _keycloakClient.AddRealmRoleMappingsToUserAsync(_keycloakOptions.RealmName, userId, roles, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
// Updating user with roles is not working
|
||||
|
public async Task<IEnumerable<CachedKeycloakRole>> GetRealmRolesOfUserAsync(string userId, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var userRoles = await _userRolesCache.GetAsync(userId, token: cancellationToken); |
||||
|
if (userRoles == null) |
||||
|
{ |
||||
|
var roles = (await _keycloakClient.GetRealmRoleMappingsForUserAsync(_keycloakOptions.RealmName, userId, |
||||
|
cancellationToken)) |
||||
|
.ToList(); |
||||
|
userRoles = _objectMapper.Map<List<Role>, List<CachedKeycloakRole>>(roles); |
||||
|
await _userRolesCache.SetAsync(userId, userRoles, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return userRoles; |
||||
|
} |
||||
|
|
||||
|
public Task RemoveRealmRolesFromUserAsync(string userId, IEnumerable<Role> roles, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return _keycloakClient.DeleteRealmRoleMappingsFromUserAsync(_keycloakOptions.RealmName, userId, roles, |
||||
|
cancellationToken); |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> CreateRoleAsync(string name, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.CreateRoleAsync(_keycloakOptions.RealmName, new Role() { Name = name }, |
||||
|
cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> DeleteRoleByIdAsync(string id, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.DeleteRoleByIdAsync(_keycloakOptions.RealmName, id, cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public async Task<bool> UpdateRoleAsync(string id, Role role, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var result = await _keycloakClient.UpdateRoleByIdAsync(_keycloakOptions.RealmName, id, role, cancellationToken); |
||||
|
if (result) |
||||
|
{ |
||||
|
await _keycloakRolesCache.RemoveAsync(RolesCacheKey, token: cancellationToken); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Security.Cryptography; |
||||
|
using System.Text; |
||||
|
using System.Text.Json; |
||||
|
using Keycloak.Net.Models.Roles; |
||||
|
using Keycloak.Net.Models.Users; |
||||
|
|
||||
|
namespace EShopOnAbp.IdentityService.Keycloak.Service; |
||||
|
|
||||
|
public static class KeycloakServiceExtensions |
||||
|
{ |
||||
|
public static string GenerateCacheKeyBasedOnValues(this IEnumerable<Role> roles) |
||||
|
{ |
||||
|
return GenerateUniqueCacheKeyBasedOnList(roles); |
||||
|
} |
||||
|
|
||||
|
public static string GenerateCacheKeyBasedOnValues(this IEnumerable<User> users) |
||||
|
{ |
||||
|
return GenerateUniqueCacheKeyBasedOnList(users); |
||||
|
} |
||||
|
|
||||
|
private static string GenerateUniqueCacheKeyBasedOnList<T>(IEnumerable<T> list) |
||||
|
{ |
||||
|
string serializedList = JsonSerializer.Serialize(list); |
||||
|
byte[] bytes = Encoding.UTF8.GetBytes(serializedList); |
||||
|
byte[] hash = SHA256.Create().ComputeHash(bytes); |
||||
|
string hashString = BitConverter.ToString(hash).Replace("-", ""); |
||||
|
return hashString; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue