diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/IdentityUserDeletionJob.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/IdentityUserDeletionJob.cs new file mode 100644 index 00000000..21206599 --- /dev/null +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/IdentityUserDeletionJob.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Keycloak.Net; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.IdentityService.BackgroundJobs; + +public class IdentityUserDeletionJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly KeycloakClient _keycloakClient; + private readonly KeycloakClientOptions _keycloakOptions; + private readonly ILogger _logger; + + public IdentityUserDeletionJob(IOptions keycloakOptions, + ILogger logger) + { + _logger = logger; + _keycloakOptions = keycloakOptions.Value; + + _keycloakClient = new KeycloakClient( + _keycloakOptions.Url, + _keycloakOptions.AdminUserName, + _keycloakOptions.AdminPassword + ); + } + + public override async Task ExecuteAsync(IdentityUserDeletionArgs args) + { + try + { + var keycloakUser = (await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: args.UserName)) + .First(); + if (keycloakUser == null) + { + _logger.LogError($"Keycloak user could not be found to delete! Username:{args.UserName}"); + throw new UserFriendlyException($"Keycloak user with the username:{args.UserName} could not be found!"); + } + + var result = await _keycloakClient.DeleteUserAsync(_keycloakOptions.RealmName, keycloakUser.Id); + if (result) + { + _logger.LogInformation($"Keycloak user with the username:{args.UserName} has been deleted."); + } + } + catch (Exception e) + { + _logger.LogError($"Keycloak user deletion failed! Username:{args.UserName}"); + } + } +} + +public class IdentityUserDeletionArgs +{ + public string UserName { get; set; } +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakClientOptions.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakClientOptions.cs new file mode 100644 index 00000000..648a3823 --- /dev/null +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakClientOptions.cs @@ -0,0 +1,9 @@ +namespace EShopOnAbp.IdentityService.BackgroundJobs; + +public class KeycloakClientOptions +{ + public string Url { get; set; } + public string AdminUserName { get; set; } + public string AdminPassword { get; set; } + public string RealmName { get; set; } +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserCreationJob.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserCreationJob.cs new file mode 100644 index 00000000..1da1cbca --- /dev/null +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserCreationJob.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Keycloak.Net; +using Keycloak.Net.Models.Users; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.IdentityService.BackgroundJobs; + +public class KeycloakUserCreationJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly KeycloakClient _keycloakClient; + private readonly KeycloakClientOptions _keycloakOptions; + private readonly ILogger _logger; + + public KeycloakUserCreationJob(IOptions keycloakOptions, + ILogger logger) + { + _logger = logger; + _keycloakOptions = keycloakOptions.Value; + + _keycloakClient = new KeycloakClient( + _keycloakOptions.Url, + _keycloakOptions.AdminUserName, + _keycloakOptions.AdminPassword + ); + } + + public override async Task ExecuteAsync(IdentityUserCreationArgs args) + { + var keycloakUser = new User + { + Email = args.Email, + UserName = args.UserName, + FirstName = args.Name, + LastName = args.Surname, + Enabled = true, + Credentials = new List() + { + new() { Type = "password", Value = args.Password } + } + }; + + try + { + var result = await _keycloakClient.CreateUserAsync(_keycloakOptions.RealmName, keycloakUser); + if (result) + { + _logger.LogInformation($"Keycloak user with the username:{args.UserName} has been created."); + } + } + catch (Exception e) + { + _logger.LogError($"Keycloak user creation with the Username:{args.UserName} has been failed!"); + throw; + } + } +} + +public class IdentityUserCreationArgs +{ + public string Email { get; set; } + public string UserName { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + public string Password { get; set; } +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserUpdatingJob.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserUpdatingJob.cs new file mode 100644 index 00000000..17d82e38 --- /dev/null +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/BackgroundJobs/KeycloakUserUpdatingJob.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Keycloak.Net; +using Keycloak.Net.Models.Users; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.IdentityService.BackgroundJobs; + +public class KeycloakUserUpdatingJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly KeycloakClient _keycloakClient; + private readonly KeycloakClientOptions _keycloakOptions; + private readonly ILogger _logger; + + public KeycloakUserUpdatingJob(IOptions keycloakOptions, ILogger logger) + { + _logger = logger; + _keycloakOptions = keycloakOptions.Value; + + _keycloakClient = new KeycloakClient( + _keycloakOptions.Url, + _keycloakOptions.AdminUserName, + _keycloakOptions.AdminPassword + ); + } + + public override async Task ExecuteAsync(IdentityUserUpdatingArgs args) + { + try + { + var keycloakUser = (await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: args.UserName)) + .First(); + if (keycloakUser == null) + { + _logger.LogError($"Keycloak user could not be found to update! Username:{args.UserName}"); + throw new UserFriendlyException($"Keycloak user with the username:{args.UserName} could not be found!"); + } + + keycloakUser.UserName = args.UserName; + keycloakUser.Email = args.Email; + keycloakUser.FirstName = args.Name; + keycloakUser.LastName = args.Surname; + keycloakUser.EmailVerified = args.EmailConfirmed; + + var result = await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, keycloakUser.Id, keycloakUser); + if (result) + { + _logger.LogInformation($"Keycloak user with the username:{args.UserName} has been updated."); + } + } + catch (Exception e) + { + _logger.LogError($"Keycloak user updating failed! Username:{args.UserName}"); + } + } +} + +public class IdentityUserUpdatingArgs +{ + public string Email { get; set; } + public string UserName { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + public bool EmailConfirmed { get; set; } +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/EShopIdentityUserAppService.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/EShopIdentityUserAppService.cs new file mode 100644 index 00000000..673720e7 --- /dev/null +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/EShopIdentityUserAppService.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using EShopOnAbp.IdentityService.BackgroundJobs; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundJobs; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; + +namespace EShopOnAbp.IdentityService; + +[ExposeServices(typeof(IdentityUserAppService), typeof(IIdentityUserAppService))] +public class EShopIdentityUserAppService : IdentityUserAppService +{ + private readonly IBackgroundJobManager _backgroundJobManager; + private readonly IIdentityUserRepository _userRepository; + + public EShopIdentityUserAppService( + IdentityUserManager userManager, + IIdentityUserRepository userRepository, + IIdentityRoleRepository roleRepository, + IOptions identityOptions, IBackgroundJobManager backgroundJobManager) : base(userManager, + userRepository, + roleRepository, + identityOptions) + { + _backgroundJobManager = backgroundJobManager; + _userRepository = userRepository; + } + + public override async Task CreateAsync(IdentityUserCreateDto input) + { + var createdUser = await base.CreateAsync(input); + await _backgroundJobManager.EnqueueAsync(new IdentityUserCreationArgs + { + Email = createdUser.Email, + UserName = createdUser.UserName, + Name = createdUser.Name, + Surname = createdUser.Surname, + Password = input.Password + }); + + return createdUser; + } + + public override async Task UpdateAsync(Guid id, IdentityUserUpdateDto input) + { + var updatedUser = await base.UpdateAsync(id, input); + await _backgroundJobManager.EnqueueAsync(new IdentityUserUpdatingArgs + { + Email = updatedUser.Email, + UserName = updatedUser.UserName, + Name = updatedUser.Name, + Surname = updatedUser.Surname, + EmailConfirmed = updatedUser.EmailConfirmed + }); + + return updatedUser; + } + + public override async Task DeleteAsync(Guid id) + { + var user = await _userRepository.FindAsync(id); + await base.DeleteAsync(id); + + await _backgroundJobManager.EnqueueAsync(new IdentityUserDeletionArgs + { + UserName = user.UserName + }); + } +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/EShopOnAbp.IdentityService.Application.csproj b/services/identity/src/EShopOnAbp.IdentityService.Application/EShopOnAbp.IdentityService.Application.csproj index 9a5c929f..bb85acac 100644 --- a/services/identity/src/EShopOnAbp.IdentityService.Application/EShopOnAbp.IdentityService.Application.csproj +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/EShopOnAbp.IdentityService.Application.csproj @@ -11,7 +11,9 @@ + + diff --git a/services/identity/src/EShopOnAbp.IdentityService.Application/IdentityServiceApplicationModule.cs b/services/identity/src/EShopOnAbp.IdentityService.Application/IdentityServiceApplicationModule.cs index 502d3733..c0b7e66f 100644 --- a/services/identity/src/EShopOnAbp.IdentityService.Application/IdentityServiceApplicationModule.cs +++ b/services/identity/src/EShopOnAbp.IdentityService.Application/IdentityServiceApplicationModule.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using EShopOnAbp.IdentityService.BackgroundJobs; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AutoMapper; +using Volo.Abp.BackgroundJobs; using Volo.Abp.Identity; using Volo.Abp.Modularity; @@ -8,17 +10,29 @@ namespace EShopOnAbp.IdentityService [DependsOn( typeof(IdentityServiceDomainModule), typeof(IdentityServiceApplicationContractsModule), - typeof(AbpIdentityApplicationModule) - )] + typeof(AbpIdentityApplicationModule), + typeof(AbpBackgroundJobsModule) + )] public class IdentityServiceApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { + var configuration = context.Services.GetConfiguration(); + context.Services.AddAutoMapperObjectMapper(); Configure(options => { options.AddMaps(validate: true); }); + + Configure(options => + { + options.Url = configuration["Keycloak:url"]; + options.AdminUserName = configuration["Keycloak:adminUsername"]; + options.AdminPassword = configuration["Keycloak:adminPassword"]; + options.RealmName = configuration["Keycloak:realmName"]; + } + ); } } -} +} \ No newline at end of file diff --git a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json index 9cdfa28f..f37922ad 100644 --- a/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json +++ b/services/identity/src/EShopOnAbp.IdentityService.HttpApi.Host/appsettings.json @@ -9,6 +9,12 @@ "SwaggerClientId": "WebGateway_Swagger", "SwaggerClientSecret": "1q2w3e*" }, + "Keycloak": { + "url": "http://localhost:8080", + "adminUsername": "admin", + "adminPassword": "1q2w3E*", + "realmName": "master" + }, "Logging": { "LogLevel": { "Default": "Information",