diff --git a/source/backend/api/Pims.Api.csproj b/source/backend/api/Pims.Api.csproj index b10d75ea6b..b4f44470ea 100644 --- a/source/backend/api/Pims.Api.csproj +++ b/source/backend/api/Pims.Api.csproj @@ -2,8 +2,8 @@ 0ef6255f-9ea0-49ec-8c65-c172304b4926 - 4.0.0-67.25 - 4.0.0-67.25 + 4.0.0-67.27 + 4.0.0-67.27 4.0.0.67 true 16BC0468-78F6-4C91-87DA-7403C919E646 diff --git a/source/backend/dal.keycloak/Extensions/ServiceCollectionExtensions.cs b/source/backend/dal.keycloak/Extensions/ServiceCollectionExtensions.cs index 0e2342285f..76ce80847c 100644 --- a/source/backend/dal.keycloak/Extensions/ServiceCollectionExtensions.cs +++ b/source/backend/dal.keycloak/Extensions/ServiceCollectionExtensions.cs @@ -16,7 +16,7 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddPimsKeycloakService(this IServiceCollection services) { return services.AddScoped() - .AddScoped(); + .AddScoped(); } } } diff --git a/source/backend/dal.keycloak/PimsKeycloakService.cs b/source/backend/dal.keycloak/PimsKeycloakService.cs index fa6669c39d..fc5381b454 100644 --- a/source/backend/dal.keycloak/PimsKeycloakService.cs +++ b/source/backend/dal.keycloak/PimsKeycloakService.cs @@ -20,7 +20,7 @@ namespace Pims.Dal.Keycloak public class PimsKeycloakService : IPimsKeycloakService { #region Variable - private readonly IKeycloakService _keycloakService; + private readonly IKeycloakRepository _keycloakRepository; private readonly IUserRepository _userRepository; private readonly IRoleRepository _roleRepository; private readonly IAccessRequestRepository _accessRequestRepository; @@ -33,19 +33,19 @@ public class PimsKeycloakService : IPimsKeycloakService /// /// Creates a new instance of a PimsKeycloakService object, initializes with the specified arguments. /// - /// + /// /// /// /// /// public PimsKeycloakService( - IKeycloakService keycloakService, + IKeycloakRepository keycloakRepository, IUserRepository userRepository, IRoleRepository roleRepository, IAccessRequestRepository accessRequestRepository, ClaimsPrincipal user) { - _keycloakService = keycloakService; + _keycloakRepository = keycloakRepository; _userRepository = userRepository; _roleRepository = roleRepository; _accessRequestRepository = accessRequestRepository; @@ -63,7 +63,7 @@ public PimsKeycloakService( /// public async Task UpdateUserAsync(Entity.PimsUser user) { - var kuser = await _keycloakService.GetUserAsync(user.GuidIdentifierValue.Value) ?? throw new KeyNotFoundException("User does not exist in Keycloak"); + var kuser = await _keycloakRepository.GetUserAsync(user.GuidIdentifierValue.Value) ?? throw new KeyNotFoundException("User does not exist in Keycloak"); var euser = _userRepository.GetTrackingById(user.Internal_Id); return await SaveUserChanges(user, euser, kuser, true); @@ -77,7 +77,7 @@ public PimsKeycloakService( /// public async Task AppendToUserAsync(Entity.PimsUser update) { - var kuser = await _keycloakService.GetUserAsync(update.GuidIdentifierValue.Value) ?? throw new KeyNotFoundException("User does not exist in Keycloak"); + var kuser = await _keycloakRepository.GetUserAsync(update.GuidIdentifierValue.Value) ?? throw new KeyNotFoundException("User does not exist in Keycloak"); var euser = _userRepository.GetTrackingById(update.Internal_Id); return await SaveUserChanges(update, euser, kuser, true); @@ -165,13 +165,13 @@ public PimsKeycloakService( var roles = update.IsDisabled.HasValue && update.IsDisabled.Value ? System.Array.Empty() : euser.PimsUserRoles.Select(ur => _roleRepository.Find(ur.RoleId)); // Now update keycloak - var keycloakUserGroups = await _keycloakService.GetUserGroupsAsync(euser.GuidIdentifierValue.Value); + var keycloakUserGroups = await _keycloakRepository.GetUserGroupsAsync(euser.GuidIdentifierValue.Value); var newRolesToAdd = roles.Where(r => keycloakUserGroups.All(crr => crr.Name != r.Name)); var rolesToRemove = keycloakUserGroups.Where(r => roles.All(crr => crr.Name != r.Name)); var addOperations = newRolesToAdd.Select(nr => new UserRoleOperation() { Operation = "add", RoleName = nr.Name, Username = update.GetIdirUsername() }); var removeOperations = rolesToRemove.Select(rr => new UserRoleOperation() { Operation = "del", RoleName = rr.Name, Username = update.GetIdirUsername() }); - await _keycloakService.ModifyUserRoleMappings(addOperations.Concat(removeOperations)); + await _keycloakRepository.ModifyUserRoleMappings(addOperations.Concat(removeOperations)); _userRepository.CommitTransaction(); return _userRepository.GetById(euser.Internal_Id); diff --git a/source/backend/keycloak/Extensions/ServiceCollectionExtensions.cs b/source/backend/keycloak/Extensions/ServiceCollectionExtensions.cs index aec90e7bfb..00bea060b9 100644 --- a/source/backend/keycloak/Extensions/ServiceCollectionExtensions.cs +++ b/source/backend/keycloak/Extensions/ServiceCollectionExtensions.cs @@ -12,9 +12,9 @@ public static class ServiceCollectionExtensions /// /// /// - public static IServiceCollection AddKeycloakService(this IServiceCollection services) + public static IServiceCollection AddKeycloakRepository(this IServiceCollection services) { - return services.AddScoped(); + return services.AddScoped(); } } } diff --git a/source/backend/keycloak/IKeycloakRepository.cs b/source/backend/keycloak/IKeycloakRepository.cs new file mode 100644 index 0000000000..bec4b6fba6 --- /dev/null +++ b/source/backend/keycloak/IKeycloakRepository.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Pims.Keycloak.Models; + +namespace Pims.Keycloak +{ + public interface IKeycloakRepository + { + #region Users + + Task GetUserAsync(Guid id); + + Task> GetUsersAsync(Guid id); + + Task AddRolesToUser(string username, IEnumerable roles); + + Task DeleteRoleFromUsers(string username, string roleName); + + Task GetUserGroupsAsync(Guid id); + + Task ModifyUserRoleMappings(IEnumerable operations); + + Task> GetAllRoles(); + + Task> GetAllGroupRoles(string groupName); + + Task> GetUserRoles(string username); + + Task AddKeycloakRole(RoleModel role); + + Task AddKeycloakRolesToGroup(string groupName, IEnumerable roles); + + Task DeleteRole(string roleName); + + Task DeleteRoleFromGroup(string groupName, string roleName); + #endregion + } +} diff --git a/source/backend/keycloak/IKeycloakService.cs b/source/backend/keycloak/IKeycloakService.cs deleted file mode 100644 index 467897c8a8..0000000000 --- a/source/backend/keycloak/IKeycloakService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Pims.Keycloak.Models; - -namespace Pims.Keycloak -{ - public interface IKeycloakService - { - #region Users - - Task GetUserAsync(Guid id); - - Task GetUserGroupsAsync(Guid id); - - Task ModifyUserRoleMappings(IEnumerable operations); - #endregion - } -} diff --git a/source/backend/keycloak/KeycloakRepository.cs b/source/backend/keycloak/KeycloakRepository.cs new file mode 100644 index 0000000000..10c1177650 --- /dev/null +++ b/source/backend/keycloak/KeycloakRepository.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Pims.Core.Extensions; +using Pims.Core.Http; +using Pims.Keycloak.Extensions; +using Pims.Keycloak.Models; + +namespace Pims.Keycloak +{ + /// + /// KeycloakRepository class, provides a service for sending HTTP requests to the keycloak admin API. + /// - https://www.keycloak.org/docs-api/5.0/rest-api/index.html#_overview. + /// + public partial class KeycloakRepository : IKeycloakRepository + { + #region Variables + private readonly IOpenIdConnectRequestClient _client; + #endregion + + #region Properties + + /// + /// get - The configuration options for keycloak. + /// + public Configuration.KeycloakOptions Options { get; } + #endregion + + #region Constructors + + /// + /// Creates a new instance of a KeycloakAdmin class, initializes it with the specified arguments. + /// + /// + /// + public KeycloakRepository(IOpenIdConnectRequestClient client, IOptions options) + { + this.Options = options.Value; + this.Options.Validate(); + this.Options.ServiceAccount.Validate(); + _client = client; + _client.AuthClientOptions.Audience = this.Options.ServiceAccount.Audience ?? this.Options.Audience; + _client.AuthClientOptions.Authority = this.Options.ServiceAccount.Authority ?? this.Options.Authority; + _client.AuthClientOptions.Client = this.Options.ServiceAccount.Client; + _client.AuthClientOptions.Secret = this.Options.ServiceAccount.Secret; + } + + /// + /// Get the user for the specified 'id'. + /// + /// + /// + public async Task GetUserAsync(Guid id) + { + var users = await GetUsersAsync(id); + return users.FirstOrDefault(); + } + + public async Task> GetUsersAsync(Guid id) + { + var response = await _client.GetAsync($"{this.Options.ServiceAccount.Api}/{this.Options.ServiceAccount.Environment}/idir/users?guid={id.ToString().Replace("-", string.Empty)}"); + var result = await response.HandleResponseAsync>(); + + return result.Data.ToList(); + } + + public async Task AddRolesToUser(string username, IEnumerable roles) + { + return await _client.PostJsonAsync($"{GetIntegrationUrl()}/users/{Uri.EscapeDataString(username)}/roles", roles); + } + + public async Task DeleteRoleFromUsers(string username, string roleName) + { + return await _client.DeleteAsync($"{GetIntegrationUrl()}/users/{Uri.EscapeDataString(username)}/roles/{Uri.EscapeDataString(roleName)}"); + } + + /// + /// Get an array of the groups the user for the specified 'id' is a member of. + /// + /// + /// + public async Task GetUserGroupsAsync(Guid id) + { + var response = await _client.GetAsync($"{GetIntegrationUrl()}/user-role-mappings/?username={id.ToString().Replace("-", string.Empty)}@idir"); + + var userRoleModel = await response.HandleResponseAsync(); + + return userRoleModel.Roles.Where(r => r.Composite.HasValue && r.Composite.Value).ToArray(); + } + + /// + /// Get the total number of groups the user for the specified 'id' is a member of. + /// + /// + /// + public async Task GetUserGroupCountAsync(Guid id) + { + var response = await GetUserGroupsAsync(id); + return response.Length; + } + + /// + /// execute all passed operations. + /// + /// + /// + public async Task ModifyUserRoleMappings(IEnumerable operations) + { + foreach (UserRoleOperation operation in operations) + { + var json = operation.Serialize(); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await _client.PostAsync($"{GetIntegrationUrl()}/user-role-mappings", content); + await response.HandleResponseAsync(); + } + } + + public async Task> GetAllRoles() + { + var response = await _client.GetAsync($"{GetIntegrationUrl()}/roles"); + + var allKeycloakRoles = await response.HandleResponseAsync>(); + return allKeycloakRoles; + } + + public async Task> GetAllGroupRoles(string groupName) + { + var response = await _client.GetAsync($"{GetIntegrationUrl()}/roles/{Uri.EscapeDataString(groupName)}/composite-roles"); + + var groupedRoles = await response.HandleResponseAsync>(); + return groupedRoles; + } + + public async Task> GetUserRoles(string username) + { + var response = await _client.GetAsync($"{GetIntegrationUrl()}/users/{Uri.EscapeDataString(username)}/roles"); + + var groupedRoles = await response.HandleResponseAsync>(); + return groupedRoles; + } + + public async Task AddKeycloakRole(RoleModel role) + { + var response = await _client.PostJsonAsync($"{GetIntegrationUrl()}/roles", role); + return response; + } + + public async Task AddKeycloakRolesToGroup(string groupName, IEnumerable roles) + { + var response = await _client.PostJsonAsync($"{GetIntegrationUrl()}/roles/{Uri.EscapeDataString(groupName)}/composite-roles", roles); + return response; + } + + public async Task DeleteRole(string roleName) + { + var response = await _client.DeleteAsync($"{GetIntegrationUrl()}/roles/{Uri.EscapeDataString(roleName)}"); + return response; + } + + public async Task DeleteRoleFromGroup(string groupName, string roleName) + { + var response = await _client.DeleteAsync($"{GetIntegrationUrl()}/roles/{Uri.EscapeDataString(groupName)}/composite-roles/{Uri.EscapeDataString(roleName)}"); + return response; + } + + private string GetIntegrationUrl() + { + return $"{this.Options.ServiceAccount.Api}/integrations/{this.Options.ServiceAccount.Integration}/{this.Options.ServiceAccount.Environment}"; + } + #endregion + + #region Methods + #endregion + } +} diff --git a/source/backend/keycloak/KeycloakService.cs b/source/backend/keycloak/KeycloakService.cs deleted file mode 100644 index c36c23fcf5..0000000000 --- a/source/backend/keycloak/KeycloakService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.Extensions.Options; -using Pims.Core.Http; - -namespace Pims.Keycloak -{ - /// - /// KeycloakService class, provides a service for sending HTTP requests to the keycloak admin API. - /// - https://www.keycloak.org/docs-api/5.0/rest-api/index.html#_overview. - /// - public partial class KeycloakService : IKeycloakService - { - #region Variables - private readonly IOpenIdConnectRequestClient _client; - #endregion - - #region Properties - - /// - /// get - The configuration options for keycloak. - /// - public Configuration.KeycloakOptions Options { get; } - #endregion - - #region Constructors - - /// - /// Creates a new instance of a KeycloakAdmin class, initializes it with the specified arguments. - /// - /// - /// - public KeycloakService(IOpenIdConnectRequestClient client, IOptions options) - { - this.Options = options.Value; - this.Options.Validate(); - this.Options.ServiceAccount.Validate(); - _client = client; - _client.AuthClientOptions.Audience = this.Options.ServiceAccount.Audience ?? this.Options.Audience; - _client.AuthClientOptions.Authority = this.Options.ServiceAccount.Authority ?? this.Options.Authority; - _client.AuthClientOptions.Client = this.Options.ServiceAccount.Client; - _client.AuthClientOptions.Secret = this.Options.ServiceAccount.Secret; - } - #endregion - - #region Methods - #endregion - } -} diff --git a/source/backend/keycloak/Models/RoleModel.cs b/source/backend/keycloak/Models/RoleModel.cs index 90c710da26..86da4ce803 100644 --- a/source/backend/keycloak/Models/RoleModel.cs +++ b/source/backend/keycloak/Models/RoleModel.cs @@ -15,7 +15,7 @@ public class RoleModel /// /// get/set - whether or not this role is a composite role. /// - public bool Composite { get; set; } + public bool? Composite { get; set; } #endregion } } diff --git a/source/backend/keycloak/Partials/KeycloakServiceUsers.cs b/source/backend/keycloak/Partials/KeycloakServiceUsers.cs deleted file mode 100644 index 2a65acde05..0000000000 --- a/source/backend/keycloak/Partials/KeycloakServiceUsers.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Pims.Core.Extensions; -using Pims.Keycloak.Extensions; -using Pims.Keycloak.Models; - -namespace Pims.Keycloak -{ - /// - /// KeycloakService class, provides a service for sending HTTP requests to the keycloak admin API. - /// - https://api.loginproxy.gov.bc.ca/openapi/swagger#/ . - /// - public partial class KeycloakService : IKeycloakService - { - #region Methods - - /// - /// Get the user for the specified 'id'. - /// - /// - /// - public async Task GetUserAsync(Guid id) - { - var response = await _client.GetAsync($"{this.Options.ServiceAccount.Api}/{this.Options.ServiceAccount.Environment}/idir/users?guid={id.ToString().Replace("-", string.Empty)}"); - var result = await response.HandleResponseAsync>(); - - return result.Data.FirstOrDefault(); - } - - /// - /// Get an array of the groups the user for the specified 'id' is a member of. - /// - /// - /// - public async Task GetUserGroupsAsync(Guid id) - { - var response = await _client.GetAsync($"{this.Options.ServiceAccount.Api}/integrations/{this.Options.ServiceAccount.Integration}/{this.Options.ServiceAccount.Environment}/user-role-mappings/?username={id.ToString().Replace("-", string.Empty)}@idir"); - - var userRoleModel = await response.HandleResponseAsync(); - - return userRoleModel.Roles.Where(r => r.Composite).ToArray(); - } - - /// - /// Get the total number of groups the user for the specified 'id' is a member of. - /// - /// - /// - public async Task GetUserGroupCountAsync(Guid id) - { - var response = await GetUserGroupsAsync(id); - return response.Length; - } - - /// - /// execute all passed operations. - /// - /// - /// - public async Task ModifyUserRoleMappings(IEnumerable operations) - { - foreach (UserRoleOperation operation in operations) - { - var json = operation.Serialize(); - using var content = new StringContent(json, Encoding.UTF8, "application/json"); - var response = await _client.PostAsync($"{this.Options.ServiceAccount.Api}/integrations/{this.Options.ServiceAccount.Integration}/{this.Options.ServiceAccount.Environment}/user-role-mappings", content); - await response.HandleResponseAsync(); - } - } - #endregion - } -} diff --git a/source/backend/tests/unit/dal/Libraries/Keycloak/KeycloakServiceTest.cs b/source/backend/tests/unit/dal/Libraries/Keycloak/KeycloakServiceTest.cs index 51f7a8fd8c..13cfd43163 100644 --- a/source/backend/tests/unit/dal/Libraries/Keycloak/KeycloakServiceTest.cs +++ b/source/backend/tests/unit/dal/Libraries/Keycloak/KeycloakServiceTest.cs @@ -37,7 +37,7 @@ public void CreateKeycloakService_NoAuthority() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:Authority is invalid or missing."); } @@ -60,7 +60,7 @@ public void CreateKeycloakService_NoAudience() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:Audience is invalid or missing."); } @@ -84,7 +84,7 @@ public void CreateKeycloakService_NoClient() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:Client is invalid or missing."); } @@ -109,7 +109,7 @@ public void CreateKeycloakService_NoServiceAccount() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:ServiceAccount is invalid or missing."); } @@ -135,7 +135,7 @@ public void CreateKeycloakService_NoServiceAccountClient() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:ServiceAccount:Client is invalid or missing."); } @@ -164,7 +164,7 @@ public void CreateKeycloakService_NoServiceAccountSecret() // Act // Assert - var result = Assert.Throws(() => helper.Create(options, user)); + var result = Assert.Throws(() => helper.Create(options, user)); result.Message.Should().Be("The configuration for Keycloak:ServiceAccount:Secret is invalid or missing."); } @@ -196,7 +196,7 @@ public void CreateKeycloakService() helper.AddSingleton(openIdConnect.Object); // Act - var service = helper.Create(options, user); + var service = helper.Create(options, user); // Assert openIdConnect.Object.AuthClientOptions.Audience.Should().Be(options.Value.Audience); @@ -235,7 +235,7 @@ public void CreateKeycloakService_ServiceAccount() helper.AddSingleton(openIdConnect.Object); // Act - var service = helper.Create(options, user); + var service = helper.Create(options, user); // Assert openIdConnect.Object.AuthClientOptions.Audience.Should().Be(options.Value.ServiceAccount.Audience); diff --git a/source/backend/tests/unit/dal/Libraries/Keycloak/PimsKeycloakUserServiceTest.cs b/source/backend/tests/unit/dal/Libraries/Keycloak/PimsKeycloakUserServiceTest.cs index 84c5a8dd8f..699664fe5d 100644 --- a/source/backend/tests/unit/dal/Libraries/Keycloak/PimsKeycloakUserServiceTest.cs +++ b/source/backend/tests/unit/dal/Libraries/Keycloak/PimsKeycloakUserServiceTest.cs @@ -41,7 +41,7 @@ public async Task UpdateUserAsync_Success() removeRole.RoleId = 1; removeRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var kuser = new Pims.Keycloak.Models.UserModel() { Username = euser.BusinessIdentifierValue, @@ -112,7 +112,7 @@ public async Task UpdateUserAsync_Success_KeycloakRoleNotInPims() removeRole.RoleId = 1; removeRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { Guid.NewGuid().ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -180,7 +180,7 @@ public async Task UpdateUserAsync_MissingKeycloakUser() var euser = EntityHelper.CreateUser("test"); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); keycloakServiceMock.Setup(m => m.GetUserAsync(It.IsAny())).ReturnsAsync((Pims.Keycloak.Models.UserModel)null); // Act @@ -204,7 +204,7 @@ public async Task UpdateUserAsync_KeycloakUserDoesNotMatch() removeRole.RoleId = 1; removeRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new[] { euser.GuidIdentifierValue.Value.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -254,7 +254,7 @@ public async Task UpdateUserAsync_AddRoleDoesNotExistInPims() var removeRole = euser.GetRoles().First(); removeRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { removeRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -300,7 +300,7 @@ public async Task UpdateUserAsync_RemoveRoleDoesNotExistInPims() removeRole.RoleId = 1; removeRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { removeRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -346,7 +346,7 @@ public async Task AppendToUserAsync_Success_AddRole() existingRole.RoleId = 1; existingRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { existingRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -406,7 +406,7 @@ public async Task AppendToUserAsync_Success_AddContactMethod() existingRole.RoleId = 1; existingRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { existingRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -453,7 +453,7 @@ public async Task AppendToUserAsync_AddRole_KeycloakGroupIdNotFound() existingRole.RoleId = 1; existingRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { existingRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -489,7 +489,7 @@ public async Task AppendToUserAsync_AddRole_KeyNotFound() existingRole.RoleId = 1; existingRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { existingRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -525,7 +525,7 @@ public async Task UpdateAccessRequestAsync_Recieved() var eRole = EntityHelper.CreateRole("test-role"); eRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { eRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -575,7 +575,7 @@ public async Task UpdateAccessRequestAsync_Approved() var eRole = EntityHelper.CreateRole("test-role"); eRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { eRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -628,7 +628,7 @@ public async Task UpdateAccessRequestAsync_Approved_RegionUpdate() var eRole = EntityHelper.CreateRole("test-role"); eRole.KeycloakGroupId = Guid.NewGuid(); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var groups = new string[] { eRole.KeycloakGroupId.ToString() }; var kuser = new Pims.Keycloak.Models.UserModel() { @@ -687,7 +687,7 @@ public async Task UpdateAccessRequestAsync_NotAuthorized() var eAccessRequest = EntityHelper.CreateAccessRequest(1); - var keycloakServiceMock = helper.GetMock(); + var keycloakServiceMock = helper.GetMock(); var updatedAccessRequest = new Entity.PimsAccessRequest() { AccessRequestId = eAccessRequest.AccessRequestId, diff --git a/source/backend/tests/unit/dal/Libraries/Keycloak/ServiceCollectionExtensionsTest.cs b/source/backend/tests/unit/dal/Libraries/Keycloak/ServiceCollectionExtensionsTest.cs index 100160fe95..1c0dab6d6b 100644 --- a/source/backend/tests/unit/dal/Libraries/Keycloak/ServiceCollectionExtensionsTest.cs +++ b/source/backend/tests/unit/dal/Libraries/Keycloak/ServiceCollectionExtensionsTest.cs @@ -51,9 +51,9 @@ public void AddKeycloakService_Success() services.AddScoped((s) => mockOpenIdConnectRequestClient.Object); // Act - var result = services.AddKeycloakService(); + var result = services.AddKeycloakRepository(); var provider = result.BuildServiceProvider(); - var service = provider.GetService(); + var service = provider.GetService(); // Assert result.Should().NotBeNull(); @@ -63,7 +63,7 @@ public void AddKeycloakService_Success() } [Fact] - public void AddPimsKeycloakService_Success() + public void AddPimsKeycloakRepository_Success() { // Arrange var services = new ServiceCollection(); diff --git a/source/frontend/package.json b/source/frontend/package.json index ebb33b8e86..b257711306 100644 --- a/source/frontend/package.json +++ b/source/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "4.0.0-67.25", + "version": "4.0.0-67.27", "private": true, "dependencies": { "@bcgov/bc-sans": "1.0.1", diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx index a085766206..8f5277bcf0 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx @@ -352,10 +352,9 @@ const UpdateCompensationRequisitionForm: React.FCError: , - `You have selected an alternate project that is the same as the file project, please select a different project`, + `You have selected an alternate project that is the same as the file project, please select a different project.`, ]} - okButtonText="Ok" + okButtonText="Close" handleOk={() => { setShowAltProjectError(false); formikRef.current?.setFieldValue('alternateProject', ''); diff --git a/tools/core/Keycloak/Configuration/KeycloakAdminOptions.cs b/tools/core/Keycloak/Configuration/KeycloakAdminOptions.cs deleted file mode 100644 index e89b85dd51..0000000000 --- a/tools/core/Keycloak/Configuration/KeycloakAdminOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Pims.Core.Http.Configuration; - -namespace Pims.Tools.Core.Keycloak.Configuration -{ - /// - /// KeycloakManagementOptions class, provides a way to configure the connection to Keycloak. - /// - public class KeycloakManagementOptions : AuthClientOptions - { - #region Properties - - /// - /// get/set - The keycloak api route. - /// - public string Api { get; set; } - - /// - /// get/set - The api environment. - /// - public string Environment { get; set; } - - /// - /// get/set - The integration id. - /// - public int Integration { get; set; } - #endregion - } -} diff --git a/tools/core/Keycloak/Configuration/KeycloakOptions.cs b/tools/core/Keycloak/Configuration/KeycloakOptions.cs deleted file mode 100644 index 1ac1122424..0000000000 --- a/tools/core/Keycloak/Configuration/KeycloakOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Pims.Core.Http.Configuration; - -namespace Pims.Tools.Core.Keycloak.Configuration -{ - /// - /// KeycloakOptions class, provides a way to configure the connection to Keycloak. - /// - public class KeycloakOptions : AuthClientOptions - { - #region Properties - - /// - /// get/set - The keycloak realm. - /// - public string Realm { get; set; } - #endregion - } -} diff --git a/tools/core/Keycloak/IKeycloakRequestClient.cs b/tools/core/Keycloak/IKeycloakRequestClient.cs deleted file mode 100644 index 05536af63c..0000000000 --- a/tools/core/Keycloak/IKeycloakRequestClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Pims.Tools.Core.Keycloak -{ - /// - /// IRequestClient interface, provides an HTTP client to make requests and handle refresh token. - /// - public interface IKeycloakRequestClient : IRequestClient - { - public string GetIntegrationEnvUri(); - - public string GetIntegrationUri(); - - public string GetEnvUri(); - } -} diff --git a/tools/core/Keycloak/KeycloakRequestClient.cs b/tools/core/Keycloak/KeycloakRequestClient.cs deleted file mode 100644 index 6780af833f..0000000000 --- a/tools/core/Keycloak/KeycloakRequestClient.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Pims.Core.Http.Configuration; -using Pims.Tools.Core.Configuration; -using Pims.Tools.Core.Keycloak.Configuration; - -namespace Pims.Tools.Core.Keycloak -{ - - /// - /// KeycloakRequestClient class, provides a way to make HTTP requests to the keycloak management api provided by sso gold. - /// - public class KeycloakRequestClient : RequestClient, IKeycloakRequestClient - { - #region Variables - private readonly KeycloakManagementOptions _keycloakManagementOptions; - #endregion - - #region Constructors - - /// - /// Creates a new instance of an KeycloakRequestClient class, initializes it with the specified arguments. - /// - /// - /// - /// - /// - /// - /// - /// - public KeycloakRequestClient( - IHttpClientFactory clientFactory, - JwtSecurityTokenHandler tokenHandler, - IOptionsMonitor keycloakManagementOptions, - IOptionsMonitor openIdConnectOptions, - IOptionsMonitor requestOptions, - IOptionsMonitor serializerOptions, - ILogger logger) - : base(clientFactory, tokenHandler, keycloakManagementOptions, openIdConnectOptions, requestOptions, serializerOptions, logger) - { - this.OpenIdConnectOptions.Token = $"{keycloakManagementOptions.CurrentValue.Authority}{this.OpenIdConnectOptions.Token}"; - _keycloakManagementOptions = keycloakManagementOptions.CurrentValue; - } - #endregion - - #region Methods - - // Override the provided url with the base url provided with for the keycloak management api. - public override Task SendAsync(string url, HttpMethod method = null, HttpContent content = null) - { - return base.SendAsync($"{_keycloakManagementOptions.Api}/{url}", method, content); - } - - public override Task SendJsonAsync(string url, HttpMethod method = null, T data = null) - { - return base.SendJsonAsync($"{_keycloakManagementOptions.Api}/{url}", method, data); - } - - // Get the integrations endpoint for a specific environment provided by the appsettings file. - public string GetIntegrationEnvUri() - { - return $"integrations/{_keycloakManagementOptions.Integration}/{_keycloakManagementOptions.Environment}"; - } - - // Get just the integration endpoint provided by the appsettings file. - public string GetIntegrationUri() - { - return $"integrations/{_keycloakManagementOptions.Integration}"; - } - - // Get just the environment from the appsettings file. - public string GetEnvUri() - { - return $"{_keycloakManagementOptions.Environment}"; - } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/ClientModel.cs b/tools/core/Keycloak/Models/ClientModel.cs deleted file mode 100644 index c21eb8cd97..0000000000 --- a/tools/core/Keycloak/Models/ClientModel.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// ClientModel class, provides a model to represent a keycloak client. - /// - public class ClientModel - { - #region Properties - - /// - /// get/set - A unique primary key id. - /// - public Guid? Id { get; set; } - - /// - /// get/set - A unique key identity for the client. - /// - public string ClientId { get; set; } - - /// - /// get/set - A unique name to identify this client. - /// - public string Name { get; set; } - - /// - /// get/set - Client description. - /// - public string Description { get; set; } - - /// - /// get/set - Whether the client is enabled. - /// - public bool Enabled { get; set; } - - /// - /// get/set - The client protocol. - /// - public string Protocol { get; set; } - - /// - /// get/set - Access configuration. - /// - public Dictionary Access { get; set; } - - /// - /// get/set - Authentication flow binding configuration. - /// - public Dictionary AuthenticationFlowBindingOverrides { get; set; } - - // public ResourceServerModel AuthorizationServicesEnabled { get; set; } - - /// - /// get/set - Client authentictor type. - /// - public string ClientAuthenticatorType { get; set; } - - /// - /// get/set - Whether consent is required. - /// - public bool ConsentRequired { get; set; } - - /// - /// get/set - Default client scopes. - /// - public string[] DefaultClientScopes { get; set; } - - /// - /// get/set - Default roles for new users. - /// - public string[] DefaultRoles { get; set; } - - /// - /// get/set - Frontchannel logout. - /// - public bool FrontchannelLogout { get; set; } - - /// - /// get/set - Whether full scope is allowed. - /// - public bool FullScopeAllowed { get; set; } - - /// - /// get/set - Node regregistration timeout. - /// - public int NodeReRegistrationTimeout { get; set; } = -1; - - /// - /// get/set - Not before. - /// - public int NotBefore { get; set; } - - /// - /// get/set - Optional client scopes. - /// - public string[] OptionalClientScopes { get; set; } - - /// - /// get/set - An array of protocol mappers. - /// - public ProtocolMapperModel[] ProtocolMappers { get; set; } - - /// - /// get/set - Whether this is a public client. - /// - public bool PublicClient { get; set; } - - /// - /// get/set - A dictionary of registered nodes. - /// - public Dictionary RegisteredNodes { get; set; } - - /// - /// get/set - Registration access token. - /// - public string RegistrationAccessToken { get; set; } - - /// - /// get/set - Whether surrogate authentication is required. - /// - public bool SurrogateAuthRequired { get; set; } - - /// - /// get/set - Whether authorization services are enabled. - /// - public bool AuthorizationServicesEnabled { get; set; } - - /// - /// get/set - Whether this client is a bearer only. - /// - public bool BearerOnly { get; set; } - - /// - /// get/set - Whether client allows direct access. - /// - public bool DirectAccessGrantsEnabled { get; set; } - - /// - /// get/set - Whether client allows implicit flow. - /// - public bool ImplicitFlowEnabled { get; set; } - - /// - /// get/set - Whether client has a service account. - /// - public bool ServiceAccountsEnabled { get; set; } - - /// - /// get/set - Whether client has standard flow. - /// - public bool StandardFlowEnabled { get; set; } - - /// - /// get/set - Client secret. - /// - public string Secret { get; set; } - - /// - /// get/set - Client base URL. - /// - public string BaseUrl { get; set; } - - /// - /// get/set - Client root URL. - /// - public string RootUrl { get; set; } - - /// - /// get/set - Client redirect URIs. - /// - public string[] RedirectUris { get; set; } - - /// - /// get/set - Client origin. - /// - public string Origin { get; set; } - - /// - /// get/set - Client web origins. - /// - public string[] WebOrigins { get; set; } - - /// - /// get/set - Client admin URL. - /// - public string AdminUrl { get; set; } - - /// - /// get/set - Dictionary of attributes. - /// - public Dictionary Attributes { get; set; } - #endregion - - #region Constructors - - /// - /// Creates a new instance of a ClientModel class. - /// - public ClientModel() - { - } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/GroupModel.cs b/tools/core/Keycloak/Models/GroupModel.cs deleted file mode 100644 index 9e07adcf6b..0000000000 --- a/tools/core/Keycloak/Models/GroupModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// GroupModel class, provides a model to represent a keycloak group. - /// - public class GroupModel - { - #region Properties - - /// - /// get/set - A unique primary key. - /// - public Guid? Id { get; set; } - - /// - /// get/set - A unique name to identify this group. - /// - public string Name { get; set; } - - /// - /// get/set - The full path to the group. - /// - public string Path { get; set; } - - /// - /// get/set - An array of role names associated to this group. - /// - public string[] RealmRoles { get; set; } - - /// - /// get/set - A dictionary of client roles. - /// - public Dictionary ClientRoles { get; set; } - - /// - /// get/set - An array of sub-groups. - /// - public string[] SubGroups { get; set; } - - /// - /// get/set - A dictionary of attributes. - /// - public Dictionary Attributes { get; set; } - #endregion - - #region Constructors - - /// - /// Creates a new instance of a GroupModel class. - /// - public GroupModel() - { - } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/ProtocolMapperModel.cs b/tools/core/Keycloak/Models/ProtocolMapperModel.cs deleted file mode 100644 index 3f856218c9..0000000000 --- a/tools/core/Keycloak/Models/ProtocolMapperModel.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; - -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// ProtocolMapperModel class, provides a model to represent a keycloak protocol mapper. - /// - public class ProtocolMapperModel - { - #region Properties - - /// - /// get/set - A primary key for the protocol mapper. - /// - public string Id { get; set; } - - /// - /// get/set - A unique name to identify the protocol mapper. - /// - public string Name { get; set; } - - /// - /// get/set - A protocol. - /// - public string Protocol { get; set; } - - /// - /// get/set - The protocol mapper. - /// - public string ProtocolMapper { get; set; } - - /// - /// get/set - The protocol mapper configuration. - /// - public Dictionary Config { get; set; } - #endregion - - #region Constructors - - /// - /// Creates a new instance of a ProtocolMapperModel class. - /// - public ProtocolMapperModel() - { - } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/ResponseWrapperModel.cs b/tools/core/Keycloak/Models/ResponseWrapperModel.cs deleted file mode 100644 index d644c95aed..0000000000 --- a/tools/core/Keycloak/Models/ResponseWrapperModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Pims.Tools.Core.Keycloak.Models -{ - public class ResponseWrapperModel - { - #region properties - public T Data { get; set; } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/RoleModel.cs b/tools/core/Keycloak/Models/RoleModel.cs deleted file mode 100644 index 6cc55b2086..0000000000 --- a/tools/core/Keycloak/Models/RoleModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// RoleModel class, provides a model to represent a keycloak role. - /// - public class RoleModel - { - #region Properties - - /// - /// get/set - A unique name to identify the role. - /// - public string Name { get; set; } - - /// - /// get/set - true if this role has sub-roles. - /// - public bool? Composite { get; set; } - #endregion - - #region Constructors - - /// - /// Creates a new instance of a RoleModel class. - /// - public RoleModel() - { - } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/UserModel.cs b/tools/core/Keycloak/Models/UserModel.cs deleted file mode 100644 index 986d85e830..0000000000 --- a/tools/core/Keycloak/Models/UserModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// UserModel class, provides a model to represent a keycloak user. - /// - public class UserModel - { - #region Properties - - /// - /// get/set - The primary key IDENTITY. - /// - public Guid Id { get; set; } - - /// - /// get/set - A unique username to identify the user. - /// - public string Username { get; set; } - - /// - /// get/set - Whether the user is enabled. - /// - public bool Enabled { get; set; } - - /// - /// get/set - I don't know... - /// - public bool Totp { get; set; } - - /// - /// get/set - Whether the user's email has been verified. - /// - public bool EmailVerified { get; set; } - - /// - /// get/set - The user's first name. - /// - public string FirstName { get; set; } - - /// - /// get/set - The user's last name. - /// - public string LastName { get; set; } - - /// - /// get/set - The user's email. - /// - public string Email { get; set; } - - /// - /// get/set - A dictionary of attributes. - /// - public Dictionary Attributes { get; set; } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/UserRoleModel.cs b/tools/core/Keycloak/Models/UserRoleModel.cs deleted file mode 100644 index 3b42c9e498..0000000000 --- a/tools/core/Keycloak/Models/UserRoleModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// UserModel class, provides a model to represent a keycloak user. - /// - public class UserRoleModel - { - #region Properties - public IEnumerable Users { get; set; } - - public IEnumerable Roles { get; set; } - #endregion - } -} diff --git a/tools/core/Keycloak/Models/UserRoleOperation.cs b/tools/core/Keycloak/Models/UserRoleOperation.cs deleted file mode 100644 index f5f17c1ffa..0000000000 --- a/tools/core/Keycloak/Models/UserRoleOperation.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Pims.Tools.Core.Keycloak.Models -{ - /// - /// UserRoleOperation class, provides a model to represent a keycloak user. - /// - public class UserRoleOperation - { - #region Properties - - /// - /// get/set - A unique role name to identify the role in keycloak. - /// - public string RoleName { get; set; } - - /// - /// get/set - A unique username to identify the user in keycloak. - /// - public string Username { get; set; } - - /// - /// get/set - An operation name, either add or remove. - /// - public string Operation { get; set; } - #endregion - } -} diff --git a/tools/core/Pims.Tools.Core.csproj b/tools/core/Pims.Tools.Core.csproj deleted file mode 100644 index 8971335e88..0000000000 --- a/tools/core/Pims.Tools.Core.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - library - net6.0 - 1.0.0.0 - 1.0.0.0 - 561C2601-E1DB-44DE-B772-518A6FEA63D9 - Debug;Release - - - - - - - - - - - - - - - - - - - - diff --git a/tools/core/IRequestClient.cs b/tools/keycloak/sync/Client/IRequestClient.cs similarity index 97% rename from tools/core/IRequestClient.cs rename to tools/keycloak/sync/Client/IRequestClient.cs index 98e958df07..e945efcf68 100644 --- a/tools/core/IRequestClient.cs +++ b/tools/keycloak/sync/Client/IRequestClient.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Pims.Core.Http; -namespace Pims.Tools.Core +namespace Pims.Tools.Keycloak.Sync { /// /// IRequestClient interface, provides an HTTP client to make requests and handle refresh token. diff --git a/tools/keycloak/sync/Client/PimsRequestClient.cs b/tools/keycloak/sync/Client/PimsRequestClient.cs index 99cc5d85d6..2df6c4abb0 100644 --- a/tools/keycloak/sync/Client/PimsRequestClient.cs +++ b/tools/keycloak/sync/Client/PimsRequestClient.cs @@ -5,9 +5,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Pims.Core.Http.Configuration; -using Pims.Tools.Core; -using Pims.Tools.Core.Configuration; -using Pims.Tools.Core.Keycloak.Configuration; +using Pims.Keycloak.Configuration; using Pims.Tools.Keycloak.Sync.Configuration; namespace Pims.Tools.Keycloak.Sync diff --git a/tools/core/RequestClient.cs b/tools/keycloak/sync/Client/RequestClient.cs similarity index 98% rename from tools/core/RequestClient.cs rename to tools/keycloak/sync/Client/RequestClient.cs index 31629bb9d0..31141cbd0a 100644 --- a/tools/core/RequestClient.cs +++ b/tools/keycloak/sync/Client/RequestClient.cs @@ -1,7 +1,6 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Net.Http; -using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -9,11 +8,11 @@ using Microsoft.Extensions.Options; using Pims.Core.Exceptions; using Pims.Core.Http.Configuration; -using Pims.Tools.Core.Configuration; +using Pims.Tools.Keycloak.Sync.Configuration; using Polly; using Polly.Retry; -namespace Pims.Tools.Core +namespace Pims.Tools.Keycloak.Sync { /// /// RequestClient class, provides a way to make HTTP requests, handle errors and handle refresh tokens. diff --git a/tools/keycloak/sync/Configuration/AuthOptions.cs b/tools/keycloak/sync/Configuration/AuthOptions.cs index 71bd16d99b..ab98571f7b 100644 --- a/tools/keycloak/sync/Configuration/AuthOptions.cs +++ b/tools/keycloak/sync/Configuration/AuthOptions.cs @@ -1,5 +1,5 @@ using Pims.Core.Http.Configuration; -using Pims.Tools.Core.Keycloak.Configuration; +using Pims.Keycloak.Configuration; namespace Pims.Tools.Keycloak.Sync.Configuration { diff --git a/tools/core/Configuration/RequestOptions.cs b/tools/keycloak/sync/Configuration/RequestOptions.cs similarity index 94% rename from tools/core/Configuration/RequestOptions.cs rename to tools/keycloak/sync/Configuration/RequestOptions.cs index 4ee47f1df5..badedeacb1 100644 --- a/tools/core/Configuration/RequestOptions.cs +++ b/tools/keycloak/sync/Configuration/RequestOptions.cs @@ -1,4 +1,4 @@ -namespace Pims.Tools.Core.Configuration +namespace Pims.Tools.Keycloak.Sync.Configuration { /// /// RequestOptions class, provides a way to configure requests. diff --git a/tools/keycloak/sync/Configuration/ToolOptions.cs b/tools/keycloak/sync/Configuration/ToolOptions.cs index 6afcc5166d..4e41f14099 100644 --- a/tools/keycloak/sync/Configuration/ToolOptions.cs +++ b/tools/keycloak/sync/Configuration/ToolOptions.cs @@ -1,4 +1,3 @@ -using Pims.Tools.Core.Configuration; using Pims.Tools.Keycloak.Sync.Configuration.Realm; namespace Pims.Tools.Keycloak.Sync.Configuration diff --git a/tools/keycloak/sync/Models/Keycloak/ClientModel.cs b/tools/keycloak/sync/Models/Keycloak/ClientModel.cs index bac2a582fc..78e932ead3 100644 --- a/tools/keycloak/sync/Models/Keycloak/ClientModel.cs +++ b/tools/keycloak/sync/Models/Keycloak/ClientModel.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Pims.Tools.Keycloak.Sync.Configuration.Realm; @@ -6,9 +8,191 @@ namespace Pims.Tools.Keycloak.Sync.Models.Keycloak /// /// ClientModel class, provides a model to represent a keycloak client. /// - public class ClientModel : Core.Keycloak.Models.ClientModel + public class ClientModel { #region Properties + + /// + /// get/set - A unique primary key id. + /// + public Guid? Id { get; set; } + + /// + /// get/set - A unique key identity for the client. + /// + public string ClientId { get; set; } + + /// + /// get/set - A unique name to identify this client. + /// + public string Name { get; set; } + + /// + /// get/set - Client description. + /// + public string Description { get; set; } + + /// + /// get/set - Whether the client is enabled. + /// + public bool Enabled { get; set; } + + /// + /// get/set - The client protocol. + /// + public string Protocol { get; set; } + + /// + /// get/set - Access configuration. + /// + public Dictionary Access { get; set; } + + /// + /// get/set - Authentication flow binding configuration. + /// + public Dictionary AuthenticationFlowBindingOverrides { get; set; } + + // public ResourceServerModel AuthorizationServicesEnabled { get; set; } + + /// + /// get/set - Client authentictor type. + /// + public string ClientAuthenticatorType { get; set; } + + /// + /// get/set - Whether consent is required. + /// + public bool ConsentRequired { get; set; } + + /// + /// get/set - Default client scopes. + /// + public string[] DefaultClientScopes { get; set; } + + /// + /// get/set - Default roles for new users. + /// + public string[] DefaultRoles { get; set; } + + /// + /// get/set - Frontchannel logout. + /// + public bool FrontchannelLogout { get; set; } + + /// + /// get/set - Whether full scope is allowed. + /// + public bool FullScopeAllowed { get; set; } + + /// + /// get/set - Node regregistration timeout. + /// + public int NodeReRegistrationTimeout { get; set; } = -1; + + /// + /// get/set - Not before. + /// + public int NotBefore { get; set; } + + /// + /// get/set - Optional client scopes. + /// + public string[] OptionalClientScopes { get; set; } + + /// + /// get/set - An array of protocol mappers. + /// + public ProtocolMapperModel[] ProtocolMappers { get; set; } + + /// + /// get/set - Whether this is a public client. + /// + public bool PublicClient { get; set; } + + /// + /// get/set - A dictionary of registered nodes. + /// + public Dictionary RegisteredNodes { get; set; } + + /// + /// get/set - Registration access token. + /// + public string RegistrationAccessToken { get; set; } + + /// + /// get/set - Whether surrogate authentication is required. + /// + public bool SurrogateAuthRequired { get; set; } + + /// + /// get/set - Whether authorization services are enabled. + /// + public bool AuthorizationServicesEnabled { get; set; } + + /// + /// get/set - Whether this client is a bearer only. + /// + public bool BearerOnly { get; set; } + + /// + /// get/set - Whether client allows direct access. + /// + public bool DirectAccessGrantsEnabled { get; set; } + + /// + /// get/set - Whether client allows implicit flow. + /// + public bool ImplicitFlowEnabled { get; set; } + + /// + /// get/set - Whether client has a service account. + /// + public bool ServiceAccountsEnabled { get; set; } + + /// + /// get/set - Whether client has standard flow. + /// + public bool StandardFlowEnabled { get; set; } + + /// + /// get/set - Client secret. + /// + public string Secret { get; set; } + + /// + /// get/set - Client base URL. + /// + public string BaseUrl { get; set; } + + /// + /// get/set - Client root URL. + /// + public string RootUrl { get; set; } + + /// + /// get/set - Client redirect URIs. + /// + public string[] RedirectUris { get; set; } + + /// + /// get/set - Client origin. + /// + public string Origin { get; set; } + + /// + /// get/set - Client web origins. + /// + public string[] WebOrigins { get; set; } + + /// + /// get/set - Client admin URL. + /// + public string AdminUrl { get; set; } + + /// + /// get/set - Dictionary of attributes. + /// + public Dictionary Attributes { get; set; } #endregion #region Constructors diff --git a/tools/keycloak/sync/Models/Keycloak/GroupModel.cs b/tools/keycloak/sync/Models/Keycloak/GroupModel.cs index 875b00b773..526d53d448 100644 --- a/tools/keycloak/sync/Models/Keycloak/GroupModel.cs +++ b/tools/keycloak/sync/Models/Keycloak/GroupModel.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Pims.Tools.Keycloak.Sync.Configuration.Realm; @@ -7,9 +8,45 @@ namespace Pims.Tools.Keycloak.Sync.Models.Keycloak /// /// GroupModel class, provides a model to represent a keycloak group. /// - public class GroupModel : Core.Keycloak.Models.GroupModel + public class GroupModel { #region Properties + + /// + /// get/set - A unique primary key. + /// + public Guid? Id { get; set; } + + /// + /// get/set - A unique name to identify this group. + /// + public string Name { get; set; } + + /// + /// get/set - The full path to the group. + /// + public string Path { get; set; } + + /// + /// get/set - An array of role names associated to this group. + /// + public string[] RealmRoles { get; set; } + + /// + /// get/set - A dictionary of client roles. + /// + public Dictionary ClientRoles { get; set; } + + /// + /// get/set - An array of sub-groups. + /// + public string[] SubGroups { get; set; } + + /// + /// get/set - A dictionary of attributes. + /// + public Dictionary Attributes { get; set; } + #endregion #region Constructors diff --git a/tools/keycloak/sync/Models/Keycloak/ProtocolMapperModel.cs b/tools/keycloak/sync/Models/Keycloak/ProtocolMapperModel.cs index 965f4680e4..e35b77c51c 100644 --- a/tools/keycloak/sync/Models/Keycloak/ProtocolMapperModel.cs +++ b/tools/keycloak/sync/Models/Keycloak/ProtocolMapperModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Pims.Tools.Keycloak.Sync.Configuration.Realm; namespace Pims.Tools.Keycloak.Sync.Models.Keycloak @@ -5,9 +6,35 @@ namespace Pims.Tools.Keycloak.Sync.Models.Keycloak /// /// ProtocolMapperModel class, provides a model to represent a keycloak protocol mapper. /// - public class ProtocolMapperModel : Core.Keycloak.Models.ProtocolMapperModel + public class ProtocolMapperModel { #region Properties + + /// + /// get/set - A primary key for the protocol mapper. + /// + public string Id { get; set; } + + /// + /// get/set - A unique name to identify the protocol mapper. + /// + public string Name { get; set; } + + /// + /// get/set - A protocol. + /// + public string Protocol { get; set; } + + /// + /// get/set - The protocol mapper. + /// + public string ProtocolMapper { get; set; } + + /// + /// get/set - The protocol mapper configuration. + /// + public Dictionary Config { get; set; } + #endregion #region Constructors diff --git a/tools/core/Keycloak/Models/RealmModel.cs b/tools/keycloak/sync/Models/Keycloak/RealmModel.cs similarity index 100% rename from tools/core/Keycloak/Models/RealmModel.cs rename to tools/keycloak/sync/Models/Keycloak/RealmModel.cs diff --git a/tools/keycloak/sync/Models/Keycloak/RoleModel.cs b/tools/keycloak/sync/Models/Keycloak/RoleModel.cs deleted file mode 100644 index 0f6c65456d..0000000000 --- a/tools/keycloak/sync/Models/Keycloak/RoleModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Pims.Tools.Keycloak.Sync.Configuration.Realm; - -namespace Pims.Tools.Keycloak.Sync.Models.Keycloak -{ - /// - /// RoleModel class, provides a model to represent a keycloak role. - /// - public class RoleModel : Core.Keycloak.Models.RoleModel - { - #region Properties - #endregion - - #region Constructors - - /// - /// Creates a new instance of a RoleModel class. - /// - public RoleModel() - { - } - - /// - /// Creates a new instance of a RoleModel class, initializes with specified arguments. - /// - /// - public RoleModel(RoleOptions role) - { - Name = role.Name; - } - #endregion - } -} diff --git a/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.csproj b/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.csproj index 6b9a8395d8..c097c5105a 100644 --- a/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.csproj +++ b/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.csproj @@ -36,6 +36,7 @@ + @@ -59,7 +60,6 @@ - diff --git a/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.sln b/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.sln index c599a0a3c1..be746525eb 100644 --- a/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.sln +++ b/tools/keycloak/sync/Pims.Tools.Keycloak.Sync.sln @@ -8,12 +8,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Tools.Keycloak.Sync", {16BC0468-78F6-4C91-87DA-7403C919E646} = {16BC0468-78F6-4C91-87DA-7403C919E646} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Tools.Core", "..\..\core\Pims.Tools.Core.csproj", "{561C2601-E1DB-44DE-B772-518A6FEA63D9}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Core", "..\..\..\source\backend\core\Pims.Core.csproj", "{AC8F04FF-3164-41FB-9EDF-E468B8B77837}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Api", "..\..\..\source\backend\api\Pims.Api.csproj", "{16BC0468-78F6-4C91-87DA-7403C919E646}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pims.Keycloak", "..\..\..\source\backend\keycloak\Pims.Keycloak.csproj", "{970903E9-BC53-436F-BA77-C62349546425}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,10 +24,6 @@ Global {951EEA85-8BCA-4D88-9CA5-0453F447DFC5}.Debug|Any CPU.Build.0 = Debug|Any CPU {951EEA85-8BCA-4D88-9CA5-0453F447DFC5}.Release|Any CPU.ActiveCfg = Release|Any CPU {951EEA85-8BCA-4D88-9CA5-0453F447DFC5}.Release|Any CPU.Build.0 = Release|Any CPU - {561C2601-E1DB-44DE-B772-518A6FEA63D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {561C2601-E1DB-44DE-B772-518A6FEA63D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {561C2601-E1DB-44DE-B772-518A6FEA63D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {561C2601-E1DB-44DE-B772-518A6FEA63D9}.Release|Any CPU.Build.0 = Release|Any CPU {AC8F04FF-3164-41FB-9EDF-E468B8B77837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AC8F04FF-3164-41FB-9EDF-E468B8B77837}.Debug|Any CPU.Build.0 = Debug|Any CPU {AC8F04FF-3164-41FB-9EDF-E468B8B77837}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/tools/keycloak/sync/Program.cs b/tools/keycloak/sync/Program.cs index 81564c8a10..270437286b 100644 --- a/tools/keycloak/sync/Program.cs +++ b/tools/keycloak/sync/Program.cs @@ -8,10 +8,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pims.Core.Exceptions; +using Pims.Core.Http; using Pims.Core.Http.Configuration; -using Pims.Tools.Core.Configuration; -using Pims.Tools.Core.Keycloak; -using Pims.Tools.Core.Keycloak.Configuration; +using Pims.Keycloak; +using Pims.Keycloak.Configuration; using Pims.Tools.Keycloak.Sync.Configuration; using Pims.Tools.Keycloak.Sync.Configuration.Realm; @@ -40,7 +40,7 @@ public static async Task Main(string[] args) .Configure(config.GetSection("Api")) .Configure(config.GetSection("Realm")) .Configure(config.GetSection("Auth:Keycloak")) - .Configure(config.GetSection("Auth:Keycloak:ServiceAccount")) + .Configure(config.GetSection("Auth:Keycloak:ServiceAccount")) .Configure(config.GetSection("Auth:OpenIdConnect")) .Configure(options => { @@ -78,8 +78,9 @@ public static async Task Main(string[] args) }) .AddTransient() .AddTransient() - .AddScoped() + .AddScoped() .AddScoped() + .AddScoped() .AddTransient(); services.AddHttpClient("Pims.Tools.Keycloak.Sync", client => { }); diff --git a/tools/keycloak/sync/ISyncFactory.cs b/tools/keycloak/sync/Syncronizer/ISyncFactory.cs similarity index 100% rename from tools/keycloak/sync/ISyncFactory.cs rename to tools/keycloak/sync/Syncronizer/ISyncFactory.cs diff --git a/tools/keycloak/sync/SyncFactory.cs b/tools/keycloak/sync/Syncronizer/SyncFactory.cs similarity index 77% rename from tools/keycloak/sync/SyncFactory.cs rename to tools/keycloak/sync/Syncronizer/SyncFactory.cs index b23a229452..b91c88e093 100644 --- a/tools/keycloak/sync/SyncFactory.cs +++ b/tools/keycloak/sync/Syncronizer/SyncFactory.cs @@ -8,9 +8,8 @@ using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using Pims.Core.Exceptions; -using Pims.Tools.Core.Keycloak; -using Pims.Tools.Core.Keycloak.Models; -using KModel = Pims.Tools.Core.Keycloak.Models; +using Pims.Keycloak; +using KModel = Pims.Keycloak.Models; using PModel = Pims.Api.Models.Concepts; namespace Pims.Tools.Keycloak.Sync @@ -23,7 +22,7 @@ public class SyncFactory : ISyncFactory { #region Variables private static readonly int MAXPAGES = 20; - private readonly IKeycloakRequestClient _keycloakManagementClient; + private readonly IKeycloakRepository _keycloakRepository; private readonly IPimsRequestClient _pimsClient; private readonly ILogger _logger; #endregion @@ -33,12 +32,12 @@ public class SyncFactory : ISyncFactory /// /// Creates a new instance of an Factory class, initializes it with the specified arguments. /// - /// + /// /// /// - public SyncFactory(IKeycloakRequestClient keycloakClient, IPimsRequestClient pimsClient, ILogger logger) + public SyncFactory(IKeycloakRepository keycloakRepository, IPimsRequestClient pimsClient, ILogger logger) { - _keycloakManagementClient = keycloakClient; + _keycloakRepository = keycloakRepository; _pimsClient = pimsClient; _logger = logger; } @@ -79,7 +78,7 @@ public async Task SyncAsync() /// private async Task ActivateAccountAsync() { - var aRes = await _pimsClient.HandleRequestAsync (HttpMethod.Post, "auth/activate"); + var aRes = await _pimsClient.HandleRequestAsync(HttpMethod.Post, "auth/activate"); if (!aRes.IsSuccessStatusCode) { throw new HttpClientRequestException(aRes); @@ -106,7 +105,7 @@ private async Task SyncClaimsAsync(StringBuilder log, StringBuilder errorLog) } while (claimsPage != null && claimsPage.Items.Any() && page < MAXPAGES); - var allKeycloakRoles = await _keycloakManagementClient.HandleRequestAsync>(HttpMethod.Get, $"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles"); + var allKeycloakRoles = await _keycloakRepository.GetAllRoles(); var keycloakRoles = allKeycloakRoles.Data.Where(x => x.Composite.HasValue && x.Composite.Value == false); var keycloakRolesToDelete = keycloakRoles.Where(r => claims.All(crr => crr.Name != r.Name)); @@ -146,10 +145,10 @@ private async Task AddKeycloakRoleAsync(PModel.ClaimModel claim) // Add the role to keycloak and sync with PIMS. _logger.LogInformation($"Adding keycloak role: {claim.Name}"); - var kresponse = await _keycloakManagementClient.SendJsonAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles", HttpMethod.Post, krole); - if (kresponse.StatusCode != HttpStatusCode.Created) + var response = await _keycloakRepository.AddKeycloakRole(krole); + if (response.StatusCode != HttpStatusCode.Created) { - throw new HttpClientRequestException(kresponse, $"Failed to add the role '{claim.Name}' to keycloak."); + throw new HttpClientRequestException(response, $"Failed to add the role '{claim.Name}' to keycloak."); } _logger.LogInformation($"Keycloak role: {claim.Name} added"); return; @@ -176,7 +175,7 @@ private async Task SyncRolesAsync(StringBuilder log, StringBuilder errorLog) } while (rolesPage != null && rolesPage.Items.Any() && page < MAXPAGES); - var keycloakRoles = await _keycloakManagementClient.HandleRequestAsync>(HttpMethod.Get, $"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles"); + var keycloakRoles = await _keycloakRepository.GetAllRoles(); var keycloakCompositeRoles = keycloakRoles.Data.Where(x => x.Composite.HasValue && x.Composite.Value); var keycloakRolesToDelete = keycloakCompositeRoles.Where(r => roles.All(crr => crr.Name != r.Name)); @@ -187,7 +186,7 @@ private async Task SyncRolesAsync(StringBuilder log, StringBuilder errorLog) private async Task AddRolesFromPims(IEnumerable roles, StringBuilder log, StringBuilder errorLog) { - var keycloakRoles = await _keycloakManagementClient.HandleRequestAsync>(HttpMethod.Get, $"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles"); + var keycloakRoles = await _keycloakRepository.GetAllRoles(); foreach (var role in roles) { try @@ -197,7 +196,7 @@ private async Task AddRolesFromPims(IEnumerable roles, StringB { continue; } - + _logger.LogInformation($"Adding/updating keycloak composite role '{prole.Name}'"); var matchingKeycloakGroup = keycloakRoles.Data.FirstOrDefault(r => r.Name == prole.Name); if (matchingKeycloakGroup != null) @@ -224,7 +223,7 @@ private async Task RemoveRolesFromPims(IEnumerable roles, Stri try { _logger.LogInformation($"Deleting keycloak role '{role.Name}'"); - await _keycloakManagementClient.DeleteAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles/{Uri.EscapeDataString(role.Name)}"); + await _keycloakRepository.DeleteRole(role.Name); LogInfo(log, $"Deleted keycloak role '{role.Name}'"); } catch (HttpClientRequestException ex) @@ -243,16 +242,16 @@ private async Task RemoveRolesFromPims(IEnumerable roles, Stri /// private async Task AddGroupToKeycloak(PModel.RoleModel role) { - var addGroup = new KModel.RoleModel() + var krole = new KModel.RoleModel() { Name = role.Name, }; // Add the group to keycloak and sync with PIMS. - var response = await _keycloakManagementClient.SendJsonAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles", HttpMethod.Post, addGroup); + var response = await _keycloakRepository.AddKeycloakRole(krole); if (response.StatusCode == HttpStatusCode.Created) { - await AddRolesToGroupInKeycloak(addGroup, role); + await AddRolesToGroupInKeycloak(krole, role); } else { @@ -285,12 +284,12 @@ private async Task AddRolesToGroupInKeycloak(KModel.RoleModel group, PModel.Role Name = c.Claim.Name, }).ToArray(); - var allKeycloakGroupRolesResponse = await _keycloakManagementClient.HandleGetAsync>>($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles/{Uri.EscapeDataString(group.Name)}/composite-roles"); + var allKeycloakGroupRolesResponse = await _keycloakRepository.GetAllGroupRoles(group.Name); var newRolesToAdd = allPimsGroupRoles.Where(r => allKeycloakGroupRolesResponse.Data.All(crr => crr.Name != r.Name)); if (newRolesToAdd.Any()) { _logger.LogInformation($"Adding the following roles to the composite role {role.Name}: {string.Join(',', newRolesToAdd.Select(r => r.Name))}"); - var response = await _keycloakManagementClient.SendJsonAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles/{Uri.EscapeDataString(group.Name)}/composite-roles", HttpMethod.Post, newRolesToAdd); + var response = await _keycloakRepository.AddKeycloakRolesToGroup(group.Name, newRolesToAdd); _logger.LogInformation($"Added the following roles to the composite role {role.Name}: {string.Join(',', newRolesToAdd.Select(r => r.Name))}"); if (!response.IsSuccessStatusCode) { @@ -312,14 +311,14 @@ private async Task RemoveRolesFromGroupInKeycloak(KModel.RoleModel group, PModel Name = c.Claim.Name, }).ToArray(); - var allKeycloakGroupRolesResponse = await _keycloakManagementClient.HandleGetAsync>>($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles/{Uri.EscapeDataString(group.Name)}/composite-roles"); + var allKeycloakGroupRolesResponse = await _keycloakRepository.GetAllGroupRoles(group.Name); var rolesToRemove = allKeycloakGroupRolesResponse.Data.Where(r => allPimsGroupRoles.All(crr => crr.Name != r.Name)); foreach (var roleToRemove in rolesToRemove) { _logger.LogInformation($"Deleting the following roles to the composite role {role.Name}: {roleToRemove}"); // Update the group in keycloak. - var response = await _keycloakManagementClient.DeleteAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/roles/{Uri.EscapeDataString(group.Name)}/composite-roles/{Uri.EscapeDataString(roleToRemove.Name)}"); + var response = await _keycloakRepository.DeleteRoleFromGroup(group.Name, roleToRemove.Name); _logger.LogInformation($"Deleting the following roles to the composite role {role.Name}: {roleToRemove}"); if (!response.IsSuccessStatusCode) { @@ -383,32 +382,33 @@ private async Task SyncUsersAsync(StringBuilder log, StringBuilder errorLog) private async Task SyncUserRoles(PModel.UserModel user, StringBuilder log) { var username = user.GuidIdentifierValue.ToString().Replace("-", string.Empty) + "@idir"; - var kUserRoles = (await _keycloakManagementClient.HandleGetAsync>>( - $"{_keycloakManagementClient.GetIntegrationEnvUri()}/users/{Uri.EscapeDataString(username)}/roles", - (response) => response.StatusCode == HttpStatusCode.NotFound)).Data; + var response = await _keycloakRepository.GetUserRoles(username); + var kUserRoles = response.Data; - IEnumerable userRolesToAdd = user.UserRoles.Where(ur => kUserRoles.All(kr => kr.Name != ur.Role.Name && ur.Role.IsDisabled == false)).Select(ur => new RoleModel() { Name = ur.Role.Name }); - IEnumerable userRolesToRemove = kUserRoles.Where(kur => user.UserRoles.All(ur => ur.Role.Name != kur.Name)).Select(ur => new RoleModel() { Name = ur.Name }); + IEnumerable userRolesToAdd = user.UserRoles.Where(ur => kUserRoles.All(kr => kr.Name != ur.Role.Name && ur.Role.IsDisabled == false)) + .Select(ur => new KModel.RoleModel() { Name = ur.Role.Name }); + IEnumerable userRolesToRemove = kUserRoles.Where(kur => user.UserRoles.All(ur => ur.Role.Name != kur.Name)) + .Select(ur => new KModel.RoleModel() { Name = ur.Name }); if (userRolesToAdd.Any()) { _logger.LogInformation($"Executing operation 'add' on roles '{string.Join(',', userRolesToAdd.Select(r => r.Name))}' to user '{username}'"); - var response = await _keycloakManagementClient.SendJsonAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/users/{Uri.EscapeDataString(username)}/roles", HttpMethod.Post, userRolesToAdd); - if (!response.IsSuccessStatusCode) + var addResponse = await _keycloakRepository.AddRolesToUser(username, userRolesToAdd); + if (!addResponse.IsSuccessStatusCode) { - throw new HttpClientRequestException(response, $"Failed to update the user role mappings for '{username}' during operation 'add' on roles '{string.Join(',', userRolesToAdd.Select(r => r.Name))}'"); + throw new HttpClientRequestException(addResponse, $"Failed to update the user role mappings for '{username}' during operation 'add' on roles '{string.Join(',', userRolesToAdd.Select(r => r.Name))}'"); } LogInfo(log, $"Executed operation 'add' on roles '{string.Join(',', userRolesToAdd.Select(r => r.Name))}' to user '{username}'"); } - foreach (RoleModel userRoleToRemove in userRolesToRemove) + foreach (var userRoleToRemove in userRolesToRemove) { _logger.LogInformation($"Executing operation 'delete' on role '{userRoleToRemove.Name}' to user '{username}'"); - var response = await _keycloakManagementClient.SendAsync($"{_keycloakManagementClient.GetIntegrationEnvUri()}/users/{Uri.EscapeDataString(username)}/roles/{Uri.EscapeDataString(userRoleToRemove.Name)}", HttpMethod.Delete); - if (!response.IsSuccessStatusCode) + var deleteResponse = await _keycloakRepository.DeleteRoleFromUsers(username, userRoleToRemove.Name); + if (!deleteResponse.IsSuccessStatusCode) { - throw new HttpClientRequestException(response, $"Failed to update the user role mappings for '{username}' during operation 'delete' on role '{userRoleToRemove.Name}'"); + throw new HttpClientRequestException(deleteResponse, $"Failed to update the user role mappings for '{username}' during operation 'delete' on role '{userRoleToRemove.Name}'"); } LogInfo(log, $"Executed operation 'delete' on role '{userRoleToRemove.Name}' to user '{username}'"); } @@ -425,12 +425,12 @@ private async Task SyncUserRoles(PModel.UserModel user, StringBuilder log) try { // Make a request to keycloak to find a matching user. - var response = await _keycloakManagementClient.HandleRequestAsync>>(HttpMethod.Get, $"{_keycloakManagementClient.GetEnvUri()}/idir/users?guid={user.GuidIdentifierValue.ToString().Replace("-", string.Empty)}"); - if (response.Data.Count() > 1) + var users = await _keycloakRepository.GetUsersAsync(user.GuidIdentifierValue); + if (users.Count() > 1) { throw new HttpClientRequestException($"Found multiple users in keycloak for GUID: user: {user.GuidIdentifierValue.ToString()}"); } - return response.Data.FirstOrDefault(); + return users.FirstOrDefault(); } catch (HttpClientRequestException ex) {