From 81415e4c4f67c4e26bd5c82ec93f20cd62d302d1 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Fri, 22 Nov 2024 13:44:05 +0100 Subject: [PATCH 01/21] feat(seeding): make seeder configureable Refs: #1172 --- .../AuthenticationFlowsUpdater.cs | 168 +++++++++--------- .../BusinessLogic/ClientScopeMapperUpdater.cs | 37 ++-- .../BusinessLogic/ClientScopesUpdater.cs | 75 ++++---- .../BusinessLogic/ClientsUpdater.cs | 73 +++++--- .../BusinessLogic/ISeedDataHandler.cs | 1 + .../BusinessLogic/IdentityProvidersUpdater.cs | 56 +++--- .../BusinessLogic/KeecloakSeeder.cs | 34 ++-- .../BusinessLogic/LocalizationsUpdater.cs | 40 +++-- .../BusinessLogic/RolesUpdater.cs | 67 +++---- .../BusinessLogic/SeedDataHandler.cs | 8 +- .../BusinessLogic/UserProfileUpdater.cs | 7 + .../BusinessLogic/UsersUpdater.cs | 55 +++--- .../SeederConfigurationExtensions.cs | 68 +++++++ .../Models/ConfigurationKeys.cs | 23 +++ .../Models/KeycloakRealmSettings.cs | 1 + .../Models/ModificationType.cs | 27 +++ .../Models/SeederConfiguration.cs | 33 ++++ .../Keycloak.Seeding/appsettings.json | 122 ++++++++++++- 18 files changed, 616 insertions(+), 279 deletions(-) create mode 100644 src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs create mode 100644 src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs create mode 100644 src/keycloak/Keycloak.Seeding/Models/ModificationType.cs create mode 100644 src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs index af6f2d65f4..74c38c4daa 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -23,66 +23,53 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.AuthenticationManagement; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Collections.Immutable; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class AuthenticationFlowsUpdater : IAuthenticationFlowsUpdater +public class AuthenticationFlowsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IAuthenticationFlowsUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public AuthenticationFlowsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public Task UpdateAuthenticationFlows(string keycloakInstanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var handler = new AuthenticationFlowHandler(keycloak, _seedData); + var handler = new AuthenticationFlowHandler(keycloak, seedDataHandler); return handler.UpdateAuthenticationFlows(cancellationToken); } - private sealed class AuthenticationFlowHandler + private sealed class AuthenticationFlowHandler(KeycloakClient keycloak, ISeedDataHandler seedDataHandler) { - private readonly string _realm; - private readonly KeycloakClient _keycloak; - private readonly ISeedDataHandler _seedData; - - public AuthenticationFlowHandler(KeycloakClient keycloak, ISeedDataHandler seedData) - { - _keycloak = keycloak; - _seedData = seedData; - _realm = seedData.Realm; - } + private readonly string _realm = seedDataHandler.Realm; public async Task UpdateAuthenticationFlows(CancellationToken cancellationToken) { - var flows = await _keycloak.GetAuthenticationFlowsAsync(_realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var seedFlows = _seedData.TopLevelCustomAuthenticationFlows; + var flows = await keycloak.GetAuthenticationFlowsAsync(_realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var seedFlows = seedDataHandler.TopLevelCustomAuthenticationFlows; var topLevelCustomFlows = flows.Where(flow => !(flow.BuiltIn ?? false) && (flow.TopLevel ?? false)); + var seederConfig = seedDataHandler.Configuration; - await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var delete in topLevelCustomFlows.ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias)) + foreach (var delete in topLevelCustomFlows.ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Delete, x.Alias))) + { if (delete.Id == null) throw new ConflictException($"authenticationFlow.id is null {delete.Alias} {delete.Description}"); - await _keycloak.DeleteAuthenticationFlowAsync(_realm, delete.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await keycloak.DeleteAuthenticationFlowAsync(_realm, delete.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task AddMissingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + private async Task AddMissingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var addFlow in seedFlows.ExceptBy(topLevelCustomFlows.Select(x => x.Alias), x => x.Alias)) { @@ -90,19 +77,24 @@ private async Task AddMissingAuthenticationFlows(IEnumerable throw new ConflictException($"authenticationFlow.Alias is null {addFlow.Id} {addFlow.Description}"); if (addFlow.BuiltIn ?? false) throw new ConflictException($"authenticationFlow.buildIn is true. flow cannot be added: {addFlow.Alias}"); - await _keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateAuthenticationFlowExecutions(addFlow.Alias, cancellationToken); + if (seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Create, addFlow.Id)) + { + await keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } + + await UpdateAuthenticationFlowExecutions(addFlow.Alias, seederConfig, cancellationToken); } } - private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var (flow, seed) in topLevelCustomFlows .Join( seedFlows, x => x.Alias, x => x.Alias, - (flow, seed) => (Flow: flow, Seed: seed))) + (flow, seed) => (Flow: flow, Seed: seed)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Update, x.Flow.Id))) { if (flow.Id == null) throw new ConflictException($"authenticationFlow.id is null {flow.Alias} {flow.Description}"); @@ -110,13 +102,14 @@ private async Task UpdateExistingAuthenticationFlows(IEnumerable new AuthenticationFlow + private static AuthenticationFlow CreateUpdateAuthenticationFlow(string? id, AuthenticationFlowModel update) => new() { Id = id, Alias = update.Alias, @@ -132,18 +125,19 @@ private static bool CompareAuthenticationFlow(AuthenticationFlow flow, Authentic flow.ProviderId == update.ProviderId && flow.TopLevel == update.TopLevel; - private async Task UpdateAuthenticationFlowExecutions(string alias, CancellationToken cancellationToken) + private async Task UpdateAuthenticationFlowExecutions(string alias, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - var updateExecutions = _seedData.GetAuthenticationExecutions(alias); + var updateExecutions = seedDataHandler.GetAuthenticationExecutions(alias); var executionNodes = ExecutionNode.Parse(await GetExecutions(alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)); if (!CompareStructureRecursive(executionNodes, updateExecutions)) { - await DeleteExecutionsRecursive(executionNodes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await AddExecutionsRecursive(alias, updateExecutions, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteExecutionsRecursive(executionNodes, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await AddExecutionsRecursive(alias, updateExecutions, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); executionNodes = ExecutionNode.Parse(await GetExecutions(alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)); } - await UpdateExecutionsRecursive(alias, executionNodes, updateExecutions, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + await UpdateExecutionsRecursive(alias, executionNodes, updateExecutions, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } private bool CompareStructureRecursive(IReadOnlyList executions, IEnumerable updateExecutions) => @@ -153,44 +147,45 @@ private bool CompareStructureRecursive(IReadOnlyList executions, (execution, update) => (Node: execution, Update: update)).All( x => (x.Node.Execution.AuthenticationFlow ?? false) == (x.Update.AuthenticatorFlow ?? false) && - (!(x.Node.Execution.AuthenticationFlow ?? false) || CompareStructureRecursive(x.Node.Children, _seedData.GetAuthenticationExecutions(x.Update.FlowAlias)))); + (!(x.Node.Execution.AuthenticationFlow ?? false) || CompareStructureRecursive(x.Node.Children, seedDataHandler.GetAuthenticationExecutions(x.Update.FlowAlias)))); - private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, CancellationToken cancellationToken) + private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var executionNode in executionNodes) + foreach (var executionNode in executionNodes.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Delete, x.Execution.Id))) { if (executionNode.Execution.AuthenticationFlow ?? false) { - await DeleteExecutionsRecursive(executionNode.Children, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteExecutionsRecursive(executionNode.Children, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await _keycloak.DeleteAuthenticationExecutionAsync(_realm, executionNode.Execution.Id ?? throw new ConflictException("authenticationFlow.Id is null"), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + await keycloak.DeleteAuthenticationExecutionAsync(_realm, executionNode.Execution.Id ?? throw new ConflictException("authenticationFlow.Id is null"), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, CancellationToken cancellationToken) + private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var execution in seedExecutions) + foreach (var execution in seedExecutions.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Delete, x.FlowAlias))) { await (execution.AuthenticatorFlow switch { true => AddAuthenticationFlowExecutionRecursive(alias!, execution, cancellationToken), - _ => _keycloak.AddAuthenticationFlowExecutionAsync(_realm, alias!, CreateDataWithProvider(execution), cancellationToken) + _ => keycloak.AddAuthenticationFlowExecutionAsync(_realm, alias!, CreateDataWithProvider(execution), cancellationToken) }).ConfigureAwait(ConfigureAwaitOptions.None); } - async Task AddAuthenticationFlowExecutionRecursive(string alias, AuthenticationExecutionModel execution, CancellationToken cancellationToken) + async Task AddAuthenticationFlowExecutionRecursive(string updateAlias, AuthenticationExecutionModel execution, CancellationToken ct) { - await _keycloak.AddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(_realm, alias, CreateDataWithAliasTypeProviderDescription(execution), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await AddExecutionsRecursive(execution.FlowAlias, _seedData.GetAuthenticationExecutions(execution.FlowAlias), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await keycloak.AddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(_realm, updateAlias, CreateDataWithAliasTypeProviderDescription(execution), ct).ConfigureAwait(ConfigureAwaitOptions.None); + await AddExecutionsRecursive(execution.FlowAlias, seedDataHandler.GetAuthenticationExecutions(execution.FlowAlias), seederConfig, ct).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task UpdateExecutionsRecursive(string alias, IReadOnlyList executionNodes, IEnumerable seedExecutions, CancellationToken cancellationToken) + private async Task UpdateExecutionsRecursive(string alias, IReadOnlyCollection executionNodes, IEnumerable seedExecutions, SeederConfiguration seederConfig, CancellationToken cancellationToken) { if (executionNodes.Count != seedExecutions.Count()) throw new ArgumentException("number of elements in executionNodes doesn't match seedData"); - foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions)) + foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Update, x.First.Execution.Id))) { if ((executionNode.Execution.AuthenticationFlow ?? false) != (update.AuthenticatorFlow ?? false)) throw new ArgumentException("execution.AuthenticatorFlow doesn't match seedData"); @@ -202,19 +197,19 @@ private async Task UpdateExecutionsRecursive(string alias, IReadOnlyList - execution.Description == _seedData.GetAuthenticationFlow(update.FlowAlias).Description && + execution.Description == seedDataHandler.GetAuthenticationFlow(update.FlowAlias).Description && execution.DisplayName == update.FlowAlias && execution.Requirement == update.Requirement; @@ -317,8 +313,8 @@ private bool CompareFlowExecutions(AuthenticationFlowExecution execution, Authen private async Task<(bool, AuthenticatorConfig?)> CompareAuthenticationConfig(string authenticatorConfigId, string authenticatorConfigAlias, CancellationToken cancellationToken) { - var config = await _keycloak.GetAuthenticatorConfigurationAsync(_realm, authenticatorConfigId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var update = _seedData.GetAuthenticatorConfig(authenticatorConfigAlias); + var config = await keycloak.GetAuthenticatorConfigurationAsync(_realm, authenticatorConfigId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var update = seedDataHandler.GetAuthenticatorConfig(authenticatorConfigAlias); return (CompareAuthenticatorConfig(config, update), config); } @@ -327,11 +323,11 @@ private static bool CompareAuthenticatorConfig(AuthenticatorConfig config, Authe config.Config.NullOrContentEqual(update.Config?.FilterNotNullValues()); private Task> GetExecutions(string alias, CancellationToken cancellationToken) => - _keycloak.GetAuthenticationFlowExecutionsAsync(_realm, alias, cancellationToken); + keycloak.GetAuthenticationFlowExecutionsAsync(_realm, alias, cancellationToken); private IDictionary CreateDataWithAliasTypeProviderDescription(AuthenticationExecutionModel execution) { - var seedFlow = _seedData.GetAuthenticationFlow(execution.FlowAlias); + var seedFlow = seedDataHandler.GetAuthenticationFlow(execution.FlowAlias); return new Dictionary { { "alias", execution.FlowAlias ?? throw new ConflictException($"authenticationExecution.FlowAlias is null: {seedFlow.Alias}")}, { "description", seedFlow.Description ?? throw new ConflictException($"authenticationFlow.ProviderId is null: {seedFlow.Alias}")}, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs index bb74869231..a985e2bb9f 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs @@ -22,27 +22,22 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class ClientScopeMapperUpdater : IClientScopeMapperUpdater +public class ClientScopeMapperUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IClientScopeMapperUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public ClientScopeMapperUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public async Task UpdateClientScopeMapper(string instanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(instanceName); - var realm = _seedData.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); + var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; var clients = await keycloak.GetClientsAsync(realm, null, true, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var (clientName, mappingModels) in _seedData.ClientScopeMappings) + foreach (var (clientName, mappingModels) in seedDataHandler.ClientScopeMappings) { var client = clients.SingleOrDefault(x => x.ClientId == clientName); if (client?.Id is null) @@ -60,17 +55,21 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken } var clientRoles = await keycloak.GetClientRolesScopeMappingsForClientAsync(realm, clientScope.Id, client.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var mappingModelRoles = mappingModel.Roles?.Select(roleName => roles.SingleOrDefault(r => r.Name == roleName) ?? throw new ConflictException($"No role with name {roleName} found")) ?? Enumerable.Empty(); - await AddAndDeleteRoles(keycloak, realm, clientScope.Id, client.Id, clientRoles, mappingModelRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await AddAndDeleteRoles(keycloak, realm, clientScope.Id, client.Id, clientRoles, mappingModelRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } } - private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, CancellationToken cancellationToken) + private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - await updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name).IfAnyAwait(rolesToAdd => - keycloak.AddClientRolesScopeMappingToClientAsync(realm, clientScopeId, clientId, rolesToAdd, cancellationToken)).ConfigureAwait(false); + await updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, x.Name)) + .IfAnyAwait(rolesToAdd => + keycloak.AddClientRolesScopeMappingToClientAsync(realm, clientScopeId, clientId, rolesToAdd, cancellationToken)).ConfigureAwait(false); - await roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name).IfAnyAwait(rolesToDelete => - keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false); + await roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x.Name)) + .IfAnyAwait(rolesToDelete => + keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index 6419a4f844..3c3394643c 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -22,37 +22,42 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ClientScopes; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMappers; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class ClientScopesUpdater : IClientScopesUpdater +public class ClientScopesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IClientScopesUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public ClientScopesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public async Task UpdateClientScopes(string instanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(instanceName); - var realm = _seedData.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); + var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var seedClientScopes = _seedData.ClientScopes; + var seedClientScopes = seedDataHandler.ClientScopes; + + await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + } + + private static async Task CheckAndExecute(string configKey, ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken, Func, IEnumerable, SeederConfiguration, CancellationToken, Task> executeLogic) + { + if (!seederConfig.ModificationAllowed(configKey, modificationType)) + { + return; + } - await RemoveObsoleteClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name)) + foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x.Name))) { await keycloak.DeleteClientScopeAsync( realm, @@ -61,28 +66,30 @@ await keycloak.DeleteClientScopeAsync( } } - private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name)) + foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, x.Name))) { await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var (clientScope, update) in clientScopes .Join( seedClientScopes, x => x.Name, x => x.Name, - (clientScope, update) => (ClientScope: clientScope, Update: update))) + (clientScope, update) => (ClientScope: clientScope, Update: update)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x.Update.Name))) { - await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, CancellationToken cancellationToken) + private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, SeederConfiguration seederConfig, CancellationToken cancellationToken) { if (clientScope.Id == null) throw new ConflictException($"clientScope.Id is null: {clientScope.Name}"); @@ -99,14 +106,15 @@ await keycloak.UpdateClientScopeAsync( var mappers = clientScope.ProtocolMappers ?? Enumerable.Empty(); var updateMappers = update.ProtocolMappers ?? Enumerable.Empty(); - await DeleteObsoleteProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var mapper in mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name)) + foreach (var mapper in mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Delete, x.Name))) { await keycloak.DeleteProtocolMapperAsync( realm, @@ -116,9 +124,10 @@ await keycloak.DeleteProtocolMapperAsync( } } - private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var update in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name)) + foreach (var update in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Create, x.Name))) { await keycloak.CreateProtocolMapperAsync( realm, @@ -128,14 +137,14 @@ await keycloak.CreateProtocolMapperAsync( } } - private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers.Join( updateMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update))) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Update, x.Update.Name))) { await keycloak.UpdateProtocolMapperAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs index 92582fd248..f3538f9a0d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -24,35 +24,30 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Clients; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMappers; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Runtime.CompilerServices; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class ClientsUpdater : IClientsUpdater +public class ClientsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IClientsUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public ClientsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public Task UpdateClients(string keycloakInstanceName, CancellationToken cancellationToken) { - var realm = _seedData.Realm; - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - return _seedData.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, cancellationToken)); + var realm = seedDataHandler.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var seederConfig = seedDataHandler.Configuration; + + return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); } - private async IAsyncEnumerable<(string ClientId, string Id)> UpdateClientsInternal(KeycloakClient keycloak, string realm, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, SeederConfiguration seederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) { var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); string GetClientScopeId(string scope) => clientScopes.SingleOrDefault(x => x.Name == scope)?.Id ?? throw new ConflictException($"id of clientScope {scope} is undefined"); - foreach (var update in _seedData.Clients) + foreach (var update in seedDataHandler.Clients) { if (update.ClientId == null) throw new ConflictException($"clientId must not be null {update.Id}"); @@ -60,24 +55,33 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell var client = (await keycloak.GetClientsAsync(realm, clientId: update.ClientId, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.ClientId == update.ClientId); if (client == null) { + if (!seederConfig.ModificationAllowed(ConfigurationKeys.ClientsConfigKey, ModificationType.Create, update.Id)) + { + yield return null; + } + client = await CreateClient(keycloak, realm, update, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } else { - await UpdateClient( + if (seederConfig.ModificationAllowed(ConfigurationKeys.ClientsConfigKey, ModificationType.Update, update.Id)) + { + await UpdateClient( keycloak, realm, client.Id ?? throw new ConflictException($"client.Id must not be null: clientId {update.ClientId}"), client, update, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } await UpdateClientProtocolMappers( keycloak, realm, - client.Id, + client.Id ?? throw new ConflictException($"client.Id must not be null: clientId {update.ClientId}"), client, update, + seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -88,6 +92,7 @@ await UpdateDefaultClientScopes( client, update, GetClientScopeId, + seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await UpdateOptionalClientScopes( @@ -97,6 +102,7 @@ await UpdateOptionalClientScopes( client, update, GetClientScopeId, + seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); yield return (update.ClientId, client.Id); @@ -110,6 +116,7 @@ private static async Task CreateClient(KeycloakClient keycloak, string r { throw new ConflictException($"PartialImport failed to add client id: {update.Id}, clientId: {update.ClientId}"); } + var client = (await keycloak.GetClientsAsync(realm, clientId: update.ClientId, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.ClientId == update.ClientId); return client ?? throw new ConflictException($"failed to read newly created client {update.ClientId}"); } @@ -127,17 +134,21 @@ await keycloak.UpdateClientAsync( } } - private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, CancellationToken cancellationToken) + private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, SeederConfiguration seederConfig, CancellationToken cancellationToken) { var clientProtocolMappers = client.ProtocolMappers ?? Enumerable.Empty(); var updateProtocolMappers = update.ProtocolMappers ?? Enumerable.Empty(); - foreach (var mapperId in clientProtocolMappers.ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name).Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}"))) + foreach (var mapperId in clientProtocolMappers.ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name) + .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}")) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Delete, x))) { await keycloak.DeleteClientProtocolMapperAsync(realm, clientId, mapperId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var mapper in updateProtocolMappers.ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name).Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x))) + foreach (var mapper in updateProtocolMappers.ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name) + .Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Create, x.Id))) { await keycloak.CreateClientProtocolMapperAsync(realm, clientId, mapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -148,7 +159,7 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Update, x.Update.Id)) .Select(x => ( x.Mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Mapper.Name}"), ProtocolMappersUpdater.CreateProtocolMapper(x.Mapper.Id, x.Update)))) @@ -157,33 +168,41 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s } } - private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, CancellationToken cancellationToken) + private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, SeederConfiguration seederConfig, CancellationToken cancellationToken) { var optionalScopes = client.OptionalClientScopes ?? Enumerable.Empty(); var updateScopes = update.OptionalClientScopes ?? Enumerable.Empty(); - foreach (var scopeId in optionalScopes.Except(updateScopes).Select(getClientScopeId)) + foreach (var scopeId in optionalScopes.Except(updateScopes) + .Select(getClientScopeId) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x))) { await keycloak.DeleteOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var scopeId in updateScopes.Except(optionalScopes).Select(getClientScopeId)) + foreach (var scopeId in updateScopes.Except(optionalScopes) + .Select(getClientScopeId) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x))) { await keycloak.UpdateOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, CancellationToken cancellationToken) + private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, SeederConfiguration seederConfig, CancellationToken cancellationToken) { var defaultScopes = client.DefaultClientScopes ?? Enumerable.Empty(); var updateScopes = update.DefaultClientScopes ?? Enumerable.Empty(); - foreach (var scopeId in defaultScopes.Except(updateScopes).Select(getClientScopeId)) + foreach (var scopeId in defaultScopes.Except(updateScopes) + .Select(getClientScopeId) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x))) { await keycloak.DeleteDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var scopeId in updateScopes.Except(defaultScopes).Select(getClientScopeId)) + foreach (var scopeId in updateScopes.Except(defaultScopes) + .Select(getClientScopeId) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x))) { await keycloak.UpdateDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index 9a2beecac6..e3bb5ded40 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -26,6 +26,7 @@ public interface ISeedDataHandler Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken); string Realm { get; } + SeederConfiguration Configuration { get; } KeycloakRealm KeycloakRealm { get; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs index d78892a450..eb0d74668a 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -23,6 +23,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.IdentityProviders; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; @@ -34,6 +35,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; foreach (var updateIdentityProvider in seedDataHandler.IdentityProviders) { @@ -43,7 +45,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat try { var identityProvider = await keycloak.GetIdentityProviderAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - if (!CompareIdentityProvider(identityProvider, updateIdentityProvider)) + if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProvidersConfigKey, ModificationType.Update, updateIdentityProvider.Alias)) { UpdateIdentityProvider(identityProvider, updateIdentityProvider); await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -51,23 +53,27 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat } catch (KeycloakEntityNotFoundException) { - var identityProvider = new IdentityProvider(); - UpdateIdentityProvider(identityProvider, updateIdentityProvider); - await keycloak.CreateIdentityProviderAsync(realm, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + if (seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProvidersConfigKey, ModificationType.Create, updateIdentityProvider.Alias)) + { + var identityProvider = new IdentityProvider(); + UpdateIdentityProvider(identityProvider, updateIdentityProvider); + await keycloak.CreateIdentityProviderAsync(realm, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } } var updateMappers = seedDataHandler.IdentityProviderMappers.Where(x => x.IdentityProviderAlias == updateIdentityProvider.Alias); var mappers = await keycloak.GetIdentityProviderMappersAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteObsoleteIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name)) + foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Create, x.Id))) { await keycloak.AddIdentityProviderMapperAsync( realm, @@ -83,7 +89,7 @@ await keycloak.AddIdentityProviderMapperAsync( } } - private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers .Join( @@ -91,8 +97,7 @@ private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient k x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where( - x => !CompareIdentityProviderMapper(x.Mapper, x.Update))) + .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Update, x.Update.Id))) { await keycloak.UpdateIdentityProviderMapperAsync( realm, @@ -103,21 +108,22 @@ await keycloak.UpdateIdentityProviderMapperAsync( } } - private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name).IfAny( - async deleteMappers => - { - foreach (var mapper in deleteMappers) + if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Delete, x.Id)) + .IfAny(async deleteMappers => { - await keycloak.DeleteIdentityProviderMapperAsync( - realm, - alias, - mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), - cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } - }, - out var deleteMappersTask)) + foreach (var mapper in deleteMappers) + { + await keycloak.DeleteIdentityProviderMapperAsync( + realm, + alias, + mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), + cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } + }, + out var deleteMappersTask)) { await deleteMappersTask.ConfigureAwait(ConfigureAwaitOptions.None); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index b3e749aa00..402666d3c7 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -18,6 +18,8 @@ ********************************************************************************/ using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; @@ -44,17 +46,27 @@ public async Task Seed(CancellationToken cancellationToken) { await seedDataHandler.Import(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await realmUpdater.UpdateRealm(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await localizationsUpdater.UpdateLocalizations(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await userProfileUpdater.UpdateUserProfile(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await rolesUpdater.UpdateRealmRoles(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await clientScopesUpdater.UpdateClientScopes(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await clientsUpdater.UpdateClients(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await rolesUpdater.UpdateClientRoles(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await rolesUpdater.UpdateCompositeRoles(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await identityProvidersUpdater.UpdateIdentityProviders(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await usersUpdater.UpdateUsers(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await clientScopeMapperUpdater.UpdateClientScopeMapper(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await authenticationFlowsUpdater.UpdateAuthenticationFlows(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.LocalizationsConfigKey, realm.InstanceName, localizationsUpdater.UpdateLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.UserProfileConfigKey, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.RolesConfigKey, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopesConfigKey, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientsConfigKey, realm.InstanceName, clientsUpdater.UpdateClients, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientRolesConfigKey, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.RolesConfigKey, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.IdentityProvidersConfigKey, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.UsersConfigKey, realm.InstanceName, usersUpdater.UpdateUsers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopeMappersConfigKey, realm.InstanceName, clientScopeMapperUpdater.UpdateClientScopeMapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.AuthenticationFlowsConfigKey, realm.InstanceName, authenticationFlowsUpdater.UpdateAuthenticationFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } + + private async Task CheckAndExecuteUpdater(string configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) + { + if (!seedDataHandler.Configuration.ModificationAllowed(configKey)) + { + return; + } + + await updaterExecution(instanceName, cancellationToken).ConfigureAwait(false); + } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs index c6b8840ecc..03aa8c5b35 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs @@ -20,6 +20,8 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; @@ -30,19 +32,22 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; var localizations = await keycloak.GetLocaleAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmLocalizations = seedDataHandler.RealmLocalizations; - await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); foreach (var deleteTranslation in - localizations.ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale)) + localizations.ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Delete, x))) { await keycloak.DeleteLocaleAsync(realm, deleteTranslation, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, string realm, IEnumerable locales, - IEnumerable<(string Locale, IEnumerable> Translations)> translations, CancellationToken cancellationToken) + IEnumerable<(string Locale, IEnumerable> Translations)> translations, + SeederConfiguration seederConfig, CancellationToken cancellationToken) { if (!await locales .Join( @@ -56,31 +61,35 @@ private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, stri localesToUpdate) { var localizations = await keycloak.GetLocaleAsync(realm, locale, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateLocales(keycloak, realm, cancellationToken, update, localizations, locale).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteLocales(keycloak, realm, cancellationToken, localizations, update, locale).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateLocales(keycloak, realm, update, localizations, locale, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteLocales(keycloak, realm, localizations, update, locale, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } }).ConfigureAwait(false)) { - await AddLocales(keycloak, realm, translations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await AddLocales(keycloak, realm, translations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task DeleteLocales(KeycloakClient keycloak, string realm, CancellationToken cancellationToken, - IEnumerable> localizations, (string Locale, IEnumerable> Translations) update, string locale) + private static async Task DeleteLocales(KeycloakClient keycloak, string realm, + IEnumerable> localizations, (string Locale, IEnumerable> Translations) update, string locale, + SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var deleteTranslation in localizations.ExceptBy(update.Translations.Select(t => t.Key), - l => l.Key)) + l => l.Key) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Delete, x.Key))) { await keycloak.DeleteLocaleAsync(realm, locale, deleteTranslation.Key, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateLocales(KeycloakClient keycloak, string realm, CancellationToken cancellationToken, - (string Locale, IEnumerable> Translations) update, IEnumerable> localizations, string locale) + private static async Task UpdateLocales(KeycloakClient keycloak, string realm, + (string Locale, IEnumerable> Translations) update, IEnumerable> localizations, string locale, + SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var missingTranslation in update.Translations.ExceptBy(localizations.Select(loc => loc.Key), - locModel => locModel.Key)) + locModel => locModel.Key) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Update, x.Key))) { await keycloak.UpdateLocaleAsync(realm, locale, missingTranslation.Key, missingTranslation.Value, cancellationToken).ConfigureAwait(false); } @@ -90,16 +99,17 @@ private static async Task UpdateLocales(KeycloakClient keycloak, string realm, C update.Translations, l => l.Key, trans => trans.Key, - (l, trans) => (Key: l.Key, Update: trans))) + (l, trans) => (Key: l.Key, Update: trans)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Update, x.Key))) { await keycloak.UpdateLocaleAsync(realm, locale, updateTranslation.Key, updateTranslation.Update.Value, cancellationToken).ConfigureAwait(false); } } private static async Task AddLocales(KeycloakClient keycloak, string realm, IEnumerable<(string Locale, IEnumerable> Translations)> translations, - CancellationToken cancellationToken) + SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value)))) + foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value))).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Create, x.Key))) { await keycloak.UpdateLocaleAsync(realm, translation.Locale, translation.Key, translation.Value, cancellationToken).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index 928aff5d80..0b2600b1b3 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -22,62 +22,61 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class RolesUpdater : IRolesUpdater +public class RolesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IRolesUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public RolesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public async Task UpdateClientRoles(string keycloakInstanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var realm = _seedData.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; - foreach (var (clientId, updateRoles) in _seedData.ClientRoles) + foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { - var id = _seedData.GetIdOfClient(clientId); + var id = seedDataHandler.GetIdOfClient(clientId); var roles = await keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var newRole in updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + foreach (var newRole in updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientRolesConfigKey, ModificationType.Create, x.Name))) { await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, ConfigurationKeys.ClientRolesConfigKey, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var realm = _seedData.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; var roles = await keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var updateRealmRoles = _seedData.RealmRoles; + var updateRealmRoles = seedDataHandler.RealmRoles; - foreach (var newRole in updateRealmRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + foreach (var newRole in updateRealmRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Create, x.Name))) { await keycloak.CreateRoleAsync(realm, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, ConfigurationKeys.RolesConfigKey, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, CancellationToken cancellationToken) + private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, SeederConfiguration seederConfig, string configKey, CancellationToken cancellationToken) { foreach (var (role, update) in roles.Join( updateRoles, role => role.Name, roleModel => roleModel.Name, - (role, roleModel) => (Role: role, Update: roleModel))) + (role, roleModel) => (Role: role, Update: roleModel)) + .Where(x => seederConfig.ModificationAllowed(configKey, ModificationType.Update, x.Role.Name))) { if (!CompareRole(role, update)) { @@ -91,7 +90,8 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r } foreach (var deleteRole in - roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name)) + roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) + .Where(x => seederConfig.ModificationAllowed(configKey, ModificationType.Delete, x.Name))) { if (deleteRole.Id == null) throw new ConflictException($"role id must not be null: {deleteRole.Name}"); @@ -102,12 +102,13 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r public async Task UpdateCompositeRoles(string keycloakInstanceName, CancellationToken cancellationToken) { - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var realm = _seedData.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = seedDataHandler.Realm; + var seederConfig = seedDataHandler.Configuration; - foreach (var (clientId, updateRoles) in _seedData.ClientRoles) + foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { - var id = _seedData.GetIdOfClient(clientId); + var id = seedDataHandler.GetIdOfClient(clientId); await UpdateCompositeRolesInner( () => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken), @@ -118,7 +119,7 @@ await UpdateCompositeRolesInner( await UpdateCompositeRolesInner( () => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken), - _seedData.RealmRoles, + seedDataHandler.RealmRoles, (name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, name, roles, cancellationToken), (name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.None); @@ -137,7 +138,7 @@ async Task UpdateCompositeRolesInner( roleModel => roleModel.Composites?.Client? .FilterNotNullValues() .Select(x => ( - Id: _seedData.GetIdOfClient(x.Key), + Id: seedDataHandler.GetIdOfClient(x.Key), Names: x.Value)) .SelectMany(x => x.Names.Select(name => (x.Id, name))) ?? throw new ConflictException($"roleModel.Composites.Client is null: {roleModel.Id} {roleModel.Name}"), role => ( @@ -163,8 +164,8 @@ async Task RemoveAddCompositeRolesInner( Func joinUpdateKey, Func> getRoleByName) { - var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x)); - var removeComposites = roles.Where(x => compositeRolesPredicate(x)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); + var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Update, x.Name)); + var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); foreach (var remove in removeComposites) { @@ -199,7 +200,7 @@ async Task RemoveAddCompositeRolesInner( .SelectAwait(x => getRoleByName(x)) .ToListAsync(cancellationToken) .ConfigureAwait(false); - await addCompositeRoles(role.Name, add).ConfigureAwait(ConfigureAwaitOptions.None); + await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Create, x.Id))).ConfigureAwait(ConfigureAwaitOptions.None); } } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 49977033d8..121c507275 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -37,6 +37,7 @@ public class SeedDataHandler : ISeedDataHandler private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; + private SeederConfiguration? _seedingConfiguration; public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken) { @@ -46,7 +47,7 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken async (importRealm, path) => importRealm.Merge(await ReadJsonRealm(path, realmSettings.Realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)) .Merge(realmSettings.ToModel()); - + _seedingConfiguration = realmSettings.SeederConfiguration; _idOfClients = null; } @@ -69,6 +70,11 @@ public string Realm get => _keycloakRealm?.Realm ?? throw new ConflictException("realm must not be null"); } + public SeederConfiguration Configuration + { + get => _seedingConfiguration ?? throw new ConflictException("configuration must not be null"); + } + public KeycloakRealm KeycloakRealm { get => _keycloakRealm ?? throw new InvalidOperationException("Import has not been called"); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs index 812abf9692..b7e3bddac8 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs @@ -20,6 +20,8 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Users; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Text.Json; using System.Text.Json.Serialization; @@ -42,6 +44,11 @@ public async Task UpdateUserProfile(string keycloakInstanceName, CancellationTok var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; var userProfiles = seedDataHandler.RealmComponents.Where(x => x.ProviderType == UserProfileType); + var defaultConfig = seedDataHandler.Configuration; + if (defaultConfig.ModificationAllowed(ConfigurationKeys.UserProfileConfigKey, ModificationType.Update)) + { + return; + } var userProfile = await keycloak.GetUsersProfile(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs index f0d9a64ec3..40d6337664 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -24,35 +24,29 @@ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Users; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; -public class UsersUpdater : IUsersUpdater +public class UsersUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + : IUsersUpdater { - private readonly IKeycloakFactory _keycloakFactory; - private readonly ISeedDataHandler _seedData; - - public UsersUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) - { - _keycloakFactory = keycloakFactory; - _seedData = seedDataHandler; - } - public async Task UpdateUsers(string keycloakInstanceName, CancellationToken cancellationToken) { - var realm = _seedData.Realm; - var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var clientsDictionary = _seedData.ClientsDictionary; + var realm = seedDataHandler.Realm; + var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var clientsDictionary = seedDataHandler.ClientsDictionary; + var seederConfig = seedDataHandler.Configuration; - foreach (var seedUser in _seedData.Users) + foreach (var seedUser in seedDataHandler.Users) { if (seedUser.Username == null) throw new ConflictException($"username must not be null {seedUser.Id}"); var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); - if (user == null) + if (user == null && seederConfig.ModificationAllowed(ConfigurationKeys.UsersConfigKey, ModificationType.Create, seedUser.Username)) { var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) @@ -60,7 +54,7 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can throw new ConflictException($"PartialImport failed to add user id: {seedUser.Id}, userName: {seedUser.Username}"); } } - else + else if (user != null) { await UpdateUser( keycloak, @@ -68,17 +62,18 @@ await UpdateUser( user, seedUser, clientsDictionary, + seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } } - private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, CancellationToken cancellationToken) + private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, SeederConfiguration seederConfig, CancellationToken cancellationToken) { if (user.Id == null) throw new ConflictException($"user.Id must not be null: userName {seedUser.Username}"); - if (!CompareUser(user, seedUser)) + if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ConfigurationKeys.UsersConfigKey, ModificationType.Update, seedUser.Username)) { await keycloak.UpdateUserAsync( realm, @@ -100,6 +95,7 @@ await UpdateFederatedIdentities( realm, user.Id, seedUser.FederatedIdentities ?? Enumerable.Empty(), + seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -210,17 +206,19 @@ private static bool CompareFederatedIdentity(FederatedIdentity identity, Federat identity.UserId == update.UserId && identity.UserName == update.UserName; - private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable updates, CancellationToken cancellationToken) + private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) { var identities = await keycloak.GetUserSocialLoginsAsync(realm, userId).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteObsoleteFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var identity in identities.ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + foreach (var identity in identities + .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Delete, x.IdentityProvider))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, @@ -230,9 +228,10 @@ await keycloak.RemoveUserSocialLoginProviderAsync( } } - private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) { - foreach (var update in updates.ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + foreach (var update in updates.ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Create, x.IdentityProvider))) { await keycloak.AddUserSocialLoginProviderAsync( realm, @@ -248,7 +247,7 @@ await keycloak.AddUserSocialLoginProviderAsync( } } - private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) { foreach (var (identity, update) in identities .Join( @@ -256,7 +255,7 @@ private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycl x => x.IdentityProvider, x => x.IdentityProvider, (identity, update) => (Identity: identity, Update: update)) - .Where(x => !CompareFederatedIdentity(x.Identity, x.Update))) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Update, x.Update.IdentityProvider))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs new file mode 100644 index 0000000000..4036651c0f --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; + +public static class SeederConfigurationExtensions +{ + public static bool ModificationAllowed(this SeederConfiguration config, string configKey) + { + var specificConfig = config.Entities?.SingleOrDefault(x => x.Key.Equals(configKey, StringComparison.OrdinalIgnoreCase)); + if (specificConfig != null) + { + return specificConfig.Create || + specificConfig.Update || + specificConfig.Delete || + specificConfig.Entities?.Any(e => e.Create || e.Update || e.Delete) == true; + } + + return config.Create || config.Update || config.Delete; + } + + public static bool ModificationAllowed(this SeederConfiguration config, string configKey, ModificationType modificationType) => + config.ModificationAllowed(configKey, modificationType, null); + + public static bool ModificationAllowed(this SeederConfiguration config, string configKey, ModificationType modificationType, string? modelKey) + { + var specificConfig = config.Entities?.SingleOrDefault(x => x.Key.Equals(configKey, StringComparison.OrdinalIgnoreCase)); + if (modelKey == null) + return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + + // If we have a configuration for a specific entry return its value + var specificEntry = specificConfig?.Entities?.SingleOrDefault(c => c.Key == modelKey); + if (specificEntry != null) + { + return specificEntry.ModifyAllowed(modificationType); + } + + // If we don't have a specific value return the specific configuration value if we have one + return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + } + + private static bool ModifyAllowed(this SeederConfiguration configuration, ModificationType modificationType) => + modificationType switch + { + ModificationType.Create => configuration.Create, + ModificationType.Update => configuration.Update, + ModificationType.Delete => configuration.Delete, + _ => throw new ArgumentOutOfRangeException(nameof(modificationType), modificationType, null) + }; +} diff --git a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs new file mode 100644 index 0000000000..408a6e5ada --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs @@ -0,0 +1,23 @@ +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public static class ConfigurationKeys +{ + public const string RolesConfigKey = "ROLES"; + public const string LocalizationsConfigKey = "LOCALIZATIONS"; + public const string UserProfileConfigKey = "USERPROFILE"; + public const string ClientScopesConfigKey = "CLIENTSCOPES"; + public const string ClientsConfigKey = "CLIENTS"; + public const string IdentityProvidersConfigKey = "IDENTITYPROVIDERS"; + public const string IdentityProviderMappersConfigKey = "IDENTITYPROVIDERMAPPERS"; + public const string UsersConfigKey = "USERS"; + // TODO (PS): Clarify how to define the identity providers which should be skipped + public const string FederatedIdentitiesConfigKeys = "FEDERATEDIDENTITIES"; + public const string ClientScopeMappersConfigKey = "CLIENTSCOPEMAPPERS"; + public const string ProtocolMappersConfigKey = "PROTOCOLMAPPERS"; + // TODO (PS): Clarify how to define the auth flows which should be skipped + public const string AuthenticationFlowsConfigKey = "AUTHENTICATIONFLOWS"; + public const string ClientProtocolMapperConfigKey = "CLIENTPROTOCOLMAPPER"; + public const string ClientRolesConfigKey = "CLIENTROLES"; + public const string AuthenticationFlowExecutionConfigKey = "AUTHENTICATIONFLOWEXECUTION"; + public const string AuthenticatorConfigConfigKey = "AUTHENTICATORCONFIG"; +} diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs index 5ac852d022..c4aa95ec92 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs @@ -32,6 +32,7 @@ public class KeycloakRealmSettings [Required] [DistinctValues] public IEnumerable DataPaths { get; set; } = null!; + public SeederConfiguration SeederConfiguration { get; set; } = null!; public string? Id { get; set; } public string? DisplayName { get; set; } public string? DisplayNameHtml { get; set; } diff --git a/src/keycloak/Keycloak.Seeding/Models/ModificationType.cs b/src/keycloak/Keycloak.Seeding/Models/ModificationType.cs new file mode 100644 index 0000000000..759faf70e2 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/ModificationType.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public enum ModificationType +{ + Create = 1, + Update = 2, + Delete = 3 +} diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs new file mode 100644 index 0000000000..fb2f3c3c62 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public class SeederConfiguration +{ + public string Key { get; set; } = null!; + public bool Create { get; set; } + public bool Update { get; set; } + public bool Delete { get; set; } + + [DistinctValues] + public IEnumerable? Entities { get; set; } +} diff --git a/src/keycloak/Keycloak.Seeding/appsettings.json b/src/keycloak/Keycloak.Seeding/appsettings.json index eba5322645..80609f4786 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.json @@ -36,7 +36,127 @@ { "Realm": "", "InstanceName": "", - "DataPaths": [] + "DataPaths": [], + "SeederConfiguration": { + "Key": "Default", + "Create": true, + "Update": true, + "Delete": true, + "Entities":[ + { + "Key": "Roles", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "Localizations", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "UserProfile", + "Create": false, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "ClientScopes", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "Clients", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "IdentityProviders", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "IdentityProviderMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "Users", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "FederatedIdentities", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientScopeMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlows", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlowExecution", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientRoles", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticatorConfig", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + } + ] + } } ] } From aae78ae37bdecf05fdb32f08088192a33fdf571a Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Fri, 22 Nov 2024 14:09:30 +0100 Subject: [PATCH 02/21] docs(seeding): add documnentation for the keycloak seeder Refs: #1172 --- src/keycloak/Keycloak.Seeding/README.md | 116 ++++ .../Keycloak.Seeding/appsettings.example.json | 522 ++++++++++++++++++ .../Keycloak.Seeding/appsettings.json | 48 +- 3 files changed, 654 insertions(+), 32 deletions(-) create mode 100644 src/keycloak/Keycloak.Seeding/README.md create mode 100644 src/keycloak/Keycloak.Seeding/appsettings.example.json diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md new file mode 100644 index 0000000000..72ad5a1f9a --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -0,0 +1,116 @@ +# Seeding Configuration + +The Keycloak seeder has the possibility to be configured to only create, update and delete specific types or even specific entities for each realm. +The settings for the seeding can be made via the configuration. In each role config there is the possibility to set the SeederConfiguration. + +## Default Configuration + +In the Seeder configuration you must have one Default entry where the following values needs to be set: + +**Example**: + +```json + "SeederConfiguration": { + "Key": "Default", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + } +``` + +with this the general logic to create, update, delete entries can either be enabled or disabled. + +## Type Specific Configuration + +To be able to enable or disable the functionality for specific types the Entities array in the seeder configuration can be used. + +**Example**: + +```json + "SeederConfiguration": { + "Key": "Default", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [ + { + "Key": "Localizations", + "Create": false, + "Update": false, + "Delete": false, + } + ] + } +``` + +with this example configuration all entities would be created, updated and deleted, but for all entities that are roles the seeding wouldn't do anything. + +### Possible Types + +The following types can be configured: + +- `ROLES` +- `LOCALIZATIONS` +- `USERPROFILE` +- `CLIENTSCOPES` +- `CLIENTS` +- `IDENTITYPROVIDERS` +- `IDENTITYPROVIDERMAPPERS` +- `USERS` +- `FEDERATEDIDENTITIES` +- `CLIENTSCOPEMAPPERS` +- `PROTOCOLMAPPERS` +- `AUTHENTICATIONFLOWS` +- `CLIENTPROTOCOLMAPPER` +- `CLIENTROLES` +- `AUTHENTICATIONFLOWEXECUTION` +- `AUTHENTICATORCONFIG` + +## Entry Specific Configuration + +To be able to enable or disable the seeding for specific values the configuration can be adjusted as follows: + +**Example** + +```json + "SeederConfiguration": { + "Key": "Default", + "Create": true, + "Update": false, + "Delete": true, + "Entities": [ + { + "Key": "Localizations", + "Create": true, + "Update": false, + "Delete": true, + "Entities": [ + { + "Key": "profile.attributes.organisation", + "Create": true, + "Update": true, + "Delete": true + } + ] + } + ] + } +``` + +In the example above you can see that the default settings as well as the specific type settings for update are disabled. +But for localizations with the key `profile.attributes.organisation` the update is enabled. With this option you can enable the modification specifically for only the entities you want to modify with the seeding. + +**Note**: The key defers for the specific types e.g. for `Localization` it is a string for `User` it is a uuid. + +## Example Configuration + +For further reference you can have a look at the [example appsettings](./appsettings.example.json) + +## NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation +- Source URL: https://github.com/eclipse-tractusx/portal-backend diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json new file mode 100644 index 0000000000..1e9d1f88b2 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json @@ -0,0 +1,522 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithProcessId", + "WithThreadId", + "WithCorrelationId" + ], + "Properties": { + "Application": "Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding" + } + }, + "Keycloak": { + "central": { + "ConnectionString": "https://centralidp.tx.test", + "User": "admin", + "Password": "testPw", + "AuthRealm": "master", + "UseAuthTrail": true + } + }, + "KeycloakSeeding": { + "Realms": [ + { + "Realm": "CX-Central", + "InstanceName": "central", + "DataPaths": [ + "Seeding/CX-Central-realm.json" + ], + "SeederConfiguration": { + "Key": "Default", + "Create": true, + "Update": false, + "Delete": true, + "Entities":[ + { + "Key": "Roles", + "Create": false, + "Update": false, + "Delete": false, + "Entities": [] + }, + { + "Key": "Localizations", + "Create": true, + "Update": false, + "Delete": true, + "Entities": [ + { + "Key": "profile.attributes.organisation", + "Create": true, + "Update": true, + "Delete": true + } + ] + }, + { + "Key": "UserProfile", + "Create": false, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "ClientScopes", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "Clients", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "IdentityProviders", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [] + }, + { + "Key": "IdentityProviderMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "Users", + "Create": true, + "Update": false, + "Delete": false, + "Entities": [ + { + "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", + "Create": true, + "Update": true, + "Delete": false + } + ] + }, + { + "Key": "FederatedIdentities", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientScopeMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlows", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlowExecution", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientRoles", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticatorConfig", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + } + ] + }, + "Clients": [ + { + "ClientId": "Cl1-CX-Registration", + "RedirectUris": [ + "http://portal.tx.test/*", + "http://localhost:3000/*" + ] + }, + { + "ClientId": "Cl2-CX-Portal", + "RedirectUris": [ + "http://portal.tx.test/*", + "http://localhost:3000/*" + ], + "RootUrl": "http://portal.tx.test/home" + }, + { + "ClientId": "Cl3-CX-Semantic", + "RedirectUris": [ + "http://portal.tx.test/*" + ], + "RootUrl": "http://portal.tx.test/home" + }, + { + "ClientId": "Cl5-CX-Custodian", + "RedirectUris": [ + "http://managed-identity-wallets.tx.test/*" + ], + "Secret": "test" + }, + { + "ClientId": "Cl7-CX-BPDM", + "RedirectUris": [ + "http://partners-pool.tx.test/*" + ], + "Secret": "test" + }, + { + "ClientId": "Cl16-CX-BPDMGate", + "RedirectUris": [ + "http://partners-gate.tx.test/*" + ], + "Secret": "test" + }, + { + "ClientId": "Cl25-CX-BPDM-Orchestrator", + "Secret": "test" + }, + { + "ClientId": "sa-cl1-reg-2", + "Secret": "test" + }, + { + "ClientId": "sa-cl2-01", + "Secret": "test" + }, + { + "ClientId": "sa-cl2-02", + "Secret": "test" + }, + { + "ClientId": "sa-cl2-03", + "Secret": "test" + }, + { + "ClientId": "sa-cl2-04", + "Secret": "test" + }, + { + "ClientId": "sa-cl2-05", + "Secret": "test" + }, + { + "ClientId": "sa-cl3-cx-1", + "Secret": "test" + }, + { + "ClientId": "sa-cl5-custodian-2", + "Secret": "test" + }, + { + "ClientId": "sa-cl7-cx-1", + "Secret": "test" + }, + { + "ClientId": "sa-cl7-cx-5", + "Secret": "test" + }, + { + "ClientId": "sa-cl7-cx-7", + "Secret": "test" + }, + { + "ClientId": "sa-cl8-cx-1", + "Secret": "test" + }, + { + "ClientId": "sa-cl21-01", + "Secret": "test" + }, + { + "ClientId": "sa-cl22-01", + "Secret": "test" + }, + { + "ClientId": "sa-cl24-01", + "Secret": "test" + }, + { + "ClientId": "sa-cl25-cx-1", + "Secret": "test" + }, + { + "ClientId": "sa-cl25-cx-2", + "Secret": "test" + }, + { + "ClientId": "sa-cl25-cx-3", + "Secret": "test" + } + ], + "IdentityProviders": [ + { + "Alias": "CX-Operator", + "Config": { + "AuthorizationUrl": "http://sharedidp.tx.test/auth/realms/CX-Operator/protocol/openid-connect/auth", + "JwksUrl": "http://sharedidp.tx.test/auth/realms/CX-Operator/protocol/openid-connect/certs", + "LogoutUrl": "http://sharedidp.tx.test/auth/realms/CX-Operator/protocol/openid-connect/logout", + "TokenUrl": "http://sharedidp.tx.test/auth/realms/CX-Operator/protocol/openid-connect/token" + } + } + ], + "Users": [ + { + "Username": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl1-reg-2", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl7-cx-5", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl7-cx-7", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl8-cx-1", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl21-01", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl22-01", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl24-01", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl25-cx-1", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl25-cx-2", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl25-cx-3", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl2-01", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl2-02", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl2-03", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl2-04", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl2-05", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl3-cx-1", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl5-custodian-2", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + }, + { + "Username": "service-account-sa-cl7-cx-1", + "Attributes": [ + { + "Name": "bpn", + "Values": [ + "BPNL00000003CRHK" + ] + } + ] + } + ] + } + ] + } +} diff --git a/src/keycloak/Keycloak.Seeding/appsettings.json b/src/keycloak/Keycloak.Seeding/appsettings.json index 80609f4786..705ef2ae15 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.json @@ -47,113 +47,97 @@ "Key": "Roles", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "Localizations", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "UserProfile", "Create": false, "Update": true, - "Delete": false, - "Entities": [] + "Delete": false }, { "Key": "ClientScopes", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "Clients", "Create": true, "Update": true, - "Delete": false, - "Entities": [] + "Delete": false }, { "Key": "IdentityProviders", "Create": true, "Update": true, - "Delete": false, - "Entities": [] + "Delete": false }, { "Key": "IdentityProviderMappers", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "Users", "Create": true, "Update": true, - "Delete": false, - "Entities": [] + "Delete": false }, { "Key": "FederatedIdentities", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "ClientScopeMappers", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "ProtocolMappers", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "AuthenticationFlows", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "AuthenticationFlowExecution", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "ClientProtocolMappers", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "ClientRoles", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true }, { "Key": "AuthenticatorConfig", "Create": true, "Update": true, - "Delete": true, - "Entities": [] + "Delete": true } ] } From d8c1ce2168efc84214a7aeee5beaa450e0bab74a Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Fri, 22 Nov 2024 15:39:05 +0100 Subject: [PATCH 03/21] chore: add missing file header Refs: #1172 --- .../Models/ConfigurationKeys.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs index 408a6e5ada..b196b4cc56 100644 --- a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs +++ b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs @@ -1,3 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; public static class ConfigurationKeys From 45bf0d53cde0ccb69a0fb2fe7e4740bd224fb8d4 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Nov 2024 11:29:03 +0100 Subject: [PATCH 04/21] feat(seeding): adjust seeding configuration Refs: #1172 --- .../AuthenticationFlowsUpdater.cs | 38 +-- .../BusinessLogic/ClientScopeMapperUpdater.cs | 6 +- .../BusinessLogic/ClientScopesUpdater.cs | 43 +-- .../BusinessLogic/ClientsUpdater.cs | 30 +- .../BusinessLogic/ISeedDataHandler.cs | 2 +- .../BusinessLogic/IdentityProvidersUpdater.cs | 16 +- .../BusinessLogic/KeecloakSeeder.cs | 24 +- .../BusinessLogic/LocalizationsUpdater.cs | 18 +- .../BusinessLogic/RolesUpdater.cs | 16 +- .../BusinessLogic/SeedDataHandler.cs | 9 +- .../BusinessLogic/UserProfileUpdater.cs | 2 +- .../BusinessLogic/UsersUpdater.cs | 30 +- .../SeederConfigurationExtensions.cs | 74 ++++- .../Models/ConfigurationKeys.cs | 36 ++- .../Models/KeycloakRealmSettings.cs | 6 +- src/keycloak/Keycloak.Seeding/README.md | 99 +++++-- .../Keycloak.Seeding/appsettings.example.json | 276 +++++++++--------- .../Keycloak.Seeding/appsettings.json | 205 +++++++------ 18 files changed, 516 insertions(+), 414 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs index 74c38c4daa..233b5b68ab 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -57,10 +57,10 @@ public async Task UpdateAuthenticationFlows(CancellationToken cancellationToken) await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var delete in topLevelCustomFlows.ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Delete, x.Alias))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Delete, x.Alias))) { if (delete.Id == null) @@ -69,7 +69,7 @@ private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task AddMissingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var addFlow in seedFlows.ExceptBy(topLevelCustomFlows.Select(x => x.Alias), x => x.Alias)) { @@ -77,7 +77,7 @@ private async Task AddMissingAuthenticationFlows(IEnumerable throw new ConflictException($"authenticationFlow.Alias is null {addFlow.Id} {addFlow.Description}"); if (addFlow.BuiltIn ?? false) throw new ConflictException($"authenticationFlow.buildIn is true. flow cannot be added: {addFlow.Alias}"); - if (seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Create, addFlow.Id)) + if (seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Create, addFlow.Alias)) { await keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -86,7 +86,7 @@ private async Task AddMissingAuthenticationFlows(IEnumerable } } - private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var (flow, seed) in topLevelCustomFlows .Join( @@ -94,7 +94,7 @@ private async Task UpdateExistingAuthenticationFlows(IEnumerable x.Alias, x => x.Alias, (flow, seed) => (Flow: flow, Seed: seed)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowsConfigKey, ModificationType.Update, x.Flow.Id))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Update, x.Flow.Alias))) { if (flow.Id == null) throw new ConflictException($"authenticationFlow.id is null {flow.Alias} {flow.Description}"); @@ -125,7 +125,7 @@ private static bool CompareAuthenticationFlow(AuthenticationFlow flow, Authentic flow.ProviderId == update.ProviderId && flow.TopLevel == update.TopLevel; - private async Task UpdateAuthenticationFlowExecutions(string alias, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task UpdateAuthenticationFlowExecutions(string alias, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { var updateExecutions = seedDataHandler.GetAuthenticationExecutions(alias); var executionNodes = ExecutionNode.Parse(await GetExecutions(alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)); @@ -149,9 +149,9 @@ private bool CompareStructureRecursive(IReadOnlyList executions, (x.Node.Execution.AuthenticationFlow ?? false) == (x.Update.AuthenticatorFlow ?? false) && (!(x.Node.Execution.AuthenticationFlow ?? false) || CompareStructureRecursive(x.Node.Children, seedDataHandler.GetAuthenticationExecutions(x.Update.FlowAlias)))); - private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { - foreach (var executionNode in executionNodes.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Delete, x.Execution.Id))) + foreach (var executionNode in executionNodes.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Delete, x.Execution.Id))) { if (executionNode.Execution.AuthenticationFlow ?? false) { @@ -162,9 +162,9 @@ private async Task DeleteExecutionsRecursive(IEnumerable executio } } - private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { - foreach (var execution in seedExecutions.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Delete, x.FlowAlias))) + foreach (var execution in seedExecutions.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Delete, x.FlowAlias))) { await (execution.AuthenticatorFlow switch { @@ -180,12 +180,12 @@ async Task AddAuthenticationFlowExecutionRecursive(string updateAlias, Authentic } } - private async Task UpdateExecutionsRecursive(string alias, IReadOnlyCollection executionNodes, IEnumerable seedExecutions, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task UpdateExecutionsRecursive(string alias, IReadOnlyCollection executionNodes, IEnumerable seedExecutions, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { if (executionNodes.Count != seedExecutions.Count()) throw new ArgumentException("number of elements in executionNodes doesn't match seedData"); - foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Update, x.First.Execution.Id))) + foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Update, x.First.Execution.Id))) { if ((executionNode.Execution.AuthenticationFlow ?? false) != (update.AuthenticatorFlow ?? false)) throw new ArgumentException("execution.AuthenticatorFlow doesn't match seedData"); @@ -234,7 +234,7 @@ await UpdateExecutionsRecursive( async Task UpdateAuthenticationExecution(ExecutionNode executionNode, AuthenticationExecutionModel update, CancellationToken ct) { var (isEqual, authenticatorConfig) = await CompareExecutions(executionNode.Execution, update, ct).ConfigureAwait(ConfigureAwaitOptions.None); - if (!isEqual && seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecutionConfigKey, ModificationType.Update, executionNode.Execution.Id)) + if (!isEqual && seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Update, executionNode.Execution.Id)) { await keycloak.UpdateAuthenticationFlowExecutionsAsync( _realm, @@ -257,14 +257,14 @@ await keycloak.UpdateAuthenticationFlowExecutionsAsync( } } - private async Task UpdateAuthenticatorConfig(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, AuthenticatorConfig? config, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private async Task UpdateAuthenticatorConfig(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, AuthenticatorConfig? config, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { switch (execution.AuthenticationConfig, update.AuthenticatorConfig) { case (null, null): break; - case (null, var _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfigConfigKey, ModificationType.Create, execution.Id): + case (null, var _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Create, update.AuthenticatorConfig): await keycloak.CreateAuthenticationExecutionConfigurationAsync( _realm, execution.Id!, @@ -276,20 +276,20 @@ await keycloak.CreateAuthenticationExecutionConfigurationAsync( cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); break; - case (var _, null) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfigConfigKey, ModificationType.Delete, execution.Id): + case (var _, null) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Delete, update.AuthenticatorConfig): await keycloak.DeleteAuthenticatorConfigurationAsync( _realm, execution.AuthenticationConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); break; - case var (_, _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfigConfigKey, ModificationType.Update, execution.Id): + case var (_, _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Update, update.AuthenticatorConfig): var updateConfig = seedDataHandler.GetAuthenticatorConfig(update.AuthenticatorConfig); if (config == null) throw new UnexpectedConditionException("authenticatorConfig is null"); config.Alias = update.AuthenticatorConfig; config.Config = updateConfig.Config?.FilterNotNullValues().ToDictionary(); - await keycloak.UpdateAuthenticatorConfigurationAsync(_realm, execution.AuthenticationConfig, config, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await keycloak.UpdateAuthenticatorConfigurationAsync(_realm, execution.AuthenticationConfig!, config, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); break; } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs index a985e2bb9f..4b4c6021e9 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs @@ -60,15 +60,15 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken } } - private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { await updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, x.Name)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Create, x.Name)) .IfAnyAwait(rolesToAdd => keycloak.AddClientRolesScopeMappingToClientAsync(realm, clientScopeId, clientId, rolesToAdd, cancellationToken)).ConfigureAwait(false); await roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x.Name)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x.Name)) .IfAnyAwait(rolesToDelete => keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index 3c3394643c..2f9e8eedb5 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -39,12 +39,12 @@ public async Task UpdateClientScopes(string instanceName, CancellationToken canc var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedClientScopes = seedDataHandler.ClientScopes; - await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task CheckAndExecute(string configKey, ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken, Func, IEnumerable, SeederConfiguration, CancellationToken, Task> executeLogic) + private static async Task CheckAndExecute(ConfigurationKeys configKey, ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakRealmSettings, CancellationToken, Task> executeLogic) { if (!seederConfig.ModificationAllowed(configKey, modificationType)) { @@ -54,10 +54,10 @@ private static async Task CheckAndExecute(string configKey, ModificationType mod await executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x.Name))) { await keycloak.DeleteClientScopeAsync( realm, @@ -66,16 +66,16 @@ await keycloak.DeleteClientScopeAsync( } } - private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Create, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Create, x.Name))) { await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var (clientScope, update) in clientScopes .Join( @@ -83,17 +83,20 @@ private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, st x => x.Name, x => x.Name, (clientScope, update) => (ClientScope: clientScope, Update: update)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x.Update.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x.Update.Name))) { await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { if (clientScope.Id == null) throw new ConflictException($"clientScope.Id is null: {clientScope.Name}"); + if (clientScope.Name == null) + throw new ConflictException($"clientScope.Name is null: {clientScope.Name}"); + if (!CompareClientScope(clientScope, update)) { await keycloak.UpdateClientScopeAsync( @@ -106,15 +109,15 @@ await keycloak.UpdateClientScopeAsync( var mappers = clientScope.ProtocolMappers ?? Enumerable.Empty(); var updateMappers = update.ProtocolMappers ?? Enumerable.Empty(); - await DeleteObsoleteProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteProtocolMappers(keycloak, realm, clientScope.Name, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingProtocolMappers(keycloak, realm, clientScope.Name, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Name, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var mapper in mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Delete, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Delete, x.Name))) { await keycloak.DeleteProtocolMapperAsync( realm, @@ -124,10 +127,10 @@ await keycloak.DeleteProtocolMapperAsync( } } - private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var update in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Create, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Create, x.Name))) { await keycloak.CreateProtocolMapperAsync( realm, @@ -137,14 +140,14 @@ await keycloak.CreateProtocolMapperAsync( } } - private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers.Join( updateMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ProtocolMappersConfigKey, ModificationType.Update, x.Update.Name))) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Update, x.Update.Name))) { await keycloak.UpdateProtocolMapperAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs index f3538f9a0d..35864779e4 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -42,7 +42,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); } - private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, SeederConfiguration seederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakRealmSettings seederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) { var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); string GetClientScopeId(string scope) => clientScopes.SingleOrDefault(x => x.Name == scope)?.Id ?? throw new ConflictException($"id of clientScope {scope} is undefined"); @@ -55,7 +55,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell var client = (await keycloak.GetClientsAsync(realm, clientId: update.ClientId, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.ClientId == update.ClientId); if (client == null) { - if (!seederConfig.ModificationAllowed(ConfigurationKeys.ClientsConfigKey, ModificationType.Create, update.Id)) + if (!seederConfig.ModificationAllowed(ConfigurationKeys.Clients, ModificationType.Create, update.ClientId)) { yield return null; } @@ -64,7 +64,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell } else { - if (seederConfig.ModificationAllowed(ConfigurationKeys.ClientsConfigKey, ModificationType.Update, update.Id)) + if (seederConfig.ModificationAllowed(ConfigurationKeys.Clients, ModificationType.Update, update.ClientId)) { await UpdateClient( keycloak, @@ -134,21 +134,23 @@ await keycloak.UpdateClientAsync( } } - private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { var clientProtocolMappers = client.ProtocolMappers ?? Enumerable.Empty(); var updateProtocolMappers = update.ProtocolMappers ?? Enumerable.Empty(); + if (client.ClientId == null) + throw new ConflictException("client.ClientId must never be null"); foreach (var mapperId in clientProtocolMappers.ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name) - .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}")) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Delete, x.Name)) + .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}"))) { await keycloak.DeleteClientProtocolMapperAsync(realm, clientId, mapperId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } foreach (var mapper in updateProtocolMappers.ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name) .Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Create, x.Id))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Create, x.Name))) { await keycloak.CreateClientProtocolMapperAsync(realm, clientId, mapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -159,7 +161,7 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ClientProtocolMapperConfigKey, ModificationType.Update, x.Update.Id)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Update, x.Update.Id)) .Select(x => ( x.Mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Mapper.Name}"), ProtocolMappersUpdater.CreateProtocolMapper(x.Mapper.Id, x.Update)))) @@ -168,41 +170,41 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s } } - private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { var optionalScopes = client.OptionalClientScopes ?? Enumerable.Empty(); var updateScopes = update.OptionalClientScopes ?? Enumerable.Empty(); foreach (var scopeId in optionalScopes.Except(updateScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x))) { await keycloak.DeleteOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } foreach (var scopeId in updateScopes.Except(optionalScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x))) { await keycloak.UpdateOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { var defaultScopes = client.DefaultClientScopes ?? Enumerable.Empty(); var updateScopes = update.DefaultClientScopes ?? Enumerable.Empty(); foreach (var scopeId in defaultScopes.Except(updateScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x))) { await keycloak.DeleteDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } foreach (var scopeId in updateScopes.Except(defaultScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopesConfigKey, ModificationType.Update, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x))) { await keycloak.UpdateDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index e3bb5ded40..30e3bf8b88 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -26,7 +26,7 @@ public interface ISeedDataHandler Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken); string Realm { get; } - SeederConfiguration Configuration { get; } + KeycloakRealmSettings Configuration { get; } KeycloakRealm KeycloakRealm { get; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs index eb0d74668a..2d7e104de5 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -45,7 +45,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat try { var identityProvider = await keycloak.GetIdentityProviderAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProvidersConfigKey, ModificationType.Update, updateIdentityProvider.Alias)) + if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, ModificationType.Update, updateIdentityProvider.Alias)) { UpdateIdentityProvider(identityProvider, updateIdentityProvider); await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -53,7 +53,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat } catch (KeycloakEntityNotFoundException) { - if (seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProvidersConfigKey, ModificationType.Create, updateIdentityProvider.Alias)) + if (seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, ModificationType.Create, updateIdentityProvider.Alias)) { var identityProvider = new IdentityProvider(); UpdateIdentityProvider(identityProvider, updateIdentityProvider); @@ -70,10 +70,10 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat } } - private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Create, x.Id))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Create, x.Id))) { await keycloak.AddIdentityProviderMapperAsync( realm, @@ -89,7 +89,7 @@ await keycloak.AddIdentityProviderMapperAsync( } } - private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers .Join( @@ -97,7 +97,7 @@ private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient k x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Update, x.Update.Id))) + .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Update, x.Update.Id))) { await keycloak.UpdateIdentityProviderMapperAsync( realm, @@ -108,10 +108,10 @@ await keycloak.UpdateIdentityProviderMapperAsync( } } - private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviderMappersConfigKey, ModificationType.Delete, x.Id)) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Delete, x.Id)) .IfAny(async deleteMappers => { foreach (var mapper in deleteMappers) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index 402666d3c7..182bd6b092 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -46,21 +46,21 @@ public async Task Seed(CancellationToken cancellationToken) { await seedDataHandler.Import(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await realmUpdater.UpdateRealm(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.LocalizationsConfigKey, realm.InstanceName, localizationsUpdater.UpdateLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.UserProfileConfigKey, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.RolesConfigKey, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopesConfigKey, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientsConfigKey, realm.InstanceName, clientsUpdater.UpdateClients, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientRolesConfigKey, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.RolesConfigKey, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.IdentityProvidersConfigKey, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.UsersConfigKey, realm.InstanceName, usersUpdater.UpdateUsers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopeMappersConfigKey, realm.InstanceName, clientScopeMapperUpdater.UpdateClientScopeMapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.AuthenticationFlowsConfigKey, realm.InstanceName, authenticationFlowsUpdater.UpdateAuthenticationFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.Localizations, realm.InstanceName, localizationsUpdater.UpdateLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.UserProfile, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopes, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.Clients, realm.InstanceName, clientsUpdater.UpdateClients, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientRoles, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.IdentityProviders, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.Users, realm.InstanceName, usersUpdater.UpdateUsers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopeMappers, realm.InstanceName, clientScopeMapperUpdater.UpdateClientScopeMapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKeys.AuthenticationFlows, realm.InstanceName, authenticationFlowsUpdater.UpdateAuthenticationFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task CheckAndExecuteUpdater(string configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) + private async Task CheckAndExecuteUpdater(ConfigurationKeys configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) { if (!seedDataHandler.Configuration.ModificationAllowed(configKey)) { diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs index 03aa8c5b35..e4767f5cac 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs @@ -39,7 +39,7 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); foreach (var deleteTranslation in localizations.ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Delete, x))) { await keycloak.DeleteLocaleAsync(realm, deleteTranslation, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -47,7 +47,7 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, string realm, IEnumerable locales, IEnumerable<(string Locale, IEnumerable> Translations)> translations, - SeederConfiguration seederConfig, CancellationToken cancellationToken) + KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { if (!await locales .Join( @@ -72,12 +72,12 @@ private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, stri private static async Task DeleteLocales(KeycloakClient keycloak, string realm, IEnumerable> localizations, (string Locale, IEnumerable> Translations) update, string locale, - SeederConfiguration seederConfig, CancellationToken cancellationToken) + KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var deleteTranslation in localizations.ExceptBy(update.Translations.Select(t => t.Key), l => l.Key) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Delete, x.Key))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Delete, x.Key))) { await keycloak.DeleteLocaleAsync(realm, locale, deleteTranslation.Key, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -85,11 +85,11 @@ private static async Task DeleteLocales(KeycloakClient keycloak, string realm, private static async Task UpdateLocales(KeycloakClient keycloak, string realm, (string Locale, IEnumerable> Translations) update, IEnumerable> localizations, string locale, - SeederConfiguration seederConfig, CancellationToken cancellationToken) + KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var missingTranslation in update.Translations.ExceptBy(localizations.Select(loc => loc.Key), locModel => locModel.Key) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Update, x.Key))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Update, x.Key))) { await keycloak.UpdateLocaleAsync(realm, locale, missingTranslation.Key, missingTranslation.Value, cancellationToken).ConfigureAwait(false); } @@ -100,16 +100,16 @@ private static async Task UpdateLocales(KeycloakClient keycloak, string realm, l => l.Key, trans => trans.Key, (l, trans) => (Key: l.Key, Update: trans)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Update, x.Key))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Update, x.Key))) { await keycloak.UpdateLocaleAsync(realm, locale, updateTranslation.Key, updateTranslation.Update.Value, cancellationToken).ConfigureAwait(false); } } private static async Task AddLocales(KeycloakClient keycloak, string realm, IEnumerable<(string Locale, IEnumerable> Translations)> translations, - SeederConfiguration seederConfig, CancellationToken cancellationToken) + KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { - foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value))).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.LocalizationsConfigKey, ModificationType.Create, x.Key))) + foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value))).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Create, x.Key))) { await keycloak.UpdateLocaleAsync(realm, translation.Locale, translation.Key, translation.Value, cancellationToken).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index 0b2600b1b3..f97b988c78 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -42,12 +42,12 @@ public async Task UpdateClientRoles(string keycloakInstanceName, CancellationTok var roles = await keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); foreach (var newRole in updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientRolesConfigKey, ModificationType.Create, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientRoles, ModificationType.Create, x.Name))) { await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, ConfigurationKeys.ClientRolesConfigKey, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, ConfigurationKeys.ClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } @@ -60,15 +60,15 @@ public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToke var updateRealmRoles = seedDataHandler.RealmRoles; foreach (var newRole in updateRealmRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Create, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Create, x.Name))) { await keycloak.CreateRoleAsync(realm, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, ConfigurationKeys.RolesConfigKey, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, ConfigurationKeys.Roles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, SeederConfiguration seederConfig, string configKey, CancellationToken cancellationToken) + private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakRealmSettings seederConfig, ConfigurationKeys configKey, CancellationToken cancellationToken) { foreach (var (role, update) in roles.Join( @@ -164,8 +164,8 @@ async Task RemoveAddCompositeRolesInner( Func joinUpdateKey, Func> getRoleByName) { - var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Update, x.Name)); - var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); + var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Update, x.Name)); + var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); foreach (var remove in removeComposites) { @@ -200,7 +200,7 @@ async Task RemoveAddCompositeRolesInner( .SelectAwait(x => getRoleByName(x)) .ToListAsync(cancellationToken) .ConfigureAwait(false); - await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.RolesConfigKey, ModificationType.Create, x.Id))).ConfigureAwait(ConfigureAwaitOptions.None); + await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None); } } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 121c507275..0d9a65db67 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -37,7 +37,7 @@ public class SeedDataHandler : ISeedDataHandler private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; - private SeederConfiguration? _seedingConfiguration; + private KeycloakRealmSettings? _realmConfiguration; public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken) { @@ -47,7 +47,7 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken async (importRealm, path) => importRealm.Merge(await ReadJsonRealm(path, realmSettings.Realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)) .Merge(realmSettings.ToModel()); - _seedingConfiguration = realmSettings.SeederConfiguration; + _realmConfiguration = realmSettings; _idOfClients = null; } @@ -59,6 +59,7 @@ private static async Task ReadJsonRealm(string path, string realm jsonRealm = await JsonSerializer.DeserializeAsync(stream, Options, cancellationToken) .ConfigureAwait(false) ?? throw new ConfigurationException($"cannot deserialize realm from {path}"); } + if (jsonRealm.Realm != null && jsonRealm.Realm != realm) throw new ConfigurationException($"json realm {jsonRealm.Realm} doesn't match the configured realm: {realm}"); @@ -70,9 +71,9 @@ public string Realm get => _keycloakRealm?.Realm ?? throw new ConflictException("realm must not be null"); } - public SeederConfiguration Configuration + public KeycloakRealmSettings Configuration { - get => _seedingConfiguration ?? throw new ConflictException("configuration must not be null"); + get => _realmConfiguration ?? throw new ConflictException("configuration must not be null"); } public KeycloakRealm KeycloakRealm diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs index b7e3bddac8..d78e6ba40f 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs @@ -45,7 +45,7 @@ public async Task UpdateUserProfile(string keycloakInstanceName, CancellationTok var realm = seedDataHandler.Realm; var userProfiles = seedDataHandler.RealmComponents.Where(x => x.ProviderType == UserProfileType); var defaultConfig = seedDataHandler.Configuration; - if (defaultConfig.ModificationAllowed(ConfigurationKeys.UserProfileConfigKey, ModificationType.Update)) + if (defaultConfig.ModificationAllowed(ConfigurationKeys.UserProfile, ModificationType.Update)) { return; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs index 40d6337664..dd24ff98ee 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -46,7 +46,7 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); - if (user == null && seederConfig.ModificationAllowed(ConfigurationKeys.UsersConfigKey, ModificationType.Create, seedUser.Username)) + if (user == null && seederConfig.ModificationAllowed(ConfigurationKeys.Users, ModificationType.Create, seedUser.Username)) { var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) @@ -68,12 +68,15 @@ await UpdateUser( } } - private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { if (user.Id == null) throw new ConflictException($"user.Id must not be null: userName {seedUser.Username}"); - if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ConfigurationKeys.UsersConfigKey, ModificationType.Update, seedUser.Username)) + if (user.UserName == null) + throw new ConflictException($"user.UserName must not be null: userName {seedUser.Username}"); + + if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ConfigurationKeys.Users, ModificationType.Update, user.UserName)) { await keycloak.UpdateUserAsync( realm, @@ -93,6 +96,7 @@ await UpdateClientAndRealmRoles( await UpdateFederatedIdentities( keycloak, realm, + user.UserName, user.Id, seedUser.FederatedIdentities ?? Enumerable.Empty(), seederConfig, @@ -206,19 +210,19 @@ private static bool CompareFederatedIdentity(FederatedIdentity identity, Federat identity.UserId == update.UserId && identity.UserName == update.UserName; - private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { var identities = await keycloak.GetUserSocialLoginsAsync(realm, userId).ConfigureAwait(ConfigureAwaitOptions.None); - await DeleteObsoleteFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CreateMissingFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingFederatedIdentities(keycloak, realm, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteObsoleteFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CreateMissingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var identity in identities .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Delete, x.IdentityProvider))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Delete, x.IdentityProvider))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, @@ -228,10 +232,10 @@ await keycloak.RemoveUserSocialLoginProviderAsync( } } - private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var update in updates.ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Create, x.IdentityProvider))) + .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Create, x.IdentityProvider))) { await keycloak.AddUserSocialLoginProviderAsync( realm, @@ -247,7 +251,7 @@ await keycloak.AddUserSocialLoginProviderAsync( } } - private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, SeederConfiguration seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) { foreach (var (identity, update) in identities .Join( @@ -255,7 +259,7 @@ private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycl x => x.IdentityProvider, x => x.IdentityProvider, (identity, update) => (Identity: identity, Update: update)) - .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.FederatedIdentitiesConfigKeys, ModificationType.Update, x.Update.IdentityProvider))) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Update, x.Update.IdentityProvider))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 4036651c0f..c6ee7bfadb 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -23,31 +23,54 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class SeederConfigurationExtensions { - public static bool ModificationAllowed(this SeederConfiguration config, string configKey) + public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey) { - var specificConfig = config.Entities?.SingleOrDefault(x => x.Key.Equals(configKey, StringComparison.OrdinalIgnoreCase)); - if (specificConfig != null) + if (config.SeederConfiguration != null && + IsModificationAllowed(config.SeederConfiguration, configKey.ToString(), out var _)) { - return specificConfig.Create || - specificConfig.Update || - specificConfig.Delete || - specificConfig.Entities?.Any(e => e.Create || e.Update || e.Delete) == true; + return true; } return config.Create || config.Update || config.Delete; } - public static bool ModificationAllowed(this SeederConfiguration config, string configKey, ModificationType modificationType) => + private static bool IsModificationAllowed( + IEnumerable configurations, + string targetKey, + out SeederConfiguration? matchingConfig) + { + matchingConfig = null; + + foreach (var config in configurations) + { + if (config.Entities != null && IsModificationAllowed(config.Entities, targetKey, out matchingConfig)) + { + return true; + } + + if (config.Key != targetKey || config is { Create: false, Update: false, Delete: false }) + { + continue; + } + + matchingConfig = config; + return true; + } + + return false; + } + + public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey, ModificationType modificationType) => config.ModificationAllowed(configKey, modificationType, null); - public static bool ModificationAllowed(this SeederConfiguration config, string configKey, ModificationType modificationType, string? modelKey) + public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) { - var specificConfig = config.Entities?.SingleOrDefault(x => x.Key.Equals(configKey, StringComparison.OrdinalIgnoreCase)); - if (modelKey == null) + var specificConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)); + if (entityKey is null) return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); // If we have a configuration for a specific entry return its value - var specificEntry = specificConfig?.Entities?.SingleOrDefault(c => c.Key == modelKey); + var specificEntry = specificConfig?.Entities?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); if (specificEntry != null) { return specificEntry.ModifyAllowed(modificationType); @@ -57,6 +80,33 @@ public static bool ModificationAllowed(this SeederConfiguration config, string c return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); } + public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys containingConfigKey, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) + { + var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(configKey)); + if (containingEntityTypeConfig is null) + { + return config.ModificationAllowed(configKey, modificationType, entityKey); + } + + if (entityKey is null) + { + return containingEntityTypeConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + } + + // If we have a configuration for a specific entry return its value + var entity = containingEntityTypeConfig.Entities?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); + return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(configKey, modificationType, entityKey); + } + + private static bool ModifyAllowed(this KeycloakRealmSettings configuration, ModificationType modificationType) => + modificationType switch + { + ModificationType.Create => configuration.Create, + ModificationType.Update => configuration.Update, + ModificationType.Delete => configuration.Delete, + _ => throw new ArgumentOutOfRangeException(nameof(modificationType), modificationType, null) + }; + private static bool ModifyAllowed(this SeederConfiguration configuration, ModificationType modificationType) => modificationType switch { diff --git a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs index b196b4cc56..11135b6238 100644 --- a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs +++ b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs @@ -19,24 +19,22 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; -public static class ConfigurationKeys +public enum ConfigurationKeys { - public const string RolesConfigKey = "ROLES"; - public const string LocalizationsConfigKey = "LOCALIZATIONS"; - public const string UserProfileConfigKey = "USERPROFILE"; - public const string ClientScopesConfigKey = "CLIENTSCOPES"; - public const string ClientsConfigKey = "CLIENTS"; - public const string IdentityProvidersConfigKey = "IDENTITYPROVIDERS"; - public const string IdentityProviderMappersConfigKey = "IDENTITYPROVIDERMAPPERS"; - public const string UsersConfigKey = "USERS"; - // TODO (PS): Clarify how to define the identity providers which should be skipped - public const string FederatedIdentitiesConfigKeys = "FEDERATEDIDENTITIES"; - public const string ClientScopeMappersConfigKey = "CLIENTSCOPEMAPPERS"; - public const string ProtocolMappersConfigKey = "PROTOCOLMAPPERS"; - // TODO (PS): Clarify how to define the auth flows which should be skipped - public const string AuthenticationFlowsConfigKey = "AUTHENTICATIONFLOWS"; - public const string ClientProtocolMapperConfigKey = "CLIENTPROTOCOLMAPPER"; - public const string ClientRolesConfigKey = "CLIENTROLES"; - public const string AuthenticationFlowExecutionConfigKey = "AUTHENTICATIONFLOWEXECUTION"; - public const string AuthenticatorConfigConfigKey = "AUTHENTICATORCONFIG"; + Roles = 1, + Localizations = 2, + UserProfile = 3, + ClientScopes = 4, + Clients = 5, + IdentityProviders = 6, + IdentityProviderMappers = 7, + Users = 8, + FederatedIdentities = 9, + ClientScopeMappers = 10, + ProtocolMappers = 11, + AuthenticationFlows = 12, + ClientProtocolMapper = 13, + ClientRoles = 14, + AuthenticationFlowExecution = 15, + AuthenticatorConfig = 16 } diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs index c4aa95ec92..a5284b7c4e 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs @@ -32,7 +32,11 @@ public class KeycloakRealmSettings [Required] [DistinctValues] public IEnumerable DataPaths { get; set; } = null!; - public SeederConfiguration SeederConfiguration { get; set; } = null!; + public bool Create { get; set; } + public bool Update { get; set; } + public bool Delete { get; set; } + [DistinctValues] + public IEnumerable? SeederConfiguration { get; set; } public string? Id { get; set; } public string? DisplayName { get; set; } public string? DisplayNameHtml { get; set; } diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md index 72ad5a1f9a..d73ce808f4 100644 --- a/src/keycloak/Keycloak.Seeding/README.md +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -10,13 +10,12 @@ In the Seeder configuration you must have one Default entry where the following **Example**: ```json - "SeederConfiguration": { - "Key": "Default", + "Realms": [ "Create": true, "Update": true, "Delete": true, - "Entities": [] - } + "SeederConfiguration": [] + ] ``` with this the general logic to create, update, delete entries can either be enabled or disabled. @@ -28,20 +27,14 @@ To be able to enable or disable the functionality for specific types the Entitie **Example**: ```json - "SeederConfiguration": { - "Key": "Default", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [ - { - "Key": "Localizations", - "Create": false, - "Update": false, - "Delete": false, - } - ] - } + "SeederConfiguration": [ + { + "Key": "Localizations", + "Create": false, + "Update": false, + "Delete": false, + } + ] ``` with this example configuration all entities would be created, updated and deleted, but for all entities that are roles the seeding wouldn't do anything. @@ -74,28 +67,22 @@ To be able to enable or disable the seeding for specific values the configuratio **Example** ```json - "SeederConfiguration": { - "Key": "Default", + "SeederConfiguration": [ + { + "Key": "Localizations", "Create": true, "Update": false, "Delete": true, "Entities": [ { - "Key": "Localizations", + "Key": "profile.attributes.organisation", "Create": true, - "Update": false, - "Delete": true, - "Entities": [ - { - "Key": "profile.attributes.organisation", - "Create": true, - "Update": true, - "Delete": true - } - ] + "Update": true, + "Delete": true } ] - } + } + ] ``` In the example above you can see that the default settings as well as the specific type settings for update are disabled. @@ -103,10 +90,58 @@ But for localizations with the key `profile.attributes.organisation` the update **Note**: The key defers for the specific types e.g. for `Localization` it is a string for `User` it is a uuid. +## Entity Specific Type Configurations + +For some entities there is a specific entry type configuration in place. E.g. FederatedIdentities can be configured for a specific user. + +**Example** + +```json + "SeederConfiguration": [ + { + "Key": "Users", + "Create": true, + "Update": false, + "Delete": false, + "Entities": [ + { + "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [ + { + "Key": "FederatedIdentities", + "Create": false, + "Update": false, + "Delete": false, + "Entities": [ + { + "Key": "CX-Operator", + "Create": true, + "Update": true, + "Delete": true + } + ] + } + ] + } + ] + } + ] +``` + ## Example Configuration For further reference you can have a look at the [example appsettings](./appsettings.example.json) +## Not supported modifications + +- UserProfiles can only be updated. The deletion and creation of userProfiles isn't supported +- Clients can't be deleted since it isn't supported by the api +- IdentityProviders can't be deleted yet +- Users can't be deleted yet + ## NOTICE This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json index 1e9d1f88b2..e311a9b11b 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.example.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json @@ -39,140 +39,148 @@ "DataPaths": [ "Seeding/CX-Central-realm.json" ], - "SeederConfiguration": { - "Key": "Default", - "Create": true, - "Update": false, - "Delete": true, - "Entities":[ - { - "Key": "Roles", - "Create": false, - "Update": false, - "Delete": false, - "Entities": [] - }, - { - "Key": "Localizations", - "Create": true, - "Update": false, - "Delete": true, - "Entities": [ - { - "Key": "profile.attributes.organisation", - "Create": true, - "Update": true, - "Delete": true - } - ] - }, - { - "Key": "UserProfile", - "Create": false, - "Update": true, - "Delete": false, - "Entities": [] - }, - { - "Key": "ClientScopes", - "Create": true, - "Update": true, - "Delete": false, - "Entities": [] - }, - { - "Key": "Clients", - "Create": true, - "Update": true, - "Delete": false, - "Entities": [] - }, - { - "Key": "IdentityProviders", - "Create": true, - "Update": true, - "Delete": false, - "Entities": [] - }, - { - "Key": "IdentityProviderMappers", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "Users", - "Create": true, - "Update": false, - "Delete": false, - "Entities": [ - { - "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", - "Create": true, - "Update": true, - "Delete": false - } - ] - }, - { - "Key": "FederatedIdentities", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "ClientScopeMappers", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "ProtocolMappers", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "AuthenticationFlows", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "AuthenticationFlowExecution", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "ClientProtocolMappers", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "ClientRoles", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - }, - { - "Key": "AuthenticatorConfig", - "Create": true, - "Update": true, - "Delete": true, - "Entities": [] - } - ] - }, + "Create": true, + "Update": false, + "Delete": true, + "SeederConfiguration": [ + { + "Key": "Roles", + "Create": false, + "Update": false, + "Delete": false, + "Entities": [] + }, + { + "Key": "Localizations", + "Create": true, + "Update": false, + "Delete": true, + "Entities": [ + { + "Key": "profile.attributes.organisation", + "Create": true, + "Update": true, + "Delete": true + } + ] + }, + { + "Key": "UserProfile", + "Create": false, + "Update": true, + "Delete": false + }, + { + "Key": "ClientScopes", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "Clients", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "IdentityProviders", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "IdentityProviderMappers", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "Users", + "Create": true, + "Update": false, + "Delete": false, + "Entities": [ + { + "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", + "Create": true, + "Update": true, + "Delete": false, + "Entities": [ + { + "Key": "FederatedIdentities", + "Create": false, + "Update": false, + "Delete": false, + "Entities": [ + { + "Key": "CX-Operator", + "Create": true, + "Update": true, + "Delete": true + } + ] + } + ] + } + ] + }, + { + "Key": "FederatedIdentities", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientScopeMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlows", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticationFlowExecution", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientProtocolMappers", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "ClientRoles", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + }, + { + "Key": "AuthenticatorConfig", + "Create": true, + "Update": true, + "Delete": true, + "Entities": [] + } + ], "Clients": [ { "ClientId": "Cl1-CX-Registration", diff --git a/src/keycloak/Keycloak.Seeding/appsettings.json b/src/keycloak/Keycloak.Seeding/appsettings.json index 705ef2ae15..5cfea69925 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.json @@ -37,110 +37,107 @@ "Realm": "", "InstanceName": "", "DataPaths": [], - "SeederConfiguration": { - "Key": "Default", - "Create": true, - "Update": true, - "Delete": true, - "Entities":[ - { - "Key": "Roles", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Localizations", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "UserProfile", - "Create": false, - "Update": true, - "Delete": false - }, - { - "Key": "ClientScopes", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Clients", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "IdentityProviders", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "IdentityProviderMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Users", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "FederatedIdentities", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientScopeMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ProtocolMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticationFlows", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticationFlowExecution", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientProtocolMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientRoles", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticatorConfig", - "Create": true, - "Update": true, - "Delete": true - } - ] - } + "Create": true, + "Update": true, + "Delete": true, + "SeederConfiguration": [ + { + "Key": "Roles", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "Localizations", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "UserProfile", + "Create": false, + "Update": true, + "Delete": false + }, + { + "Key": "ClientScopes", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "Clients", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "IdentityProviders", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "IdentityProviderMappers", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "Users", + "Create": true, + "Update": true, + "Delete": false + }, + { + "Key": "FederatedIdentities", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "ClientScopeMappers", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "ProtocolMappers", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "AuthenticationFlows", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "AuthenticationFlowExecution", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "ClientProtocolMappers", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "ClientRoles", + "Create": true, + "Update": true, + "Delete": true + }, + { + "Key": "AuthenticatorConfig", + "Create": true, + "Update": true, + "Delete": true + } + ] } ] } From 906dbe0c5dac075caca0e52125e8bc621aa9cf18 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Nov 2024 11:46:02 +0100 Subject: [PATCH 05/21] chore: adjust code findings Refs: #1172 --- .../BusinessLogic/KeecloakSeeder.cs | 2 +- .../SeederConfigurationExtensions.cs | 6 +- src/keycloak/Keycloak.Seeding/README.md | 2 +- .../Keycloak.Seeding/appsettings.json | 100 +----------------- 4 files changed, 6 insertions(+), 104 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index 182bd6b092..4c21a26abe 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -62,7 +62,7 @@ public async Task Seed(CancellationToken cancellationToken) private async Task CheckAndExecuteUpdater(ConfigurationKeys configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) { - if (!seedDataHandler.Configuration.ModificationAllowed(configKey)) + if (!seedDataHandler.Configuration.IsModificationAllowed(configKey)) { return; } diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index c6ee7bfadb..031ce4248e 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -23,7 +23,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class SeederConfigurationExtensions { - public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey) + public static bool IsModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey) { if (config.SeederConfiguration != null && IsModificationAllowed(config.SeederConfiguration, configKey.ToString(), out var _)) @@ -82,7 +82,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys containingConfigKey, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) { - var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(configKey)); + var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(configKey.ToString())); if (containingEntityTypeConfig is null) { return config.ModificationAllowed(configKey, modificationType, entityKey); @@ -90,7 +90,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config if (entityKey is null) { - return containingEntityTypeConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + return containingEntityTypeConfig.ModifyAllowed(modificationType); } // If we have a configuration for a specific entry return its value diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md index d73ce808f4..af3f070f5a 100644 --- a/src/keycloak/Keycloak.Seeding/README.md +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -37,7 +37,7 @@ To be able to enable or disable the functionality for specific types the Entitie ] ``` -with this example configuration all entities would be created, updated and deleted, but for all entities that are roles the seeding wouldn't do anything. +with this example configuration all entities would be created, updated and deleted, but for all entities that are `Localization` the seeding wouldn't do anything. ### Possible Types diff --git a/src/keycloak/Keycloak.Seeding/appsettings.json b/src/keycloak/Keycloak.Seeding/appsettings.json index 5cfea69925..9d9c34c565 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.json @@ -39,105 +39,7 @@ "DataPaths": [], "Create": true, "Update": true, - "Delete": true, - "SeederConfiguration": [ - { - "Key": "Roles", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Localizations", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "UserProfile", - "Create": false, - "Update": true, - "Delete": false - }, - { - "Key": "ClientScopes", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Clients", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "IdentityProviders", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "IdentityProviderMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "Users", - "Create": true, - "Update": true, - "Delete": false - }, - { - "Key": "FederatedIdentities", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientScopeMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ProtocolMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticationFlows", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticationFlowExecution", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientProtocolMappers", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "ClientRoles", - "Create": true, - "Update": true, - "Delete": true - }, - { - "Key": "AuthenticatorConfig", - "Create": true, - "Update": true, - "Delete": true - } - ] + "Delete": true } ] } From 7edcf2208d7c4db6a5de28d424aa11c6fdab5a54 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Nov 2024 12:31:10 +0100 Subject: [PATCH 06/21] feat(seeding): fix client updater Refs: #1172 --- .../BusinessLogic/KeecloakSeeder.cs | 3 ++- .../SeederConfigurationExtensions.cs | 8 +++--- .../Models/SeederConfiguration.cs | 2 +- .../Keycloak.Seeding/appsettings.example.json | 26 +++++++++---------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index 4c21a26abe..0d7989e35d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -50,7 +50,8 @@ public async Task Seed(CancellationToken cancellationToken) await CheckAndExecuteUpdater(ConfigurationKeys.UserProfile, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopes, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.Clients, realm.InstanceName, clientsUpdater.UpdateClients, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + // The clients updater must run to set the clientIds + await clientsUpdater.UpdateClients(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await CheckAndExecuteUpdater(ConfigurationKeys.ClientRoles, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await CheckAndExecuteUpdater(ConfigurationKeys.IdentityProviders, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 031ce4248e..8f2e7d2af8 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -43,7 +43,7 @@ private static bool IsModificationAllowed( foreach (var config in configurations) { - if (config.Entities != null && IsModificationAllowed(config.Entities, targetKey, out matchingConfig)) + if (config.SeederConfigurations != null && IsModificationAllowed(config.SeederConfigurations, targetKey, out matchingConfig)) { return true; } @@ -70,7 +70,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); // If we have a configuration for a specific entry return its value - var specificEntry = specificConfig?.Entities?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); + var specificEntry = specificConfig?.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); if (specificEntry != null) { return specificEntry.ModifyAllowed(modificationType); @@ -82,7 +82,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys containingConfigKey, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) { - var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.Entities?.SingleOrDefault(x => x.Key.Equals(configKey.ToString())); + var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString())); if (containingEntityTypeConfig is null) { return config.ModificationAllowed(configKey, modificationType, entityKey); @@ -94,7 +94,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config } // If we have a configuration for a specific entry return its value - var entity = containingEntityTypeConfig.Entities?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); + var entity = containingEntityTypeConfig.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(configKey, modificationType, entityKey); } diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs index fb2f3c3c62..f56f24d067 100644 --- a/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs +++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs @@ -29,5 +29,5 @@ public class SeederConfiguration public bool Delete { get; set; } [DistinctValues] - public IEnumerable? Entities { get; set; } + public IEnumerable? SeederConfigurations { get; set; } } diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json index e311a9b11b..7531b38d3e 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.example.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json @@ -48,14 +48,14 @@ "Create": false, "Update": false, "Delete": false, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "Localizations", "Create": true, "Update": false, "Delete": true, - "Entities": [ + "SeederConfigurations": [ { "Key": "profile.attributes.organisation", "Create": true, @@ -99,19 +99,19 @@ "Create": true, "Update": false, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", "Create": true, "Update": true, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "FederatedIdentities", "Create": false, "Update": false, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "CX-Operator", "Create": true, @@ -129,56 +129,56 @@ "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "ClientScopeMappers", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "ProtocolMappers", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "AuthenticationFlows", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "AuthenticationFlowExecution", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "ClientProtocolMappers", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "ClientRoles", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] }, { "Key": "AuthenticatorConfig", "Create": true, "Update": true, "Delete": true, - "Entities": [] + "SeederConfigurations": [] } ], "Clients": [ From 210bfb3a5473130e1b0661a61f93ca17f3366617 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Nov 2024 12:32:41 +0100 Subject: [PATCH 07/21] chore: adjust readme Refs: #1172 --- src/keycloak/Keycloak.Seeding/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md index af3f070f5a..bfca8b1db8 100644 --- a/src/keycloak/Keycloak.Seeding/README.md +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -22,7 +22,7 @@ with this the general logic to create, update, delete entries can either be enab ## Type Specific Configuration -To be able to enable or disable the functionality for specific types the Entities array in the seeder configuration can be used. +To be able to enable or disable the functionality for specific types the SeederConfigurations array in the seeder configuration can be used. **Example**: @@ -73,7 +73,7 @@ To be able to enable or disable the seeding for specific values the configuratio "Create": true, "Update": false, "Delete": true, - "Entities": [ + "SeederConfigurations": [ { "Key": "profile.attributes.organisation", "Create": true, @@ -103,19 +103,19 @@ For some entities there is a specific entry type configuration in place. E.g. Fe "Create": true, "Update": false, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", "Create": true, "Update": true, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "FederatedIdentities", "Create": false, "Update": false, "Delete": false, - "Entities": [ + "SeederConfigurations": [ { "Key": "CX-Operator", "Create": true, From 26ac07f279aab53a09f77ab3a0c1fd29eb40259d Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Nov 2024 14:53:50 +0100 Subject: [PATCH 08/21] feat(seeding): performance adjustments Refs: #1172 --- .../AuthenticationFlowsUpdater.cs | 73 ++++++++++--------- .../BusinessLogic/ClientScopeMapperUpdater.cs | 8 +- .../BusinessLogic/ClientScopesUpdater.cs | 47 ++++++------ .../BusinessLogic/ClientsUpdater.cs | 45 ++++++------ .../BusinessLogic/ISeedDataHandler.cs | 3 +- .../BusinessLogic/IdentityProvidersUpdater.cs | 24 +++--- .../BusinessLogic/LocalizationsUpdater.cs | 33 ++++----- .../BusinessLogic/RolesUpdater.cs | 36 ++++----- .../BusinessLogic/SeedDataHandler.cs | 15 ++++ .../BusinessLogic/UserProfileUpdater.cs | 4 +- .../BusinessLogic/UsersUpdater.cs | 27 +++---- .../SeederConfigurationExtensions.cs | 22 +++--- .../Models/KeycloakSeederConfigModel.cs | 25 +++++++ .../Keycloak.Seeding/appsettings.example.json | 6 +- 14 files changed, 213 insertions(+), 155 deletions(-) create mode 100644 src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs index 233b5b68ab..bddc3393b5 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -50,17 +50,20 @@ public async Task UpdateAuthenticationFlows(CancellationToken cancellationToken) var flows = await keycloak.GetAuthenticationFlowsAsync(_realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedFlows = seedDataHandler.TopLevelCustomAuthenticationFlows; var topLevelCustomFlows = flows.Where(flow => !(flow.BuiltIn ?? false) && (flow.TopLevel ?? false)); - var seederConfig = seedDataHandler.Configuration; + var seederConfiguration = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticationFlows); + var authFlowExecutionConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticationFlowExecution); + var authenticatorConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticatorConfig); - await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfiguration, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfiguration, authenticatorConfig, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfiguration, authenticatorConfig, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var delete in topLevelCustomFlows.ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Delete, x.Alias))) + foreach (var delete in topLevelCustomFlows + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Alias)) + .ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias)) { if (delete.Id == null) @@ -69,24 +72,25 @@ private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task AddMissingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel authenticatorConfig, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { - foreach (var addFlow in seedFlows.ExceptBy(topLevelCustomFlows.Select(x => x.Alias), x => x.Alias)) + foreach (var addFlow in seedFlows + .ExceptBy(topLevelCustomFlows.Select(x => x.Alias), x => x.Alias)) { if (addFlow.Alias == null) throw new ConflictException($"authenticationFlow.Alias is null {addFlow.Id} {addFlow.Description}"); if (addFlow.BuiltIn ?? false) throw new ConflictException($"authenticationFlow.buildIn is true. flow cannot be added: {addFlow.Alias}"); - if (seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Create, addFlow.Alias)) + if (seederConfig.ModificationAllowed(ModificationType.Create, addFlow.Alias)) { await keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAuthenticationFlowExecutions(addFlow.Alias, seederConfig, cancellationToken); + await UpdateAuthenticationFlowExecutions(addFlow.Alias, authenticatorConfig, authFlowExecutionConfig, cancellationToken); } } - private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel authenticatorConfig, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { foreach (var (flow, seed) in topLevelCustomFlows .Join( @@ -94,7 +98,7 @@ private async Task UpdateExistingAuthenticationFlows(IEnumerable x.Alias, x => x.Alias, (flow, seed) => (Flow: flow, Seed: seed)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlows, ModificationType.Update, x.Flow.Alias))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Flow.Alias))) { if (flow.Id == null) throw new ConflictException($"authenticationFlow.id is null {flow.Alias} {flow.Description}"); @@ -105,7 +109,7 @@ private async Task UpdateExistingAuthenticationFlows(IEnumerable executions, IEnumerable updateExecutions) => @@ -149,22 +153,22 @@ private bool CompareStructureRecursive(IReadOnlyList executions, (x.Node.Execution.AuthenticationFlow ?? false) == (x.Update.AuthenticatorFlow ?? false) && (!(x.Node.Execution.AuthenticationFlow ?? false) || CompareStructureRecursive(x.Node.Children, seedDataHandler.GetAuthenticationExecutions(x.Update.FlowAlias)))); - private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { - foreach (var executionNode in executionNodes.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Delete, x.Execution.Id))) + foreach (var executionNode in executionNodes.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Delete, x.Execution.Id))) { if (executionNode.Execution.AuthenticationFlow ?? false) { - await DeleteExecutionsRecursive(executionNode.Children, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await DeleteExecutionsRecursive(executionNode.Children, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } await keycloak.DeleteAuthenticationExecutionAsync(_realm, executionNode.Execution.Id ?? throw new ConflictException("authenticationFlow.Id is null"), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { - foreach (var execution in seedExecutions.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Delete, x.FlowAlias))) + foreach (var execution in seedExecutions.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Delete, x.FlowAlias))) { await (execution.AuthenticatorFlow switch { @@ -176,16 +180,16 @@ private async Task AddExecutionsRecursive(string? alias, IEnumerable executionNodes, IEnumerable seedExecutions, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task UpdateExecutionsRecursive(string alias, IReadOnlyCollection executionNodes, IEnumerable seedExecutions, KeycloakSeederConfigModel authenticatorConfig, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { if (executionNodes.Count != seedExecutions.Count()) throw new ArgumentException("number of elements in executionNodes doesn't match seedData"); - foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Update, x.First.Execution.Id))) + foreach (var (executionNode, update) in executionNodes.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Update, x.Execution.Id)).Zip(seedExecutions)) { if ((executionNode.Execution.AuthenticationFlow ?? false) != (update.AuthenticatorFlow ?? false)) throw new ArgumentException("execution.AuthenticatorFlow doesn't match seedData"); @@ -227,14 +231,15 @@ await UpdateExecutionsRecursive( update.FlowAlias!, executionNode.Children, executions, - seederConfig, + authenticatorConfig, + authFlowExecutionConfig, ct).ConfigureAwait(ConfigureAwaitOptions.None); } async Task UpdateAuthenticationExecution(ExecutionNode executionNode, AuthenticationExecutionModel update, CancellationToken ct) { - var (isEqual, authenticatorConfig) = await CompareExecutions(executionNode.Execution, update, ct).ConfigureAwait(ConfigureAwaitOptions.None); - if (!isEqual && seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticationFlowExecution, ModificationType.Update, executionNode.Execution.Id)) + var (isEqual, config) = await CompareExecutions(executionNode.Execution, update, ct).ConfigureAwait(ConfigureAwaitOptions.None); + if (!isEqual && authFlowExecutionConfig.ModificationAllowed(ModificationType.Update, executionNode.Execution.Id)) { await keycloak.UpdateAuthenticationFlowExecutionsAsync( _realm, @@ -252,19 +257,19 @@ await keycloak.UpdateAuthenticationFlowExecutionsAsync( }, ct).ConfigureAwait(ConfigureAwaitOptions.None); - await UpdateAuthenticatorConfig(executionNode.Execution, update, authenticatorConfig, seederConfig, ct).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAuthenticatorConfig(executionNode.Execution, update, config, authenticatorConfig, ct).ConfigureAwait(ConfigureAwaitOptions.None); } } } - private async Task UpdateAuthenticatorConfig(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, AuthenticatorConfig? config, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private async Task UpdateAuthenticatorConfig(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, AuthenticatorConfig? config, KeycloakSeederConfigModel authenticatorConfig, CancellationToken cancellationToken) { switch (execution.AuthenticationConfig, update.AuthenticatorConfig) { case (null, null): break; - case (null, var _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Create, update.AuthenticatorConfig): + case (null, var _) when authenticatorConfig.ModificationAllowed(ModificationType.Create, update.AuthenticatorConfig): await keycloak.CreateAuthenticationExecutionConfigurationAsync( _realm, execution.Id!, @@ -276,14 +281,14 @@ await keycloak.CreateAuthenticationExecutionConfigurationAsync( cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); break; - case (var _, null) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Delete, update.AuthenticatorConfig): + case (var _, null) when authenticatorConfig.ModificationAllowed(ModificationType.Delete, update.AuthenticatorConfig): await keycloak.DeleteAuthenticatorConfigurationAsync( _realm, execution.AuthenticationConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); break; - case var (_, _) when seederConfig.ModificationAllowed(ConfigurationKeys.AuthenticatorConfig, ModificationType.Update, update.AuthenticatorConfig): + case var (_, _) when authenticatorConfig.ModificationAllowed(ModificationType.Update, update.AuthenticatorConfig): var updateConfig = seedDataHandler.GetAuthenticatorConfig(update.AuthenticatorConfig); if (config == null) throw new UnexpectedConditionException("authenticatorConfig is null"); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs index 4b4c6021e9..7db91ecf43 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs @@ -34,7 +34,7 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken { var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); var clients = await keycloak.GetClientsAsync(realm, null, true, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); foreach (var (clientName, mappingModels) in seedDataHandler.ClientScopeMappings) @@ -60,15 +60,15 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken } } - private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { await updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Create, x.Name)) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) .IfAnyAwait(rolesToAdd => keycloak.AddClientRolesScopeMappingToClientAsync(realm, clientScopeId, clientId, rolesToAdd, cancellationToken)).ConfigureAwait(false); await roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x.Name)) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) .IfAnyAwait(rolesToDelete => keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index 2f9e8eedb5..aa61ff9f49 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -34,19 +34,19 @@ public async Task UpdateClientScopes(string instanceName, CancellationToken canc { var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedClientScopes = seedDataHandler.ClientScopes; - await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecute(ConfigurationKeys.ClientScopes, ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ModificationType.Delete, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, RemoveObsoleteClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ModificationType.Create, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, CreateMissingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecute(ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task CheckAndExecute(ConfigurationKeys configKey, ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakRealmSettings, CancellationToken, Task> executeLogic) + private static async Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakSeederConfigModel, CancellationToken, Task> executeLogic) { - if (!seederConfig.ModificationAllowed(configKey, modificationType)) + if (!seederConfig.ModificationAllowed(modificationType)) { return; } @@ -54,10 +54,11 @@ private static async Task CheckAndExecute(ConfigurationKeys configKey, Modificat await executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x.Name))) + foreach (var deleteScope in clientScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) + .ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name)) { await keycloak.DeleteClientScopeAsync( realm, @@ -66,16 +67,16 @@ await keycloak.DeleteClientScopeAsync( } } - private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Create, x.Name))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name))) { await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (clientScope, update) in clientScopes .Join( @@ -83,13 +84,13 @@ private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, st x => x.Name, x => x.Name, (clientScope, update) => (ClientScope: clientScope, Update: update)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x.Update.Name))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Update.Name))) { await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { if (clientScope.Id == null) throw new ConflictException($"clientScope.Id is null: {clientScope.Name}"); @@ -114,10 +115,11 @@ await keycloak.UpdateClientScopeAsync( await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Name, clientScope.Id, mappers, updateMappers, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var mapper in mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Delete, x.Name))) + foreach (var mapper in mappers + .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Delete, x.Name)) + .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name)) { await keycloak.DeleteProtocolMapperAsync( realm, @@ -127,10 +129,11 @@ await keycloak.DeleteProtocolMapperAsync( } } - private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var update in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Create, x.Name))) + foreach (var update in updateMappers + .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Create, x.Name)) + .ExceptBy(mappers.Select(x => x.Name), x => x.Name)) { await keycloak.CreateProtocolMapperAsync( realm, @@ -140,14 +143,14 @@ await keycloak.CreateProtocolMapperAsync( } } - private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers.Join( updateMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Update, x.Update.Name))) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Update, x.Update.Name))) { await keycloak.UpdateProtocolMapperAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs index 35864779e4..636139fbcd 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -37,12 +37,13 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell { var realm = seedDataHandler.Realm; var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Clients); + var clientScopesSeederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); - return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); + return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, clientScopesSeederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); } - private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakRealmSettings seederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel clientScopesSeederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) { var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); string GetClientScopeId(string scope) => clientScopes.SingleOrDefault(x => x.Name == scope)?.Id ?? throw new ConflictException($"id of clientScope {scope} is undefined"); @@ -55,7 +56,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell var client = (await keycloak.GetClientsAsync(realm, clientId: update.ClientId, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.ClientId == update.ClientId); if (client == null) { - if (!seederConfig.ModificationAllowed(ConfigurationKeys.Clients, ModificationType.Create, update.ClientId)) + if (!seederConfig.ModificationAllowed(ModificationType.Create, update.ClientId)) { yield return null; } @@ -64,7 +65,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell } else { - if (seederConfig.ModificationAllowed(ConfigurationKeys.Clients, ModificationType.Update, update.ClientId)) + if (seederConfig.ModificationAllowed(ModificationType.Update, update.ClientId)) { await UpdateClient( keycloak, @@ -92,7 +93,7 @@ await UpdateDefaultClientScopes( client, update, GetClientScopeId, - seederConfig, + clientScopesSeederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await UpdateOptionalClientScopes( @@ -102,7 +103,7 @@ await UpdateOptionalClientScopes( client, update, GetClientScopeId, - seederConfig, + clientScopesSeederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); yield return (update.ClientId, client.Id); @@ -134,23 +135,25 @@ await keycloak.UpdateClientAsync( } } - private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { var clientProtocolMappers = client.ProtocolMappers ?? Enumerable.Empty(); var updateProtocolMappers = update.ProtocolMappers ?? Enumerable.Empty(); if (client.ClientId == null) throw new ConflictException("client.ClientId must never be null"); - foreach (var mapperId in clientProtocolMappers.ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Delete, x.Name)) - .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}"))) + foreach (var mapperId in clientProtocolMappers + .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Delete, x.Name)) + .ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name) + .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}"))) { await keycloak.DeleteClientProtocolMapperAsync(realm, clientId, mapperId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var mapper in updateProtocolMappers.ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name) - .Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Create, x.Name))) + foreach (var mapper in updateProtocolMappers + .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Create, x.Name)) + .ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name) + .Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x))) { await keycloak.CreateClientProtocolMapperAsync(realm, clientId, mapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -161,7 +164,7 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.Clients, client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Update, x.Update.Id)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Update, x.Update.Id)) .Select(x => ( x.Mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Mapper.Name}"), ProtocolMappersUpdater.CreateProtocolMapper(x.Mapper.Id, x.Update)))) @@ -170,41 +173,41 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s } } - private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { var optionalScopes = client.OptionalClientScopes ?? Enumerable.Empty(); var updateScopes = update.OptionalClientScopes ?? Enumerable.Empty(); foreach (var scopeId in optionalScopes.Except(updateScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x))) { await keycloak.DeleteOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } foreach (var scopeId in updateScopes.Except(optionalScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x))) { await keycloak.UpdateOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, string realm, string idOfClient, Client client, ClientModel update, Func getClientScopeId, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { var defaultScopes = client.DefaultClientScopes ?? Enumerable.Empty(); var updateScopes = update.DefaultClientScopes ?? Enumerable.Empty(); foreach (var scopeId in defaultScopes.Except(updateScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Delete, x))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x))) { await keycloak.DeleteDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } foreach (var scopeId in updateScopes.Except(defaultScopes) .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientScopes, ModificationType.Update, x))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x))) { await keycloak.UpdateDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index 30e3bf8b88..727d63a020 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -26,7 +26,6 @@ public interface ISeedDataHandler Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken); string Realm { get; } - KeycloakRealmSettings Configuration { get; } KeycloakRealm KeycloakRealm { get; } @@ -52,6 +51,7 @@ public interface ISeedDataHandler IEnumerable<(string ProviderType, ComponentModel ComponentModel)> RealmComponents { get; } IEnumerable<(string Locale, IEnumerable> Translations)> RealmLocalizations { get; } + KeycloakRealmSettings Configuration { get; } Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds); @@ -62,4 +62,5 @@ public interface ISeedDataHandler IEnumerable GetAuthenticationExecutions(string? alias); AuthenticatorConfigModel GetAuthenticatorConfig(string? alias); + KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKeys configKey); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs index 2d7e104de5..281b1311b5 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -35,7 +35,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.IdentityProviders); foreach (var updateIdentityProvider in seedDataHandler.IdentityProviders) { @@ -45,7 +45,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat try { var identityProvider = await keycloak.GetIdentityProviderAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, ModificationType.Update, updateIdentityProvider.Alias)) + if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ModificationType.Update, updateIdentityProvider.Alias)) { UpdateIdentityProvider(identityProvider, updateIdentityProvider); await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -53,7 +53,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat } catch (KeycloakEntityNotFoundException) { - if (seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, ModificationType.Create, updateIdentityProvider.Alias)) + if (seederConfig.ModificationAllowed(ModificationType.Create, updateIdentityProvider.Alias)) { var identityProvider = new IdentityProvider(); UpdateIdentityProvider(identityProvider, updateIdentityProvider); @@ -70,10 +70,11 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat } } - private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Create, x.Id))) + foreach (var mapper in updateMappers + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Create, x.Id)) + .ExceptBy(mappers.Select(x => x.Name), x => x.Name)) { await keycloak.AddIdentityProviderMapperAsync( realm, @@ -89,7 +90,7 @@ await keycloak.AddIdentityProviderMapperAsync( } } - private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers .Join( @@ -97,7 +98,7 @@ private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient k x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Update, x.Update.Id))) + .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Update, x.Update.Id))) { await keycloak.UpdateIdentityProviderMapperAsync( realm, @@ -108,10 +109,11 @@ await keycloak.UpdateIdentityProviderMapperAsync( } } - private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.IdentityProviders, alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Delete, x.Id)) + if (mappers + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Delete, x.Id)) + .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) .IfAny(async deleteMappers => { foreach (var mapper in deleteMappers) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs index e4767f5cac..51e9203588 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs @@ -32,14 +32,14 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Localizations); var localizations = await keycloak.GetLocaleAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmLocalizations = seedDataHandler.RealmLocalizations; await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var deleteTranslation in - localizations.ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Delete, x))) + foreach (var deleteTranslation in localizations + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x)) + .ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale)) { await keycloak.DeleteLocaleAsync(realm, deleteTranslation, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -47,7 +47,7 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, string realm, IEnumerable locales, IEnumerable<(string Locale, IEnumerable> Translations)> translations, - KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { if (!await locales .Join( @@ -72,12 +72,11 @@ private static async Task UpdateLocaleTranslations(KeycloakClient keycloak, stri private static async Task DeleteLocales(KeycloakClient keycloak, string realm, IEnumerable> localizations, (string Locale, IEnumerable> Translations) update, string locale, - KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var deleteTranslation in - localizations.ExceptBy(update.Translations.Select(t => t.Key), - l => l.Key) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Delete, x.Key))) + foreach (var deleteTranslation in localizations + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Key)) + .ExceptBy(update.Translations.Select(t => t.Key), l => l.Key)) { await keycloak.DeleteLocaleAsync(realm, locale, deleteTranslation.Key, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -85,11 +84,11 @@ private static async Task DeleteLocales(KeycloakClient keycloak, string realm, private static async Task UpdateLocales(KeycloakClient keycloak, string realm, (string Locale, IEnumerable> Translations) update, IEnumerable> localizations, string locale, - KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var missingTranslation in update.Translations.ExceptBy(localizations.Select(loc => loc.Key), - locModel => locModel.Key) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Update, x.Key))) + foreach (var missingTranslation in update.Translations + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Key)) + .ExceptBy(localizations.Select(loc => loc.Key), locModel => locModel.Key)) { await keycloak.UpdateLocaleAsync(realm, locale, missingTranslation.Key, missingTranslation.Value, cancellationToken).ConfigureAwait(false); } @@ -100,16 +99,16 @@ private static async Task UpdateLocales(KeycloakClient keycloak, string realm, l => l.Key, trans => trans.Key, (l, trans) => (Key: l.Key, Update: trans)) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Update, x.Key))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Key))) { await keycloak.UpdateLocaleAsync(realm, locale, updateTranslation.Key, updateTranslation.Update.Value, cancellationToken).ConfigureAwait(false); } } private static async Task AddLocales(KeycloakClient keycloak, string realm, IEnumerable<(string Locale, IEnumerable> Translations)> translations, - KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value))).Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Localizations, ModificationType.Create, x.Key))) + foreach (var translation in translations.SelectMany(x => x.Translations.Select(t => (x.Locale, t.Key, t.Value))).Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Key))) { await keycloak.UpdateLocaleAsync(realm, translation.Locale, translation.Key, translation.Value, cancellationToken).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index f97b988c78..0066439577 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -34,20 +34,21 @@ public async Task UpdateClientRoles(string keycloakInstanceName, CancellationTok { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientRoles); foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { var id = seedDataHandler.GetIdOfClient(clientId); var roles = await keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - foreach (var newRole in updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.ClientRoles, ModificationType.Create, x.Name))) + foreach (var newRole in updateRoles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) { await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, ConfigurationKeys.ClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } @@ -55,20 +56,21 @@ public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToke { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Roles); var roles = await keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmRoles = seedDataHandler.RealmRoles; - foreach (var newRole in updateRealmRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Create, x.Name))) + foreach (var newRole in updateRealmRoles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) { await keycloak.CreateRoleAsync(realm, CreateRole(newRole), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, ConfigurationKeys.Roles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakRealmSettings seederConfig, ConfigurationKeys configKey, CancellationToken cancellationToken) + private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (role, update) in roles.Join( @@ -76,7 +78,7 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r role => role.Name, roleModel => roleModel.Name, (role, roleModel) => (Role: role, Update: roleModel)) - .Where(x => seederConfig.ModificationAllowed(configKey, ModificationType.Update, x.Role.Name))) + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Role.Name))) { if (!CompareRole(role, update)) { @@ -89,9 +91,9 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r } } - foreach (var deleteRole in - roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) - .Where(x => seederConfig.ModificationAllowed(configKey, ModificationType.Delete, x.Name))) + foreach (var deleteRole in roles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) + .ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name)) { if (deleteRole.Id == null) throw new ConflictException($"role id must not be null: {deleteRole.Name}"); @@ -104,7 +106,7 @@ public async Task UpdateCompositeRoles(string keycloakInstanceName, Cancellation { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Roles); foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { @@ -164,8 +166,8 @@ async Task RemoveAddCompositeRolesInner( Func joinUpdateKey, Func> getRoleByName) { - var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Update, x.Name)); - var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); + var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ModificationType.Update, x.Name)); + var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); foreach (var remove in removeComposites) { @@ -200,7 +202,7 @@ async Task RemoveAddCompositeRolesInner( .SelectAwait(x => getRoleByName(x)) .ToListAsync(cancellationToken) .ConfigureAwait(false); - await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Roles, ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None); + await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None); } } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 0d9a65db67..d1d5f85062 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -35,6 +35,8 @@ public class SeedDataHandler : ISeedDataHandler PropertyNameCaseInsensitive = false }; + private readonly IDictionary _specificConfigurations = new Dictionary(); + private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; private KeycloakRealmSettings? _realmConfiguration; @@ -149,6 +151,7 @@ public async Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string { clientIds[clientId] = id; } + _idOfClients = clientIds.ToImmutableDictionary(); } @@ -164,4 +167,16 @@ public IEnumerable GetAuthenticationExecutions(str public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) => _keycloakRealm?.AuthenticatorConfig?.SingleOrDefault(x => x.Alias == (alias ?? throw new ConflictException("alias is null"))) ?? throw new ConflictException($"authenticatorConfig {alias} does not exist"); + + public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKeys configKey) + { + if (_specificConfigurations.TryGetValue(configKey, out var specificConfiguration)) + { + return new KeycloakSeederConfigModel(Configuration, specificConfiguration); + } + + specificConfiguration = _realmConfiguration?.SeederConfiguration?.SingleOrDefault(); + _specificConfigurations.Add(configKey, specificConfiguration); + return new KeycloakSeederConfigModel(Configuration, specificConfiguration); + } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs index d78e6ba40f..5bf8f15293 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs @@ -44,8 +44,8 @@ public async Task UpdateUserProfile(string keycloakInstanceName, CancellationTok var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; var userProfiles = seedDataHandler.RealmComponents.Where(x => x.ProviderType == UserProfileType); - var defaultConfig = seedDataHandler.Configuration; - if (defaultConfig.ModificationAllowed(ConfigurationKeys.UserProfile, ModificationType.Update)) + var defaultConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.UserProfile); + if (defaultConfig.ModificationAllowed(ModificationType.Update)) { return; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs index dd24ff98ee..c9f0f78b30 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -37,7 +37,7 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can var realm = seedDataHandler.Realm; var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var clientsDictionary = seedDataHandler.ClientsDictionary; - var seederConfig = seedDataHandler.Configuration; + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Users); foreach (var seedUser in seedDataHandler.Users) { @@ -46,7 +46,7 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); - if (user == null && seederConfig.ModificationAllowed(ConfigurationKeys.Users, ModificationType.Create, seedUser.Username)) + if (user == null && seederConfig.ModificationAllowed(ModificationType.Create, seedUser.Username)) { var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) @@ -68,7 +68,7 @@ await UpdateUser( } } - private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateUser(KeycloakClient keycloak, string realm, User user, UserModel seedUser, IReadOnlyDictionary clientsDictionary, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { if (user.Id == null) throw new ConflictException($"user.Id must not be null: userName {seedUser.Username}"); @@ -76,7 +76,7 @@ private static async Task UpdateUser(KeycloakClient keycloak, string realm, User if (user.UserName == null) throw new ConflictException($"user.UserName must not be null: userName {seedUser.Username}"); - if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ConfigurationKeys.Users, ModificationType.Update, user.UserName)) + if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ModificationType.Update, user.UserName)) { await keycloak.UpdateUserAsync( realm, @@ -210,7 +210,7 @@ private static bool CompareFederatedIdentity(FederatedIdentity identity, Federat identity.UserId == update.UserId && identity.UserName == update.UserName; - private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { var identities = await keycloak.GetUserSocialLoginsAsync(realm, userId).ConfigureAwait(ConfigureAwaitOptions.None); await DeleteObsoleteFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -218,11 +218,11 @@ private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, str await UpdateExistingFederatedIdentities(keycloak, realm, username, userId, identities, updates, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var identity in identities - .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Delete, x.IdentityProvider))) + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Delete, x.IdentityProvider)) + .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider)) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, @@ -232,10 +232,11 @@ await keycloak.RemoveUserSocialLoginProviderAsync( } } - private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var update in updates.ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider) - .Where(x => seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Create, x.IdentityProvider))) + foreach (var update in updates + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Create, x.IdentityProvider)) + .ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider)) { await keycloak.AddUserSocialLoginProviderAsync( realm, @@ -251,7 +252,7 @@ await keycloak.AddUserSocialLoginProviderAsync( } } - private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakRealmSettings seederConfig, CancellationToken cancellationToken) + private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (identity, update) in identities .Join( @@ -259,7 +260,7 @@ private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycl x => x.IdentityProvider, x => x.IdentityProvider, (identity, update) => (Identity: identity, Update: update)) - .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(ConfigurationKeys.Users, username, ConfigurationKeys.FederatedIdentities, ModificationType.Update, x.Update.IdentityProvider))) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Update, x.Update.IdentityProvider))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 8f2e7d2af8..0be394eee4 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -17,6 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; @@ -60,14 +61,14 @@ private static bool IsModificationAllowed( return false; } - public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey, ModificationType modificationType) => - config.ModificationAllowed(configKey, modificationType, null); + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, ModificationType modificationType) => + config.ModificationAllowed(modificationType, null); - public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, ModificationType modificationType, string? entityKey) { - var specificConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)); + var (defaultConfig, specificConfig) = config; if (entityKey is null) - return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); // If we have a configuration for a specific entry return its value var specificEntry = specificConfig?.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); @@ -77,15 +78,16 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config } // If we don't have a specific value return the specific configuration value if we have one - return specificConfig?.ModifyAllowed(modificationType) ?? config.ModifyAllowed(modificationType); + return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); } - public static bool ModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys containingConfigKey, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) { - var containingEntityTypeConfig = config.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(containingConfigKey.ToString(), StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString())); + var containingEntityTypeConfig = config.SpecificConfiguration?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)); if (containingEntityTypeConfig is null) { - return config.ModificationAllowed(configKey, modificationType, entityKey); + var configModel = config with { SpecificConfiguration = config.DefaultSettings.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)) }; + return configModel.ModificationAllowed(modificationType, entityKey); } if (entityKey is null) @@ -95,7 +97,7 @@ public static bool ModificationAllowed(this KeycloakRealmSettings config, Config // If we have a configuration for a specific entry return its value var entity = containingEntityTypeConfig.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); - return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(configKey, modificationType, entityKey); + return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(modificationType, entityKey); } private static bool ModifyAllowed(this KeycloakRealmSettings configuration, ModificationType modificationType) => diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs new file mode 100644 index 0000000000..e37395c25a --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public record KeycloakSeederConfigModel( + KeycloakRealmSettings DefaultSettings, + SeederConfiguration? SpecificConfiguration +); diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json index 7531b38d3e..ab09b5bcd5 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.example.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json @@ -126,9 +126,9 @@ }, { "Key": "FederatedIdentities", - "Create": true, - "Update": true, - "Delete": true, + "Create": false, + "Update": false, + "Delete": false, "SeederConfigurations": [] }, { From ed71ac36079ae2352468c4e3035bd149bb7f50cd Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Mon, 25 Nov 2024 23:41:38 +0100 Subject: [PATCH 09/21] fix review findings --- .../AuthenticationFlowsUpdater.cs | 15 +++---- .../BusinessLogic/ClientScopeMapperUpdater.cs | 8 ++-- .../BusinessLogic/ClientScopesUpdater.cs | 42 +++++++++--------- .../BusinessLogic/ClientsUpdater.cs | 43 +++++++++++-------- .../BusinessLogic/ISeedDataHandler.cs | 2 +- .../BusinessLogic/IdentityProvidersUpdater.cs | 11 ++--- .../BusinessLogic/KeecloakSeeder.cs | 33 ++++++-------- .../BusinessLogic/LocalizationsUpdater.cs | 15 ++++--- .../BusinessLogic/RolesUpdater.cs | 19 ++++---- .../BusinessLogic/SeedDataHandler.cs | 9 ++-- .../BusinessLogic/UserProfileUpdater.cs | 2 +- .../BusinessLogic/UsersUpdater.cs | 20 ++++++--- .../SeederConfigurationExtensions.cs | 19 +++----- ...nfigurationKeys.cs => ConfigurationKey.cs} | 2 +- .../Models/KeycloakRealmSettings.cs | 2 +- 15 files changed, 124 insertions(+), 118 deletions(-) rename src/keycloak/Keycloak.Seeding/Models/{ConfigurationKeys.cs => ConfigurationKey.cs} (97%) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs index bddc3393b5..0c9dda7aa7 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -50,9 +50,9 @@ public async Task UpdateAuthenticationFlows(CancellationToken cancellationToken) var flows = await keycloak.GetAuthenticationFlowsAsync(_realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedFlows = seedDataHandler.TopLevelCustomAuthenticationFlows; var topLevelCustomFlows = flows.Where(flow => !(flow.BuiltIn ?? false) && (flow.TopLevel ?? false)); - var seederConfiguration = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticationFlows); - var authFlowExecutionConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticationFlowExecution); - var authenticatorConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.AuthenticatorConfig); + var seederConfiguration = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticationFlows); + var authFlowExecutionConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticationFlowExecution); + var authenticatorConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticatorConfig); await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfiguration, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, seederConfiguration, authenticatorConfig, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -64,7 +64,6 @@ private async Task DeleteRedundantAuthenticationFlows(IEnumerable seederConfig.ModificationAllowed(ModificationType.Delete, x.Alias)) .ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias)) - { if (delete.Id == null) throw new ConflictException($"authenticationFlow.id is null {delete.Alias} {delete.Description}"); @@ -93,12 +92,12 @@ private async Task AddMissingAuthenticationFlows(IEnumerable private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel authenticatorConfig, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { foreach (var (flow, seed) in topLevelCustomFlows + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Alias)) .Join( seedFlows, x => x.Alias, x => x.Alias, - (flow, seed) => (Flow: flow, Seed: seed)) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Flow.Alias))) + (flow, seed) => (Flow: flow, Seed: seed))) { if (flow.Id == null) throw new ConflictException($"authenticationFlow.id is null {flow.Alias} {flow.Description}"); @@ -168,7 +167,7 @@ private async Task DeleteExecutionsRecursive(IEnumerable executio private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken) { - foreach (var execution in seedExecutions.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Delete, x.FlowAlias))) + foreach (var execution in seedExecutions.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Create, x.FlowAlias))) { await (execution.AuthenticatorFlow switch { @@ -189,7 +188,7 @@ private async Task UpdateExecutionsRecursive(string alias, IReadOnlyCollection authFlowExecutionConfig.ModificationAllowed(ModificationType.Update, x.Execution.Id)).Zip(seedExecutions)) + foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions).Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Update, x.First.Execution.Id))) { if ((executionNode.Execution.AuthenticationFlow ?? false) != (update.AuthenticatorFlow ?? false)) throw new ArgumentException("execution.AuthenticatorFlow doesn't match seedData"); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs index 7db91ecf43..1593d9fb1e 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopeMapperUpdater.cs @@ -34,7 +34,7 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken { var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes); var clients = await keycloak.GetClientsAsync(realm, null, true, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); foreach (var (clientName, mappingModels) in seedDataHandler.ClientScopeMappings) @@ -62,13 +62,15 @@ public async Task UpdateClientScopeMapper(string instanceName, CancellationToken private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - await updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) + await updateRoles .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name) .IfAnyAwait(rolesToAdd => keycloak.AddClientRolesScopeMappingToClientAsync(realm, clientScopeId, clientId, rolesToAdd, cancellationToken)).ConfigureAwait(false); - await roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) + await roles .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)) + .ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name) .IfAnyAwait(rolesToDelete => keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs index aa61ff9f49..a9ecfef54d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -34,7 +34,7 @@ public async Task UpdateClientScopes(string instanceName, CancellationToken canc { var keycloak = keycloakFactory.CreateKeycloakClient(instanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes); var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var seedClientScopes = seedDataHandler.ClientScopes; @@ -44,15 +44,10 @@ public async Task UpdateClientScopes(string instanceName, CancellationToken canc await CheckAndExecute(ModificationType.Update, keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken, UpdateExistingClientScopes).ConfigureAwait(ConfigureAwaitOptions.None); } - private static async Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakSeederConfigModel, CancellationToken, Task> executeLogic) - { - if (!seederConfig.ModificationAllowed(modificationType)) - { - return; - } - - await executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - } + private static Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken, Func, IEnumerable, KeycloakSeederConfigModel, CancellationToken, Task> executeLogic) => + seederConfig.ModificationAllowed(modificationType) + ? executeLogic(keycloak, realm, clientScopes, seedClientScopes, seederConfig, cancellationToken) + : Task.CompletedTask; private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { @@ -69,8 +64,9 @@ await keycloak.DeleteClientScopeAsync( private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name))) + foreach (var addScope in seedClientScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name)) + .ExceptBy(clientScopes.Select(x => x.Name), x => x.Name)) { await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -79,12 +75,12 @@ private static async Task CreateMissingClientScopes(KeycloakClient keycloak, str private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (clientScope, update) in clientScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Name)) .Join( seedClientScopes, x => x.Name, x => x.Name, - (clientScope, update) => (ClientScope: clientScope, Update: update)) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Update.Name))) + (clientScope, update) => (ClientScope: clientScope, Update: update))) { await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -118,7 +114,7 @@ await keycloak.UpdateClientScopeAsync( private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var mapper in mappers - .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Delete, x.Name)) + .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKey.ProtocolMappers, ModificationType.Delete, x.Name)) .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name)) { await keycloak.DeleteProtocolMapperAsync( @@ -132,7 +128,7 @@ await keycloak.DeleteProtocolMapperAsync( private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var update in updateMappers - .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Create, x.Name)) + .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKey.ProtocolMappers, ModificationType.Create, x.Name)) .ExceptBy(mappers.Select(x => x.Name), x => x.Name)) { await keycloak.CreateProtocolMapperAsync( @@ -145,12 +141,14 @@ await keycloak.CreateProtocolMapperAsync( private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { - foreach (var (mapper, update) in mappers.Join( - updateMappers, - x => x.Name, - x => x.Name, - (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(clientScopeName, ConfigurationKeys.ProtocolMappers, ModificationType.Update, x.Update.Name))) + foreach (var (mapper, update) in mappers + .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKey.ProtocolMappers, ModificationType.Update, x.Name)) + .Join( + updateMappers, + x => x.Name, + x => x.Name, + (mapper, update) => (Mapper: mapper, Update: update)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update))) { await keycloak.UpdateProtocolMapperAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs index 636139fbcd..91c4172424 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -37,13 +37,13 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell { var realm = seedDataHandler.Realm; var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Clients); - var clientScopesSeederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientScopes); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Clients); + var clientScopesSeederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes); return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, clientScopesSeederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); } - private async IAsyncEnumerable<(string ClientId, string Id)?> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel clientScopesSeederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable<(string ClientId, string Id)> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel clientScopesSeederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) { var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); string GetClientScopeId(string scope) => clientScopes.SingleOrDefault(x => x.Name == scope)?.Id ?? throw new ConflictException($"id of clientScope {scope} is undefined"); @@ -58,7 +58,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell { if (!seederConfig.ModificationAllowed(ModificationType.Create, update.ClientId)) { - yield return null; + continue; } client = await CreateClient(keycloak, realm, update, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -143,7 +143,7 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s throw new ConflictException("client.ClientId must never be null"); foreach (var mapperId in clientProtocolMappers - .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Delete, x.Name)) + .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKey.ClientProtocolMapper, ModificationType.Delete, x.Name)) .ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name) .Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}"))) { @@ -151,7 +151,7 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s } foreach (var mapper in updateProtocolMappers - .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Create, x.Name)) + .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKey.ClientProtocolMapper, ModificationType.Create, x.Name)) .ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name) .Select(x => ProtocolMappersUpdater.CreateProtocolMapper(null, x))) { @@ -159,12 +159,13 @@ private static async Task UpdateClientProtocolMappers(KeycloakClient keycloak, s } foreach (var (mapperId, mapper) in clientProtocolMappers + .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKey.ClientProtocolMapper, ModificationType.Update, x.Name)) .Join( updateProtocolMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(client.ClientId, ConfigurationKeys.ClientProtocolMapper, ModificationType.Update, x.Update.Id)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update)) .Select(x => ( x.Mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Mapper.Name}"), ProtocolMappersUpdater.CreateProtocolMapper(x.Mapper.Id, x.Update)))) @@ -178,16 +179,18 @@ private static async Task UpdateOptionalClientScopes(KeycloakClient keycloak, st var optionalScopes = client.OptionalClientScopes ?? Enumerable.Empty(); var updateScopes = update.OptionalClientScopes ?? Enumerable.Empty(); - foreach (var scopeId in optionalScopes.Except(updateScopes) - .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x))) + foreach (var scopeId in optionalScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x)) + .Except(updateScopes) + .Select(getClientScopeId)) { await keycloak.DeleteOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var scopeId in updateScopes.Except(optionalScopes) - .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x))) + foreach (var scopeId in updateScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x)) + .Except(optionalScopes) + .Select(getClientScopeId)) { await keycloak.UpdateOptionalClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } @@ -198,16 +201,18 @@ private static async Task UpdateDefaultClientScopes(KeycloakClient keycloak, str var defaultScopes = client.DefaultClientScopes ?? Enumerable.Empty(); var updateScopes = update.DefaultClientScopes ?? Enumerable.Empty(); - foreach (var scopeId in defaultScopes.Except(updateScopes) - .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x))) + foreach (var scopeId in defaultScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, x)) + .Except(updateScopes) + .Select(getClientScopeId)) { await keycloak.DeleteDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } - foreach (var scopeId in updateScopes.Except(defaultScopes) - .Select(getClientScopeId) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x))) + foreach (var scopeId in updateScopes + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x)) + .Except(defaultScopes) + .Select(getClientScopeId)) { await keycloak.UpdateDefaultClientScopeAsync(realm, idOfClient, scopeId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index 727d63a020..6efe1ed6dd 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -62,5 +62,5 @@ public interface ISeedDataHandler IEnumerable GetAuthenticationExecutions(string? alias); AuthenticatorConfigModel GetAuthenticatorConfig(string? alias); - KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKeys configKey); + KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs index 281b1311b5..63cbabc6e7 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -35,7 +35,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.IdentityProviders); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.IdentityProviders); foreach (var updateIdentityProvider in seedDataHandler.IdentityProviders) { @@ -45,7 +45,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat try { var identityProvider = await keycloak.GetIdentityProviderAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - if (!CompareIdentityProvider(identityProvider, updateIdentityProvider) && seederConfig.ModificationAllowed(ModificationType.Update, updateIdentityProvider.Alias)) + if (seederConfig.ModificationAllowed(ModificationType.Update, updateIdentityProvider.Alias) && !CompareIdentityProvider(identityProvider, updateIdentityProvider)) { UpdateIdentityProvider(identityProvider, updateIdentityProvider); await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); @@ -73,7 +73,7 @@ public async Task UpdateIdentityProviders(string keycloakInstanceName, Cancellat private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var mapper in updateMappers - .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Create, x.Id)) + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Create, x.Name)) .ExceptBy(mappers.Select(x => x.Name), x => x.Name)) { await keycloak.AddIdentityProviderMapperAsync( @@ -93,12 +93,13 @@ await keycloak.AddIdentityProviderMapperAsync( private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (mapper, update) in mappers + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Update, x.Name)) .Join( updateMappers, x => x.Name, x => x.Name, (mapper, update) => (Mapper: mapper, Update: update)) - .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update) && seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Update, x.Update.Id))) + .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update))) { await keycloak.UpdateIdentityProviderMapperAsync( realm, @@ -112,7 +113,7 @@ await keycloak.UpdateIdentityProviderMapperAsync( private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { if (mappers - .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKeys.IdentityProviderMappers, ModificationType.Delete, x.Id)) + .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Delete, x.Name)) .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name) .IfAny(async deleteMappers => { diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index 0d7989e35d..d385eb3686 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -46,28 +46,23 @@ public async Task Seed(CancellationToken cancellationToken) { await seedDataHandler.Import(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); await realmUpdater.UpdateRealm(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.Localizations, realm.InstanceName, localizationsUpdater.UpdateLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.UserProfile, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopes, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.Localizations, realm.InstanceName, localizationsUpdater.UpdateLocalizations, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.UserProfile, realm.InstanceName, userProfileUpdater.UpdateUserProfile, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.Roles, realm.InstanceName, rolesUpdater.UpdateRealmRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.ClientScopes, realm.InstanceName, clientScopesUpdater.UpdateClientScopes, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); // The clients updater must run to set the clientIds await clientsUpdater.UpdateClients(realm.InstanceName, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientRoles, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.Roles, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.IdentityProviders, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.Users, realm.InstanceName, usersUpdater.UpdateUsers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.ClientScopeMappers, realm.InstanceName, clientScopeMapperUpdater.UpdateClientScopeMapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - await CheckAndExecuteUpdater(ConfigurationKeys.AuthenticationFlows, realm.InstanceName, authenticationFlowsUpdater.UpdateAuthenticationFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.ClientRoles, realm.InstanceName, rolesUpdater.UpdateClientRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.Roles, realm.InstanceName, rolesUpdater.UpdateCompositeRoles, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.IdentityProviders, realm.InstanceName, identityProvidersUpdater.UpdateIdentityProviders, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.Users, realm.InstanceName, usersUpdater.UpdateUsers, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.ClientScopeMappers, realm.InstanceName, clientScopeMapperUpdater.UpdateClientScopeMapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await CheckAndExecuteUpdater(ConfigurationKey.AuthenticationFlows, realm.InstanceName, authenticationFlowsUpdater.UpdateAuthenticationFlows, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } } - private async Task CheckAndExecuteUpdater(ConfigurationKeys configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) - { - if (!seedDataHandler.Configuration.IsModificationAllowed(configKey)) - { - return; - } - - await updaterExecution(instanceName, cancellationToken).ConfigureAwait(false); - } + private Task CheckAndExecuteUpdater(ConfigurationKey configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) => + seedDataHandler.Configuration.IsModificationAllowed(configKey) + ? updaterExecution(instanceName, cancellationToken) + : Task.CompletedTask; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs index 51e9203588..b867723bed 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/LocalizationsUpdater.cs @@ -32,7 +32,7 @@ public async Task UpdateLocalizations(string keycloakInstanceName, CancellationT { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Localizations); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Localizations); var localizations = await keycloak.GetLocaleAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmLocalizations = seedDataHandler.RealmLocalizations; @@ -94,12 +94,13 @@ private static async Task UpdateLocales(KeycloakClient keycloak, string realm, } foreach (var updateTranslation in - localizations.Join( - update.Translations, - l => l.Key, - trans => trans.Key, - (l, trans) => (Key: l.Key, Update: trans)) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Key))) + localizations + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Key)) + .Join( + update.Translations, + l => l.Key, + trans => trans.Key, + (l, trans) => (Key: l.Key, Update: trans))) { await keycloak.UpdateLocaleAsync(realm, locale, updateTranslation.Key, updateTranslation.Update.Value, cancellationToken).ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index 0066439577..b84820522a 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -34,7 +34,7 @@ public async Task UpdateClientRoles(string keycloakInstanceName, CancellationTok { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.ClientRoles); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientRoles); foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { @@ -56,7 +56,7 @@ public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToke { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Roles); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Roles); var roles = await keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var updateRealmRoles = seedDataHandler.RealmRoles; @@ -73,12 +73,13 @@ public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToke private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (role, update) in - roles.Join( - updateRoles, - role => role.Name, - roleModel => roleModel.Name, - (role, roleModel) => (Role: role, Update: roleModel)) - .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Role.Name))) + roles + .Where(x => seederConfig.ModificationAllowed(ModificationType.Update, x.Name)) + .Join( + updateRoles, + role => role.Name, + roleModel => roleModel.Name, + (role, roleModel) => (Role: role, Update: roleModel))) { if (!CompareRole(role, update)) { @@ -106,7 +107,7 @@ public async Task UpdateCompositeRoles(string keycloakInstanceName, Cancellation { var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Roles); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Roles); foreach (var (clientId, updateRoles) in seedDataHandler.ClientRoles) { diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index d1d5f85062..4e8197800e 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -20,6 +20,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Collections.Immutable; using System.Text.Json; @@ -35,7 +36,7 @@ public class SeedDataHandler : ISeedDataHandler PropertyNameCaseInsensitive = false }; - private readonly IDictionary _specificConfigurations = new Dictionary(); + private readonly IDictionary _specificConfigurations = new Dictionary(); private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; @@ -168,14 +169,16 @@ public IEnumerable GetAuthenticationExecutions(str public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) => _keycloakRealm?.AuthenticatorConfig?.SingleOrDefault(x => x.Alias == (alias ?? throw new ConflictException("alias is null"))) ?? throw new ConflictException($"authenticatorConfig {alias} does not exist"); - public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKeys configKey) + public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey) { if (_specificConfigurations.TryGetValue(configKey, out var specificConfiguration)) { return new KeycloakSeederConfigModel(Configuration, specificConfiguration); } - specificConfiguration = _realmConfiguration?.SeederConfiguration?.SingleOrDefault(); + var configKeyString = configKey.ToString(); + + specificConfiguration = _realmConfiguration?.SeederConfigurations?.SingleOrDefault(x => x.Key == configKeyString); _specificConfigurations.Add(configKey, specificConfiguration); return new KeycloakSeederConfigModel(Configuration, specificConfiguration); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs index 5bf8f15293..78e9ae449d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs @@ -44,7 +44,7 @@ public async Task UpdateUserProfile(string keycloakInstanceName, CancellationTok var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var realm = seedDataHandler.Realm; var userProfiles = seedDataHandler.RealmComponents.Where(x => x.ProviderType == UserProfileType); - var defaultConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.UserProfile); + var defaultConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.UserProfile); if (defaultConfig.ModificationAllowed(ModificationType.Update)) { return; diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs index c9f0f78b30..af712e3304 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -37,16 +37,23 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can var realm = seedDataHandler.Realm; var keycloak = keycloakFactory.CreateKeycloakClient(keycloakInstanceName); var clientsDictionary = seedDataHandler.ClientsDictionary; - var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKeys.Users); + var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Users); foreach (var seedUser in seedDataHandler.Users) { if (seedUser.Username == null) throw new ConflictException($"username must not be null {seedUser.Id}"); + var createAllowed = seederConfig.ModificationAllowed(ModificationType.Create, seedUser.Username); + var updateAllowed = seederConfig.ModificationAllowed(ModificationType.Update, seedUser.Username); + if (!createAllowed && !updateAllowed) + { + continue; + } + var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).SingleOrDefault(x => x.UserName == seedUser.Username); - if (user == null && seederConfig.ModificationAllowed(ModificationType.Create, seedUser.Username)) + if (user == null && createAllowed) { var result = await keycloak.RealmPartialImportAsync(realm, CreatePartialImportUser(seedUser), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); if (result.Overwritten != 0 || result.Added != 1 || result.Skipped != 0) @@ -54,7 +61,7 @@ public async Task UpdateUsers(string keycloakInstanceName, CancellationToken can throw new ConflictException($"PartialImport failed to add user id: {seedUser.Id}, userName: {seedUser.Username}"); } } - else if (user != null) + else if (user != null && updateAllowed) { await UpdateUser( keycloak, @@ -221,7 +228,7 @@ private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, str private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var identity in identities - .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Delete, x.IdentityProvider)) + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Delete, x.IdentityProvider)) .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider)) { await keycloak.RemoveUserSocialLoginProviderAsync( @@ -235,7 +242,7 @@ await keycloak.RemoveUserSocialLoginProviderAsync( private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var update in updates - .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Create, x.IdentityProvider)) + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Create, x.IdentityProvider)) .ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider)) { await keycloak.AddUserSocialLoginProviderAsync( @@ -255,12 +262,13 @@ await keycloak.AddUserSocialLoginProviderAsync( private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string username, string userId, IEnumerable identities, IEnumerable updates, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken) { foreach (var (identity, update) in identities + .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Update, x.IdentityProvider)) .Join( updates, x => x.IdentityProvider, x => x.IdentityProvider, (identity, update) => (Identity: identity, Update: update)) - .Where(x => !CompareFederatedIdentity(x.Identity, x.Update) && seederConfig.ModificationAllowed(username, ConfigurationKeys.FederatedIdentities, ModificationType.Update, x.Update.IdentityProvider))) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update))) { await keycloak.RemoveUserSocialLoginProviderAsync( realm, diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 0be394eee4..b37d8f60e1 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -17,23 +17,16 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class SeederConfigurationExtensions { - public static bool IsModificationAllowed(this KeycloakRealmSettings config, ConfigurationKeys configKey) - { - if (config.SeederConfiguration != null && - IsModificationAllowed(config.SeederConfiguration, configKey.ToString(), out var _)) - { - return true; - } - - return config.Create || config.Update || config.Delete; - } + public static bool IsModificationAllowed(this KeycloakRealmSettings config, ConfigurationKey configKey) => + config.Create || config.Update || config.Delete || + (config.SeederConfigurations != null && + IsModificationAllowed(config.SeederConfigurations, configKey.ToString(), out var _)); private static bool IsModificationAllowed( IEnumerable configurations, @@ -81,12 +74,12 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, Mo return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); } - public static bool ModificationAllowed(this KeycloakSeederConfigModel config, string containingEntityKey, ConfigurationKeys configKey, ModificationType modificationType, string? entityKey) + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, string containingEntityKey, ConfigurationKey configKey, ModificationType modificationType, string? entityKey) { var containingEntityTypeConfig = config.SpecificConfiguration?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)); if (containingEntityTypeConfig is null) { - var configModel = config with { SpecificConfiguration = config.DefaultSettings.SeederConfiguration?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)) }; + var configModel = config with { SpecificConfiguration = config.DefaultSettings.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)) }; return configModel.ModificationAllowed(modificationType, entityKey); } diff --git a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKey.cs similarity index 97% rename from src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs rename to src/keycloak/Keycloak.Seeding/Models/ConfigurationKey.cs index 11135b6238..edbc1b6aae 100644 --- a/src/keycloak/Keycloak.Seeding/Models/ConfigurationKeys.cs +++ b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKey.cs @@ -19,7 +19,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; -public enum ConfigurationKeys +public enum ConfigurationKey { Roles = 1, Localizations = 2, diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs index a5284b7c4e..d7ea980e7e 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs @@ -36,7 +36,7 @@ public class KeycloakRealmSettings public bool Update { get; set; } public bool Delete { get; set; } [DistinctValues] - public IEnumerable? SeederConfiguration { get; set; } + public IEnumerable? SeederConfigurations { get; set; } public string? Id { get; set; } public string? DisplayName { get; set; } public string? DisplayNameHtml { get; set; } From 7b1ef437769e9722b84a0b2a5ef285cc990e8ff1 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 26 Nov 2024 09:20:55 +0100 Subject: [PATCH 10/21] fix build Refs: #1172 --- src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs index 91c4172424..9e7b9a11c6 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -40,7 +40,7 @@ public Task UpdateClients(string keycloakInstanceName, CancellationToken cancell var seederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.Clients); var clientScopesSeederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes); - return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, clientScopesSeederConfig, cancellationToken).Where(x => x != null).Select(x => x!.Value)); + return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, clientScopesSeederConfig, cancellationToken)); } private async IAsyncEnumerable<(string ClientId, string Id)> UpdateClientsInternal(KeycloakClient keycloak, string realm, KeycloakSeederConfigModel seederConfig, KeycloakSeederConfigModel clientScopesSeederConfig, [EnumeratorCancellation] CancellationToken cancellationToken) From b45b1b212cdfd52f0d6223be2799f13137f927f5 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 26 Nov 2024 10:51:50 +0100 Subject: [PATCH 11/21] feat: adjust configuration handling Refs: #1172 --- .../BusinessLogic/ISeedDataHandler.cs | 2 +- .../BusinessLogic/SeedDataHandler.cs | 17 +++++++++++------ .../SeederConfigurationExtensions.cs | 18 +++++------------- .../Models/KeycloakRealmSettings.cs | 2 +- ...s.cs => KeycloakRealmSettingsExtensions.cs} | 2 +- .../Models/KeycloakSeederConfigModel.cs | 2 +- src/keycloak/Keycloak.Seeding/README.md | 8 ++++---- .../Keycloak.Seeding/appsettings.example.json | 2 +- 8 files changed, 25 insertions(+), 28 deletions(-) rename src/keycloak/Keycloak.Seeding/Models/{KeycloakRealmSettingsExtentions.cs => KeycloakRealmSettingsExtensions.cs} (99%) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index 6efe1ed6dd..8ef19ea64e 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -51,7 +51,7 @@ public interface ISeedDataHandler IEnumerable<(string ProviderType, ComponentModel ComponentModel)> RealmComponents { get; } IEnumerable<(string Locale, IEnumerable> Translations)> RealmLocalizations { get; } - KeycloakRealmSettings Configuration { get; } + SeederConfiguration Configuration { get; } Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds); diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 4e8197800e..45a2dfab20 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -20,7 +20,6 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; -using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Collections.Immutable; using System.Text.Json; @@ -40,7 +39,7 @@ public class SeedDataHandler : ISeedDataHandler private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; - private KeycloakRealmSettings? _realmConfiguration; + private SeederConfiguration? _defaultConfiguration; public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken) { @@ -50,7 +49,13 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken async (importRealm, path) => importRealm.Merge(await ReadJsonRealm(path, realmSettings.Realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)) .Merge(realmSettings.ToModel()); - _realmConfiguration = realmSettings; + _defaultConfiguration = new SeederConfiguration + { + Create = realmSettings.Create, + Update = realmSettings.Update, + Delete = realmSettings.Delete, + SeederConfigurations = realmSettings.SeederConfigurations + }; _idOfClients = null; } @@ -74,9 +79,9 @@ public string Realm get => _keycloakRealm?.Realm ?? throw new ConflictException("realm must not be null"); } - public KeycloakRealmSettings Configuration + public SeederConfiguration Configuration { - get => _realmConfiguration ?? throw new ConflictException("configuration must not be null"); + get => _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); } public KeycloakRealm KeycloakRealm @@ -178,7 +183,7 @@ public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey confi var configKeyString = configKey.ToString(); - specificConfiguration = _realmConfiguration?.SeederConfigurations?.SingleOrDefault(x => x.Key == configKeyString); + specificConfiguration = Configuration.SeederConfigurations?.SingleOrDefault(x => x.Key == configKeyString); _specificConfigurations.Add(configKey, specificConfiguration); return new KeycloakSeederConfigModel(Configuration, specificConfiguration); } diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index b37d8f60e1..63c71e7dc2 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -23,26 +23,27 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class SeederConfigurationExtensions { - public static bool IsModificationAllowed(this KeycloakRealmSettings config, ConfigurationKey configKey) => + public static bool IsModificationAllowed(this SeederConfiguration config, ConfigurationKey configKey) => config.Create || config.Update || config.Delete || (config.SeederConfigurations != null && - IsModificationAllowed(config.SeederConfigurations, configKey.ToString(), out var _)); + IsModificationAllowed(config.SeederConfigurations, configKey.ToString(), false, out var _)); private static bool IsModificationAllowed( IEnumerable configurations, string targetKey, + bool isKeyParentConfig, out SeederConfiguration? matchingConfig) { matchingConfig = null; foreach (var config in configurations) { - if (config.SeederConfigurations != null && IsModificationAllowed(config.SeederConfigurations, targetKey, out matchingConfig)) + if (config.SeederConfigurations != null && IsModificationAllowed(config.SeederConfigurations, targetKey, isKeyParentConfig || config.Key == targetKey, out matchingConfig)) { return true; } - if (config.Key != targetKey || config is { Create: false, Update: false, Delete: false }) + if ((config.Key != targetKey && !isKeyParentConfig) || config is { Create: false, Update: false, Delete: false }) { continue; } @@ -93,15 +94,6 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, st return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(modificationType, entityKey); } - private static bool ModifyAllowed(this KeycloakRealmSettings configuration, ModificationType modificationType) => - modificationType switch - { - ModificationType.Create => configuration.Create, - ModificationType.Update => configuration.Update, - ModificationType.Delete => configuration.Delete, - _ => throw new ArgumentOutOfRangeException(nameof(modificationType), modificationType, null) - }; - private static bool ModifyAllowed(this SeederConfiguration configuration, ModificationType modificationType) => modificationType switch { diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs index d7ea980e7e..bb4acfdf09 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs @@ -35,7 +35,7 @@ public class KeycloakRealmSettings public bool Create { get; set; } public bool Update { get; set; } public bool Delete { get; set; } - [DistinctValues] + [DistinctValues("x => x.Key")] public IEnumerable? SeederConfigurations { get; set; } public string? Id { get; set; } public string? DisplayName { get; set; } diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtentions.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtensions.cs similarity index 99% rename from src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtentions.cs rename to src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtensions.cs index 932badf53e..c5850d6857 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtentions.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettingsExtensions.cs @@ -22,7 +22,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; -public static class KeycloakRealmSettingsExtentions +public static class KeycloakRealmSettingsExtensions { public static KeycloakRealm ToModel(this KeycloakRealmSettings keycloakRealmSettings) => new() diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs index e37395c25a..ffdd624b7f 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs @@ -20,6 +20,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; public record KeycloakSeederConfigModel( - KeycloakRealmSettings DefaultSettings, + SeederConfiguration DefaultSettings, SeederConfiguration? SpecificConfiguration ); diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md index bfca8b1db8..723d42adc2 100644 --- a/src/keycloak/Keycloak.Seeding/README.md +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -14,7 +14,7 @@ In the Seeder configuration you must have one Default entry where the following "Create": true, "Update": true, "Delete": true, - "SeederConfiguration": [] + "SeederConfigurations": [] ] ``` @@ -27,7 +27,7 @@ To be able to enable or disable the functionality for specific types the SeederC **Example**: ```json - "SeederConfiguration": [ + "SeederConfigurations": [ { "Key": "Localizations", "Create": false, @@ -67,7 +67,7 @@ To be able to enable or disable the seeding for specific values the configuratio **Example** ```json - "SeederConfiguration": [ + "SeederConfigurations": [ { "Key": "Localizations", "Create": true, @@ -97,7 +97,7 @@ For some entities there is a specific entry type configuration in place. E.g. Fe **Example** ```json - "SeederConfiguration": [ + "SeederConfigurations": [ { "Key": "Users", "Create": true, diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json index ab09b5bcd5..b6193cba7f 100644 --- a/src/keycloak/Keycloak.Seeding/appsettings.example.json +++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json @@ -42,7 +42,7 @@ "Create": true, "Update": false, "Delete": true, - "SeederConfiguration": [ + "SeederConfigurations": [ { "Key": "Roles", "Create": false, From defb40dd07ed20dc64ee98df414676a8c7098a46 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 26 Nov 2024 13:39:06 +0100 Subject: [PATCH 12/21] fix: clean seederConfig dictionary for each realm Refs: #1172 --- src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 45a2dfab20..e7cef754ae 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -56,7 +56,9 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken Delete = realmSettings.Delete, SeederConfigurations = realmSettings.SeederConfigurations }; + _idOfClients = null; + _specificConfigurations.Clear(); } private static async Task ReadJsonRealm(string path, string realm, CancellationToken cancellationToken) From 6690aed00be8f323b9466eb4755644a463862308 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 26 Nov 2024 15:16:29 +0100 Subject: [PATCH 13/21] fix: enable userProfileSeeding Refs: #1172 --- .../Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs index 78e9ae449d..7dcb8c47b4 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs @@ -45,7 +45,7 @@ public async Task UpdateUserProfile(string keycloakInstanceName, CancellationTok var realm = seedDataHandler.Realm; var userProfiles = seedDataHandler.RealmComponents.Where(x => x.ProviderType == UserProfileType); var defaultConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.UserProfile); - if (defaultConfig.ModificationAllowed(ModificationType.Update)) + if (!defaultConfig.ModificationAllowed(ModificationType.Update)) { return; } From 7e45f50bdce3fd61f3524b902983f5bab02ed99c Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 27 Nov 2024 16:23:13 +0100 Subject: [PATCH 14/21] feat(seeding): adjust configuration for seeding Refs: #1172 --- .../BusinessLogic/ISeedDataHandler.cs | 2 +- .../BusinessLogic/KeecloakSeeder.cs | 2 +- .../BusinessLogic/RolesUpdater.cs | 202 +++++--- .../BusinessLogic/SeedDataHandler.cs | 40 +- .../KeycloakRealmSettingsExtensions.cs | 91 ++++ .../SeederConfigurationExtensions.cs | 75 ++- .../Models/KeycloakSeederConfigModel.cs | 4 +- .../Models/SeederConfigurationModel.cs | 27 + .../Extensions/KeycloakRealmSettingsTests.cs | 123 +++++ .../SeederConfigurationExtensionsTests.cs | 461 ++++++++++++++++++ .../KeycloakRealmModelTests.cs | 1 + 11 files changed, 875 insertions(+), 153 deletions(-) create mode 100644 src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs create mode 100644 src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs create mode 100644 tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs create mode 100644 tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs index 8ef19ea64e..10dd81fe55 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -51,7 +51,6 @@ public interface ISeedDataHandler IEnumerable<(string ProviderType, ComponentModel ComponentModel)> RealmComponents { get; } IEnumerable<(string Locale, IEnumerable> Translations)> RealmLocalizations { get; } - SeederConfiguration Configuration { get; } Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds); @@ -63,4 +62,5 @@ public interface ISeedDataHandler AuthenticatorConfigModel GetAuthenticatorConfig(string? alias); KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey); + bool IsModificationAllowed(ConfigurationKey configKey); } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs index d385eb3686..cd3ad2983e 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -62,7 +62,7 @@ public async Task Seed(CancellationToken cancellationToken) } private Task CheckAndExecuteUpdater(ConfigurationKey configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) => - seedDataHandler.Configuration.IsModificationAllowed(configKey) + seedDataHandler.IsModificationAllowed(configKey) ? updaterExecution(instanceName, cancellationToken) : Task.CompletedTask; } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index b84820522a..d9c95bec73 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -114,98 +114,140 @@ public async Task UpdateCompositeRoles(string keycloakInstanceName, Cancellation var id = seedDataHandler.GetIdOfClient(clientId); await UpdateCompositeRolesInner( - () => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken), + keycloak, + realm, + seederConfig, updateRoles, + () => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken), (name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, id, name, roles, cancellationToken), - (name, roles) => keycloak.AddCompositesToRoleAsync(realm, id, name, roles, cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.None); + (name, roles) => keycloak.AddCompositesToRoleAsync(realm, id, name, roles, cancellationToken), + cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } await UpdateCompositeRolesInner( - () => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken), + keycloak, + realm, + seederConfig, seedDataHandler.RealmRoles, + () => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken), (name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, name, roles, cancellationToken), - (name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.None); + (name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken), + cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } + + public async Task UpdateCompositeRolesInner( + KeycloakClient keycloak, + string realm, + KeycloakSeederConfigModel seederConfig, + IEnumerable updateRoles, + Func>> getRoles, + Func, Task> removeCompositeRoles, + Func, Task> addCompositeRoles, + CancellationToken cancellationToken) + { + var roles = await getRoles().ConfigureAwait(ConfigureAwaitOptions.None); + + await RemoveAddCompositeRolesInner<(string ContainerId, string Name)>( + keycloak, + realm, + seederConfig, + updateRoles, + roles, + removeCompositeRoles, + addCompositeRoles, + roleModel => roleModel.Composites?.Client?.Any() ?? false, + role => role.Composites?.Client?.Any() ?? false, + role => role.ClientRole ?? false, + roleModel => roleModel.Composites?.Client? + .FilterNotNullValues() + .Select(x => ( + Id: seedDataHandler.GetIdOfClient(x.Key), + Names: x.Value)) + .SelectMany(x => x.Names.Select(name => (x.Id, name))) ?? throw new ConflictException($"roleModel.Composites.Client is null: {roleModel.Id} {roleModel.Name}"), + role => ( + role.ContainerId ?? throw new ConflictException($"role.ContainerId is null: {role.Id} {role.Name}"), + role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}")), + async x => await keycloak.GetRoleByNameAsync(realm, x.ContainerId, x.Name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None), + cancellationToken + ).ConfigureAwait(ConfigureAwaitOptions.None); + + await RemoveAddCompositeRolesInner( + keycloak, + realm, + seederConfig, + updateRoles, + roles, + removeCompositeRoles, + addCompositeRoles, + roleModel => roleModel.Composites?.Realm?.Any() ?? false, + role => role.Composites?.Realm?.Any() ?? false, + role => !(role.ClientRole ?? false), + roleModel => roleModel.Composites?.Realm ?? throw new ConflictException($"roleModel.Composites.Realm is null: {roleModel.Id} {roleModel.Name}"), + role => role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}"), + async name => await keycloak.GetRoleByNameAsync(realm, name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None), + cancellationToken + ).ConfigureAwait(ConfigureAwaitOptions.None); + } + + private static async Task RemoveAddCompositeRolesInner( + KeycloakClient keycloak, + string realm, + KeycloakSeederConfigModel seederConfig, + IEnumerable updateRoles, + IEnumerable roles, + Func, Task> removeCompositeRoles, + Func, Task> addCompositeRoles, + Func compositeRolesUpdatePredicate, + Func compositeRolesPredicate, + Func rolePredicate, + Func> joinUpdateSelector, + Func joinUpdateKey, + Func> getRoleByName, + CancellationToken cancellationToken) + { + var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x)); + var removeComposites = roles.Where(x => compositeRolesPredicate(x)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); - async Task UpdateCompositeRolesInner( - Func>> getRoles, - IEnumerable updateRoles, - Func, Task> removeCompositeRoles, - Func, Task> addCompositeRoles) + await RemoveRoles(keycloak, realm, removeCompositeRoles, rolePredicate, cancellationToken, removeComposites); + + var joinedComposites = roles.Join( + updateComposites, + role => role.Name, + roleModel => roleModel.Name, + (role, roleModel) => ( + Role: role, + Update: joinUpdateSelector(roleModel))); + + foreach (var (role, updates) in joinedComposites) { - var roles = await getRoles().ConfigureAwait(ConfigureAwaitOptions.None); - - await RemoveAddCompositeRolesInner<(string ContainerId, string Name)>( - roleModel => roleModel.Composites?.Client?.Any() ?? false, - role => role.Composites?.Client?.Any() ?? false, - role => role.ClientRole ?? false, - roleModel => roleModel.Composites?.Client? - .FilterNotNullValues() - .Select(x => ( - Id: seedDataHandler.GetIdOfClient(x.Key), - Names: x.Value)) - .SelectMany(x => x.Names.Select(name => (x.Id, name))) ?? throw new ConflictException($"roleModel.Composites.Client is null: {roleModel.Id} {roleModel.Name}"), - role => ( - role.ContainerId ?? throw new ConflictException($"role.ContainerId is null: {role.Id} {role.Name}"), - role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}")), - async x => await keycloak.GetRoleByNameAsync(realm, x.ContainerId, x.Name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None) - ).ConfigureAwait(ConfigureAwaitOptions.None); - - await RemoveAddCompositeRolesInner( - roleModel => roleModel.Composites?.Realm?.Any() ?? false, - role => role.Composites?.Realm?.Any() ?? false, - role => !(role.ClientRole ?? false), - roleModel => roleModel.Composites?.Realm ?? throw new ConflictException($"roleModel.Composites.Realm is null: {roleModel.Id} {roleModel.Name}"), - role => role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}"), - async name => await keycloak.GetRoleByNameAsync(realm, name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None) - ).ConfigureAwait(ConfigureAwaitOptions.None); - - async Task RemoveAddCompositeRolesInner( - Func compositeRolesUpdatePredicate, - Func compositeRolesPredicate, - Func rolePredicate, - Func> joinUpdateSelector, - Func joinUpdateKey, - Func> getRoleByName) - { - var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ModificationType.Update, x.Name)); - var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); + if (role.Id == null || role.Name == null) + throw new ConflictException($"role.id or role.name must not be null {role.Id} {role.Name}"); + var composites = (await keycloak.GetRoleChildrenAsync(realm, role.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role)); + composites.Where(role => role.ContainerId == null || role.Name == null).IfAny( + invalid => throw new ConflictException($"composites roles containerId or name must not be null: {string.Join(" ", invalid.Select(x => $"[{string.Join(",", x.Id, x.Name, x.Description, x.ContainerId)}]"))}")); - foreach (var remove in removeComposites) - { - if (remove.Id == null || remove.Name == null) - throw new ConflictException($"role.id or role.name must not be null {remove.Id} {remove.Name}"); + var remove = composites.ExceptBy(updates, role => joinUpdateKey(role)).Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, role.Name) || seederConfig.ModificationAllowed(ModificationType.Delete, x.Name)); + await removeCompositeRoles(role.Name, remove).ConfigureAwait(ConfigureAwaitOptions.None); - var composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role)); - await removeCompositeRoles(remove.Name, composites).ConfigureAwait(ConfigureAwaitOptions.None); - } + var add = await updates.Except(composites.Select(role => joinUpdateKey(role))) + .ToAsyncEnumerable() + .SelectAwait(x => getRoleByName(x)) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ModificationType.Create, role.Name) || seederConfig.ModificationAllowed(ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None); + } + } - var joinedComposites = roles.Join( - updateComposites, - role => role.Name, - roleModel => roleModel.Name, - (role, roleModel) => ( - Role: role, - Update: joinUpdateSelector(roleModel))); - - foreach (var (role, updates) in joinedComposites) - { - if (role.Id == null || role.Name == null) - throw new ConflictException($"role.id or role.name must not be null {role.Id} {role.Name}"); - var composites = (await keycloak.GetRoleChildrenAsync(realm, role.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role)); - composites.Where(role => role.ContainerId == null || role.Name == null).IfAny( - invalid => throw new ConflictException($"composites roles containerId or name must not be null: {string.Join(" ", invalid.Select(x => $"[{string.Join(",", x.Id, x.Name, x.Description, x.ContainerId)}]"))}")); - - var remove = composites.ExceptBy(updates, role => joinUpdateKey(role)); - await removeCompositeRoles(role.Name, remove).ConfigureAwait(ConfigureAwaitOptions.None); - - var add = await updates.Except(composites.Select(role => joinUpdateKey(role))) - .ToAsyncEnumerable() - .SelectAwait(x => getRoleByName(x)) - .ToListAsync(cancellationToken) - .ConfigureAwait(false); - await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None); - } - } + private static async Task RemoveRoles(KeycloakClient keycloak, string realm, Func, Task> removeCompositeRoles, + Func rolePredicate, CancellationToken cancellationToken, IEnumerable removeComposites) + { + foreach (var remove in removeComposites) + { + if (remove.Id == null || remove.Name == null) + throw new ConflictException($"role.id or role.name must not be null {remove.Id} {remove.Name}"); + + var composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role)); + await removeCompositeRoles(remove.Name, composites).ConfigureAwait(ConfigureAwaitOptions.None); } } diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index e7cef754ae..77cfaa2e03 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -20,6 +20,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; using System.Collections.Immutable; using System.Text.Json; @@ -35,11 +36,10 @@ public class SeedDataHandler : ISeedDataHandler PropertyNameCaseInsensitive = false }; - private readonly IDictionary _specificConfigurations = new Dictionary(); - private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; - private SeederConfiguration? _defaultConfiguration; + private SeederConfigurationModel? _defaultConfiguration; + private Dictionary? _flatConfiguration; public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken) { @@ -49,16 +49,10 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken async (importRealm, path) => importRealm.Merge(await ReadJsonRealm(path, realmSettings.Realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)) .Merge(realmSettings.ToModel()); - _defaultConfiguration = new SeederConfiguration - { - Create = realmSettings.Create, - Update = realmSettings.Update, - Delete = realmSettings.Delete, - SeederConfigurations = realmSettings.SeederConfigurations - }; + _defaultConfiguration = realmSettings.GetConfigurationDictionaries(); + _flatConfiguration = realmSettings.GetFlatDictionary(); _idOfClients = null; - _specificConfigurations.Clear(); } private static async Task ReadJsonRealm(string path, string realm, CancellationToken cancellationToken) @@ -81,11 +75,6 @@ public string Realm get => _keycloakRealm?.Realm ?? throw new ConflictException("realm must not be null"); } - public SeederConfiguration Configuration - { - get => _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); - } - public KeycloakRealm KeycloakRealm { get => _keycloakRealm ?? throw new InvalidOperationException("Import has not been called"); @@ -178,15 +167,20 @@ public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) => public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey) { - if (_specificConfigurations.TryGetValue(configKey, out var specificConfiguration)) + var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); + config.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfiguration); + return new KeycloakSeederConfigModel(config, specificConfiguration); + } + + public bool IsModificationAllowed(ConfigurationKey configKey) + { + var flatConfig = _flatConfiguration ?? throw new ConflictException("configuration must not be null"); + var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); + if (flatConfig.TryGetValue(configKey.ToString().ToLower(), out var result)) { - return new KeycloakSeederConfigModel(Configuration, specificConfiguration); + return result.Create || result.Update || result.Delete; } - var configKeyString = configKey.ToString(); - - specificConfiguration = Configuration.SeederConfigurations?.SingleOrDefault(x => x.Key == configKeyString); - _specificConfigurations.Add(configKey, specificConfiguration); - return new KeycloakSeederConfigModel(Configuration, specificConfiguration); + return config.Create || config.Update || config.Delete; } } diff --git a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs new file mode 100644 index 0000000000..0725a0b012 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs @@ -0,0 +1,91 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; + +public static class KeycloakRealmSettingsExtensions +{ + public static Dictionary GetFlatDictionary(this KeycloakRealmSettings realmSettings) + { + var result = new Dictionary(); + + GetFlatDictionary(realmSettings.SeederConfigurations, 0, result); + + return result.Select(x => new KeyValuePair>(x.Key.ToLower(), new ValueTuple(x.Value.Create, x.Value.Update, x.Value.Delete))).ToDictionary(); + } + + private static (bool Create, bool Update, bool Delete) GetFlatDictionary( + IEnumerable? configurations, + int depth, + IDictionary result) + { + if (configurations == null) + { + return (false, false, false); + } + + var parentCreate = false; + var parentUpdate = false; + var parentDelete = false; + + foreach (var config in configurations) + { + // Process child configurations first + var childPermissions = GetFlatDictionary(config.SeederConfigurations, depth + 1, result); + + // Combine child permissions with current configuration's permissions + var create = config.Create || childPermissions.Create; + var update = config.Update || childPermissions.Update; + var delete = config.Delete || childPermissions.Delete; + + // If the key doesn't exist or the current depth is deeper, update the result + if (!result.TryGetValue(config.Key.ToLower(), out var value) || depth > value.Depth) + { + result[config.Key.ToLower()] = (create, update, delete, depth); + } + + // Aggregate permissions for the parent + parentCreate |= create; + parentUpdate |= update; + parentDelete |= delete; + } + + return (parentCreate, parentUpdate, parentDelete); + } + + public static SeederConfigurationModel GetConfigurationDictionaries(this KeycloakRealmSettings realmSettings) => + new( + realmSettings.Create, + realmSettings.Update, + realmSettings.Delete, + realmSettings.SeederConfigurations?.ToDictionary(sc => + sc.Key.ToLower(), + ConvertSeederConfigToSeederConfigurationModel) ?? new Dictionary()); + + private static SeederConfigurationModel ConvertSeederConfigToSeederConfigurationModel(this SeederConfiguration seederConfig) => + new( + seederConfig.Create, + seederConfig.Update, + seederConfig.Delete, + seederConfig.SeederConfigurations?.ToDictionary(sc => + sc.Key.ToLower(), + ConvertSeederConfigToSeederConfigurationModel) ?? new Dictionary()); +} diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 63c71e7dc2..bf29e90662 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -23,38 +23,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class SeederConfigurationExtensions { - public static bool IsModificationAllowed(this SeederConfiguration config, ConfigurationKey configKey) => - config.Create || config.Update || config.Delete || - (config.SeederConfigurations != null && - IsModificationAllowed(config.SeederConfigurations, configKey.ToString(), false, out var _)); - - private static bool IsModificationAllowed( - IEnumerable configurations, - string targetKey, - bool isKeyParentConfig, - out SeederConfiguration? matchingConfig) - { - matchingConfig = null; - - foreach (var config in configurations) - { - if (config.SeederConfigurations != null && IsModificationAllowed(config.SeederConfigurations, targetKey, isKeyParentConfig || config.Key == targetKey, out matchingConfig)) - { - return true; - } - - if ((config.Key != targetKey && !isKeyParentConfig) || config is { Create: false, Update: false, Delete: false }) - { - continue; - } - - matchingConfig = config; - return true; - } - - return false; - } - public static bool ModificationAllowed(this KeycloakSeederConfigModel config, ModificationType modificationType) => config.ModificationAllowed(modificationType, null); @@ -65,8 +33,7 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, Mo return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); // If we have a configuration for a specific entry return its value - var specificEntry = specificConfig?.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); - if (specificEntry != null) + if (specificConfig?.SeederConfigurations.TryGetValue(entityKey.ToLower(), out var specificEntry) == true) { return specificEntry.ModifyAllowed(modificationType); } @@ -75,26 +42,42 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, Mo return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); } + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, string containingEntityKey, ConfigurationKey configKey, ModificationType modificationType) => + config.ModificationAllowed(containingEntityKey, configKey, modificationType, null); + public static bool ModificationAllowed(this KeycloakSeederConfigModel config, string containingEntityKey, ConfigurationKey configKey, ModificationType modificationType, string? entityKey) { - var containingEntityTypeConfig = config.SpecificConfiguration?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(containingEntityKey, StringComparison.OrdinalIgnoreCase))?.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)); - if (containingEntityTypeConfig is null) + // Check if the specific configuration contains the entity key + // e.g. for the users configuration check for a specific user configuration + if (config.SpecificConfiguration?.SeederConfigurations.TryGetValue(containingEntityKey.ToLower(), out var containingEntityKeyConfiguration) == true) { - var configModel = config with { SpecificConfiguration = config.DefaultSettings.SeederConfigurations?.SingleOrDefault(x => x.Key.Equals(configKey.ToString(), StringComparison.OrdinalIgnoreCase)) }; - return configModel.ModificationAllowed(modificationType, entityKey); - } + // check if the specific entity configuration has a configuration for the section + // e.g. for the specific user configuration is there a section for federated identities + if (containingEntityKeyConfiguration.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var containingEntityTypeConfig) != true) + { + config.DefaultSettings.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfig); + var configModel = config with { SpecificConfiguration = specificConfig }; + return configModel.ModificationAllowed(modificationType, entityKey); + } - if (entityKey is null) - { - return containingEntityTypeConfig.ModifyAllowed(modificationType); + // if the entity key isn't set check the configuration for the type + if (entityKey is null) + { + return containingEntityTypeConfig.ModifyAllowed(modificationType); + } + + // If we have a configuration for a specific entry return its value otherwise take the section configuration + containingEntityTypeConfig.SeederConfigurations.TryGetValue(entityKey.ToLower(), out var entity); + return entity?.ModifyAllowed(modificationType) ?? containingEntityTypeConfig.ModifyAllowed(modificationType); } - // If we have a configuration for a specific entry return its value - var entity = containingEntityTypeConfig.SeederConfigurations?.SingleOrDefault(c => c.Key.Equals(entityKey, StringComparison.OrdinalIgnoreCase)); - return entity?.ModifyAllowed(modificationType) ?? config.ModificationAllowed(modificationType, entityKey); + // if no configuration isn't set check the top level configuration + config.DefaultSettings.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var topLevelSpecificConfig); + var topLevelConfig = config with { SpecificConfiguration = topLevelSpecificConfig }; + return topLevelConfig.ModificationAllowed(modificationType, entityKey); } - private static bool ModifyAllowed(this SeederConfiguration configuration, ModificationType modificationType) => + private static bool ModifyAllowed(this SeederConfigurationModel configuration, ModificationType modificationType) => modificationType switch { ModificationType.Create => configuration.Create, diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs index ffdd624b7f..9337636143 100644 --- a/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakSeederConfigModel.cs @@ -20,6 +20,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; public record KeycloakSeederConfigModel( - SeederConfiguration DefaultSettings, - SeederConfiguration? SpecificConfiguration + SeederConfigurationModel DefaultSettings, + SeederConfigurationModel? SpecificConfiguration ); diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs new file mode 100644 index 0000000000..9858919ac3 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public record SeederConfigurationModel( + bool Create, + bool Update, + bool Delete, + IDictionary SeederConfigurations +); diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs new file mode 100644 index 0000000000..88ccaa609b --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs @@ -0,0 +1,123 @@ +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests.Extensions; + +using System.Collections.Generic; +using Xunit; + +public class KeycloakRealmSettingsTests +{ + [Fact] + public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() + { + // Arrange + var realmSettings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new List + { + new() + { + Key = "FederatedIdentities", + Create = false, + Update = false, + Delete = false + }, + new() + { + Key = "Users", + Create = false, + Update = true, + Delete = true, + SeederConfigurations = new List + { + new() + { + Key = "testUser", + Create = false, + Update = true, + Delete = false, + SeederConfigurations = new List + { + new() + { + Key = "FederatedIdentities", + Create = true, + Update = true, + Delete = false + } + } + } + } + }, + new() + { + Key = "Clients", + Create = false, + Update = false, + Delete = false + } + } + }; + + // Act + var result = realmSettings.GetFlatDictionary(); + + // Assert + result.Should().HaveCount(4).And.Satisfy( + x => x.Key == "federatedidentities" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == false, + x => x.Key == "testuser" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == false, + x => x.Key == "users" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == true, + x => x.Key == "clients" && x.Value.Create == false && x.Value.Update == false && x.Value.Delete == false + ); + } + + [Fact] + public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpected() + { + // Arrange + var realmSettings = new KeycloakRealmSettings + { + Create = true, + Update = false, + Delete = true, + SeederConfigurations = new List + { + new() + { + Key = "Users", + Create = true, + Update = false, + Delete = true, + SeederConfigurations = new List + { + new() + { + Key = "testUser", + Create = false, + Update = true, + Delete = false + } + } + } + } + }; + + // Act + var result = realmSettings.GetConfigurationDictionaries(); + + // Assert + result.Create.Should().BeTrue(); + result.Update.Should().BeFalse(); + result.Delete.Should().BeTrue(); + result.SeederConfigurations.Should().ContainSingle().And.Satisfy( + x => x.Key == "users" && x.Value.Create == true && x.Value.Update == false && x.Value.Delete == true && + x.Value.SeederConfigurations.Count == 1 && x.Value.SeederConfigurations.ContainsKey("testuser") == true && + x.Value.SeederConfigurations.Single().Value.Create == false && + x.Value.SeederConfigurations.Single().Value.Update == true && + x.Value.SeederConfigurations.Single().Value.Delete == false); + } +} diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs new file mode 100644 index 0000000000..a4c46abc40 --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs @@ -0,0 +1,461 @@ +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests.Extensions; + +public class SeederConfigurationExtensionsTests +{ + [Fact] + public void ModifyAllowed_InvalidModificationType_ThrowsException() + { + var defaultConfig = new SeederConfigurationModel(false, false, false, new Dictionary()); + var config = new KeycloakSeederConfigModel(defaultConfig, null); + + Assert.Throws(() => config.ModificationAllowed((ModificationType)666)); + } + + [Theory] + [InlineData(true, false, false, ModificationType.Create, true)] + [InlineData(false, false, false, ModificationType.Create, false)] + [InlineData(false, true, false, ModificationType.Update, true)] + [InlineData(false, false, false, ModificationType.Update, false)] + [InlineData(false, false, true, ModificationType.Delete, true)] + [InlineData(false, false, false, ModificationType.Delete, false)] + public void ModifyAllowed_WithExpected_ReturnsExpected(bool create, bool update, bool delete, ModificationType modificationType, bool expectedResult) + { + var defaultConfig = new SeederConfigurationModel(create, update, delete, new Dictionary()); + var config = new KeycloakSeederConfigModel(defaultConfig, null); + + var result = config.ModificationAllowed(modificationType); + + result.Should().Be(expectedResult); + } + + [Fact] + public void ModificationAllowed_DefaultConfigAllows_ReturnsTrue() + { + var settings = new KeycloakRealmSettings + { + Create = true, + Update = false, + Delete = false + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + var config = new KeycloakSeederConfigModel(defaultSettings, null); + + var result = config.ModificationAllowed(ModificationType.Create); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_DefaultConfigDeniesCreate_ReturnsFalse() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + var config = new KeycloakSeederConfigModel(defaultSettings, null); + + var result = config.ModificationAllowed(ModificationType.Create); + + result.Should().BeFalse(); + } + + [Fact] + public void ModificationAllowed_WithSpecificConfigurationOverwrites_ReturnsSpecificConfiguration() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = true, + Update = false, + Delete = false + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed(ModificationType.Create); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_WithEntityKeySpecificConfig_ReturnsEntitySpecificKey() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = true, Update = false, Delete = false + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed(ModificationType.Create, "testUser"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_EntityKeyNotInSpecificConfig_UsesSpecificConfig() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = true, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed(ModificationType.Create, "nonexistent"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_NestedEntityConfigAllowsCreate_ReturnsTrue() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.FederatedIdentities.ToString(), + Create = true, + Update = false, + Delete = false, + } + } + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_NestedSpecificEntityConfigAllowsCreate_ReturnsTrue() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.FederatedIdentities.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "fi1", + Create = true, + Update = false, + Delete = false, + } + } + } + } + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "fi1"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_NestedSpecificEntityNotFoundConfigAllowsCreate_ReturnsNestedEntity() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.FederatedIdentities.ToString(), + Create = true, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "fi1", + Create = false, + Update = false, + Delete = false, + } + } + } + } + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "finotfound"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_WithoutNestedConfigAndSpecificEntry_ReturnsTopLevelSpecific() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false + } + } + }, + new() + { + Key = ConfigurationKey.FederatedIdentities.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "fi", + Create = true, + Update = false, + Delete = false, + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "fi"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_WithoutNestedConfig_ReturnsTopLevel() + { + var settings = new KeycloakRealmSettings + { + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false + } + } + }, + new() + { + Key = ConfigurationKey.FederatedIdentities.ToString(), + Create = true, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "fi", + Create = false, + Update = false, + Delete = false, + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "missing"); + + result.Should().BeTrue(); + } + + [Fact] + public void ModificationAllowed_WithoutConfig_ReturnsDefault() + { + var settings = new KeycloakRealmSettings + { + Create = true, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = ConfigurationKey.Users.ToString(), + Create = false, + Update = false, + Delete = false, + SeederConfigurations = new SeederConfiguration[] + { + new() + { + Key = "testUser", Create = false, Update = false, Delete = false + } + } + } + } + }; + var defaultSettings = settings.GetConfigurationDictionaries(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + + var result = config.ModificationAllowed("xy", ConfigurationKey.FederatedIdentities, ModificationType.Create, "missing"); + + result.Should().BeTrue(); + } +} diff --git a/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs index b88d09090d..afa2dfa0a5 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs @@ -20,6 +20,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests; public class KeycloakRealmModelTests From ed0b2ffb92c4c3a456a2c8bb7b3f4a1e732d86f5 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 27 Nov 2024 17:07:29 +0100 Subject: [PATCH 15/21] fix: codeql findings Refs: #1172 --- .../BusinessLogic/RolesUpdater.cs | 11 ++++++++--- .../SeederConfigurationExtensions.cs | 4 +++- .../Extensions/KeycloakRealmSettingsTests.cs | 18 +++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs index d9c95bec73..304c58b83d 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -208,7 +208,7 @@ private static async Task RemoveAddCompositeRolesInner( var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x)); var removeComposites = roles.Where(x => compositeRolesPredicate(x)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); - await RemoveRoles(keycloak, realm, removeCompositeRoles, rolePredicate, cancellationToken, removeComposites); + await RemoveRoles(keycloak, realm, removeCompositeRoles, rolePredicate, removeComposites, cancellationToken); var joinedComposites = roles.Join( updateComposites, @@ -238,8 +238,13 @@ private static async Task RemoveAddCompositeRolesInner( } } - private static async Task RemoveRoles(KeycloakClient keycloak, string realm, Func, Task> removeCompositeRoles, - Func rolePredicate, CancellationToken cancellationToken, IEnumerable removeComposites) + private static async Task RemoveRoles( + KeycloakClient keycloak, + string realm, + Func, Task> removeCompositeRoles, + Func rolePredicate, + IEnumerable removeComposites, + CancellationToken cancellationToken) { foreach (var remove in removeComposites) { diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index bf29e90662..5691f76ec0 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -30,7 +30,9 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, Mo { var (defaultConfig, specificConfig) = config; if (entityKey is null) + { return specificConfig?.ModifyAllowed(modificationType) ?? defaultConfig.ModifyAllowed(modificationType); + } // If we have a configuration for a specific entry return its value if (specificConfig?.SeederConfigurations.TryGetValue(entityKey.ToLower(), out var specificEntry) == true) @@ -53,7 +55,7 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, st { // check if the specific entity configuration has a configuration for the section // e.g. for the specific user configuration is there a section for federated identities - if (containingEntityKeyConfiguration.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var containingEntityTypeConfig) != true) + if (!containingEntityKeyConfiguration.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var containingEntityTypeConfig)) { config.DefaultSettings.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfig); var configModel = config with { SpecificConfiguration = specificConfig }; diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs index 88ccaa609b..b877908b3b 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs @@ -68,10 +68,10 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() // Assert result.Should().HaveCount(4).And.Satisfy( - x => x.Key == "federatedidentities" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == false, - x => x.Key == "testuser" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == false, - x => x.Key == "users" && x.Value.Create == true && x.Value.Update == true && x.Value.Delete == true, - x => x.Key == "clients" && x.Value.Create == false && x.Value.Update == false && x.Value.Delete == false + x => x.Key == "federatedidentities" && x.Value.Create && x.Value.Update && !x.Value.Delete, + x => x.Key == "testuser" && x.Value.Create && x.Value.Update && !x.Value.Delete, + x => x.Key == "users" && x.Value.Create && x.Value.Update && x.Value.Delete, + x => x.Key == "clients" && !x.Value.Create && !x.Value.Update && !x.Value.Delete ); } @@ -114,10 +114,10 @@ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpecte result.Update.Should().BeFalse(); result.Delete.Should().BeTrue(); result.SeederConfigurations.Should().ContainSingle().And.Satisfy( - x => x.Key == "users" && x.Value.Create == true && x.Value.Update == false && x.Value.Delete == true && - x.Value.SeederConfigurations.Count == 1 && x.Value.SeederConfigurations.ContainsKey("testuser") == true && - x.Value.SeederConfigurations.Single().Value.Create == false && - x.Value.SeederConfigurations.Single().Value.Update == true && - x.Value.SeederConfigurations.Single().Value.Delete == false); + x => x.Key == "users" && x.Value.Create && !x.Value.Update && x.Value.Delete && + x.Value.SeederConfigurations.Count == 1 && x.Value.SeederConfigurations.ContainsKey("testuser") && + !x.Value.SeederConfigurations.Single().Value.Create && + x.Value.SeederConfigurations.Single().Value.Update && + !x.Value.SeederConfigurations.Single().Value.Delete); } } From 7396156679da87f236db27a2f7f36bbe352bde57 Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 14:40:51 +0100 Subject: [PATCH 16/21] simplify logic --- .../BusinessLogic/SeedDataHandler.cs | 27 ++-- .../KeycloakRealmSettingsExtensions.cs | 72 +++-------- .../SeederConfigurationExtensions.cs | 20 ++- .../Models/SeederConfiguration.cs | 2 + .../Models/SeederConfigurationModel.cs | 2 +- .../Extensions/KeycloakRealmSettingsTests.cs | 115 ++++++++++++++---- .../SeederConfigurationExtensionsTests.cs | 19 +++ 7 files changed, 148 insertions(+), 109 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 77cfaa2e03..8482d55410 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -39,7 +39,7 @@ public class SeedDataHandler : ISeedDataHandler private KeycloakRealm? _keycloakRealm; private IReadOnlyDictionary? _idOfClients; private SeederConfigurationModel? _defaultConfiguration; - private Dictionary? _flatConfiguration; + private IReadOnlyDictionary? _flatConfiguration; public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken) { @@ -165,22 +165,13 @@ public IEnumerable GetAuthenticationExecutions(str public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) => _keycloakRealm?.AuthenticatorConfig?.SingleOrDefault(x => x.Alias == (alias ?? throw new ConflictException("alias is null"))) ?? throw new ConflictException($"authenticatorConfig {alias} does not exist"); - public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey) - { - var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); - config.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfiguration); - return new KeycloakSeederConfigModel(config, specificConfiguration); - } + public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey) => + new KeycloakSeederConfigModel( + _defaultConfiguration ?? throw new ConflictException("configuration must not be null"), + _defaultConfiguration.SeederConfigurations?.TryGetValue(configKey.ToString(), out var specificConfiguration) ?? false ? specificConfiguration : null); - public bool IsModificationAllowed(ConfigurationKey configKey) - { - var flatConfig = _flatConfiguration ?? throw new ConflictException("configuration must not be null"); - var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null"); - if (flatConfig.TryGetValue(configKey.ToString().ToLower(), out var result)) - { - return result.Create || result.Update || result.Delete; - } - - return config.Create || config.Update || config.Delete; - } + public bool IsModificationAllowed(ConfigurationKey configKey) => + (_flatConfiguration ?? throw new ConflictException("configuration must not be null")).TryGetValue(configKey, out var result) + ? result + : (_defaultConfiguration ?? throw new ConflictException("configuration must not be null")).Create || _defaultConfiguration.Update || _defaultConfiguration.Delete; } diff --git a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs index 0725a0b012..6448a25458 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs @@ -18,74 +18,42 @@ ********************************************************************************/ using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; +using System.Collections.Immutable; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; public static class KeycloakRealmSettingsExtensions { - public static Dictionary GetFlatDictionary(this KeycloakRealmSettings realmSettings) - { - var result = new Dictionary(); - - GetFlatDictionary(realmSettings.SeederConfigurations, 0, result); - - return result.Select(x => new KeyValuePair>(x.Key.ToLower(), new ValueTuple(x.Value.Create, x.Value.Update, x.Value.Delete))).ToDictionary(); - } - - private static (bool Create, bool Update, bool Delete) GetFlatDictionary( - IEnumerable? configurations, - int depth, - IDictionary result) - { - if (configurations == null) - { - return (false, false, false); - } - - var parentCreate = false; - var parentUpdate = false; - var parentDelete = false; - - foreach (var config in configurations) - { - // Process child configurations first - var childPermissions = GetFlatDictionary(config.SeederConfigurations, depth + 1, result); - - // Combine child permissions with current configuration's permissions - var create = config.Create || childPermissions.Create; - var update = config.Update || childPermissions.Update; - var delete = config.Delete || childPermissions.Delete; - - // If the key doesn't exist or the current depth is deeper, update the result - if (!result.TryGetValue(config.Key.ToLower(), out var value) || depth > value.Depth) - { - result[config.Key.ToLower()] = (create, update, delete, depth); - } - - // Aggregate permissions for the parent - parentCreate |= create; - parentUpdate |= update; - parentDelete |= delete; - } - - return (parentCreate, parentUpdate, parentDelete); - } + public static IReadOnlyDictionary? GetFlatDictionary(this KeycloakRealmSettings realmSettings) => + realmSettings.SeederConfigurations? + .Join( + Enum.GetValues(), + config => config.Key, + key => key.ToString(), + (SeederConfiguration config, ConfigurationKey key) => KeyValuePair.Create(key, GetFlat(config)), + StringComparer.OrdinalIgnoreCase + ).ToImmutableDictionary(); + + private static bool GetFlat(SeederConfiguration config) => + config.Create || config.Update || config.Delete || (config.SeederConfigurations != null && config.SeederConfigurations.Any(GetFlat)); public static SeederConfigurationModel GetConfigurationDictionaries(this KeycloakRealmSettings realmSettings) => new( realmSettings.Create, realmSettings.Update, realmSettings.Delete, - realmSettings.SeederConfigurations?.ToDictionary(sc => - sc.Key.ToLower(), - ConvertSeederConfigToSeederConfigurationModel) ?? new Dictionary()); + realmSettings.SeederConfigurations?.ToImmutableDictionary(sc => + sc.Key, + ConvertSeederConfigToSeederConfigurationModel, + StringComparer.OrdinalIgnoreCase)); private static SeederConfigurationModel ConvertSeederConfigToSeederConfigurationModel(this SeederConfiguration seederConfig) => new( seederConfig.Create, seederConfig.Update, seederConfig.Delete, - seederConfig.SeederConfigurations?.ToDictionary(sc => + seederConfig.SeederConfigurations?.ToImmutableDictionary(sc => sc.Key.ToLower(), - ConvertSeederConfigToSeederConfigurationModel) ?? new Dictionary()); + ConvertSeederConfigToSeederConfigurationModel, + StringComparer.OrdinalIgnoreCase)); } diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs index 5691f76ec0..facbe9ff71 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs @@ -35,7 +35,7 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, Mo } // If we have a configuration for a specific entry return its value - if (specificConfig?.SeederConfigurations.TryGetValue(entityKey.ToLower(), out var specificEntry) == true) + if (specificConfig?.SeederConfigurations?.TryGetValue(entityKey, out var specificEntry) ?? false) { return specificEntry.ModifyAllowed(modificationType); } @@ -51,15 +51,14 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, st { // Check if the specific configuration contains the entity key // e.g. for the users configuration check for a specific user configuration - if (config.SpecificConfiguration?.SeederConfigurations.TryGetValue(containingEntityKey.ToLower(), out var containingEntityKeyConfiguration) == true) + if (config.SpecificConfiguration?.SeederConfigurations?.TryGetValue(containingEntityKey, out var containingEntityKeyConfiguration) ?? false) { // check if the specific entity configuration has a configuration for the section // e.g. for the specific user configuration is there a section for federated identities - if (!containingEntityKeyConfiguration.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var containingEntityTypeConfig)) + if (!(containingEntityKeyConfiguration.SeederConfigurations?.TryGetValue(configKey.ToString(), out var containingEntityTypeConfig) ?? false)) { - config.DefaultSettings.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfig); - var configModel = config with { SpecificConfiguration = specificConfig }; - return configModel.ModificationAllowed(modificationType, entityKey); + return (config with { SpecificConfiguration = config.DefaultSettings.SeederConfigurations?.TryGetValue(configKey.ToString(), out var specificConfig) ?? false ? specificConfig : null }) + .ModificationAllowed(modificationType, entityKey); } // if the entity key isn't set check the configuration for the type @@ -69,14 +68,13 @@ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, st } // If we have a configuration for a specific entry return its value otherwise take the section configuration - containingEntityTypeConfig.SeederConfigurations.TryGetValue(entityKey.ToLower(), out var entity); - return entity?.ModifyAllowed(modificationType) ?? containingEntityTypeConfig.ModifyAllowed(modificationType); + return (containingEntityTypeConfig.SeederConfigurations?.TryGetValue(entityKey, out var entity) ?? false ? entity?.ModifyAllowed(modificationType) : null) + ?? containingEntityTypeConfig.ModifyAllowed(modificationType); } // if no configuration isn't set check the top level configuration - config.DefaultSettings.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var topLevelSpecificConfig); - var topLevelConfig = config with { SpecificConfiguration = topLevelSpecificConfig }; - return topLevelConfig.ModificationAllowed(modificationType, entityKey); + return (config with { SpecificConfiguration = config.DefaultSettings.SeederConfigurations?.TryGetValue(configKey.ToString(), out var topLevelSpecificConfig) ?? false ? topLevelSpecificConfig : null }) + .ModificationAllowed(modificationType, entityKey); } private static bool ModifyAllowed(this SeederConfigurationModel configuration, ModificationType modificationType) => diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs index f56f24d067..dc66e7c713 100644 --- a/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs +++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs @@ -18,11 +18,13 @@ ********************************************************************************/ using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; public class SeederConfiguration { + [Required(AllowEmptyStrings = false)] public string Key { get; set; } = null!; public bool Create { get; set; } public bool Update { get; set; } diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs index 9858919ac3..9cbe4f6cef 100644 --- a/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs +++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs @@ -23,5 +23,5 @@ public record SeederConfigurationModel( bool Create, bool Update, bool Delete, - IDictionary SeederConfigurations + IReadOnlyDictionary? SeederConfigurations ); diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs index b877908b3b..4f11a9f4d8 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs @@ -1,3 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; @@ -17,8 +36,29 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() Create = false, Update = false, Delete = false, - SeederConfigurations = new List - { + SeederConfigurations = + [ + new() + { + Key = "roles", + Create = true, + Update = false, + Delete = false + }, + new() + { + Key = "localiZations", + Create = false, + Update = true, + Delete = false + }, + new() + { + Key = "UserProfile", + Create = false, + Update = false, + Delete = true + }, new() { Key = "FederatedIdentities", @@ -30,18 +70,25 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() { Key = "Users", Create = false, - Update = true, - Delete = true, - SeederConfigurations = new List - { + Update = false, + Delete = false, + SeederConfigurations = + [ new() { - Key = "testUser", + Key = "testUser1", Create = false, - Update = true, + Update = false, + Delete = false + }, + new() + { + Key = "testUser2", + Create = false, + Update = false, Delete = false, - SeederConfigurations = new List - { + SeederConfigurations = + [ new() { Key = "FederatedIdentities", @@ -49,29 +96,41 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() Update = true, Delete = false } - } + ] } - } + ] }, new() { Key = "Clients", Create = false, Update = false, - Delete = false + Delete = false, + SeederConfigurations = + [ + new() + { + Key = "testClient", + Create = false, + Update = false, + Delete = false + } + ] } - } + ] }; // Act var result = realmSettings.GetFlatDictionary(); // Assert - result.Should().HaveCount(4).And.Satisfy( - x => x.Key == "federatedidentities" && x.Value.Create && x.Value.Update && !x.Value.Delete, - x => x.Key == "testuser" && x.Value.Create && x.Value.Update && !x.Value.Delete, - x => x.Key == "users" && x.Value.Create && x.Value.Update && x.Value.Delete, - x => x.Key == "clients" && !x.Value.Create && !x.Value.Update && !x.Value.Delete + result.Should().HaveCount(6).And.Satisfy( + x => x.Key == ConfigurationKey.Roles && x.Value, + x => x.Key == ConfigurationKey.Localizations && x.Value, + x => x.Key == ConfigurationKey.UserProfile && x.Value, + x => x.Key == ConfigurationKey.FederatedIdentities && !x.Value, + x => x.Key == ConfigurationKey.Users && x.Value, + x => x.Key == ConfigurationKey.Clients && !x.Value ); } @@ -84,16 +143,16 @@ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpecte Create = true, Update = false, Delete = true, - SeederConfigurations = new List - { + SeederConfigurations = + [ new() { Key = "Users", Create = true, Update = false, Delete = true, - SeederConfigurations = new List - { + SeederConfigurations = + [ new() { Key = "testUser", @@ -101,9 +160,9 @@ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpecte Update = true, Delete = false } - } + ] } - } + ] }; // Act @@ -114,10 +173,12 @@ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpecte result.Update.Should().BeFalse(); result.Delete.Should().BeTrue(); result.SeederConfigurations.Should().ContainSingle().And.Satisfy( - x => x.Key == "users" && x.Value.Create && !x.Value.Update && x.Value.Delete && - x.Value.SeederConfigurations.Count == 1 && x.Value.SeederConfigurations.ContainsKey("testuser") && + x => x.Key == "Users" && x.Value.Create && !x.Value.Update && x.Value.Delete && + x.Value.SeederConfigurations != null && x.Value.SeederConfigurations.Count == 1 && x.Value.SeederConfigurations.ContainsKey("testUser") && !x.Value.SeederConfigurations.Single().Value.Create && x.Value.SeederConfigurations.Single().Value.Update && !x.Value.SeederConfigurations.Single().Value.Delete); + result.SeederConfigurations!.TryGetValue("users", out var _).Should().BeTrue(); + result.SeederConfigurations["uSeRs"].SeederConfigurations!.TryGetValue("TESTUSER", out var _).Should().BeTrue(); } } diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs index a4c46abc40..3fef86d106 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs @@ -1,3 +1,22 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; From b748eaad986fedf18470ea8663ebfdc95a6bb69b Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 14:54:36 +0100 Subject: [PATCH 17/21] fix unconfigured/default case --- src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs index 8482d55410..6d6528022c 100644 --- a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -171,7 +171,7 @@ public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey confi _defaultConfiguration.SeederConfigurations?.TryGetValue(configKey.ToString(), out var specificConfiguration) ?? false ? specificConfiguration : null); public bool IsModificationAllowed(ConfigurationKey configKey) => - (_flatConfiguration ?? throw new ConflictException("configuration must not be null")).TryGetValue(configKey, out var result) + _flatConfiguration?.TryGetValue(configKey, out var result) ?? false ? result : (_defaultConfiguration ?? throw new ConflictException("configuration must not be null")).Create || _defaultConfiguration.Update || _defaultConfiguration.Delete; } From c0cb7f65515ffc9156a6b8d67a7b0954ae6be33e Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 15:08:58 +0100 Subject: [PATCH 18/21] fix case --- .../Extensions/KeycloakRealmSettingsExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs index 6448a25458..669f340f0c 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs @@ -53,7 +53,7 @@ private static SeederConfigurationModel ConvertSeederConfigToSeederConfiguration seederConfig.Update, seederConfig.Delete, seederConfig.SeederConfigurations?.ToImmutableDictionary(sc => - sc.Key.ToLower(), + sc.Key, ConvertSeederConfigToSeederConfigurationModel, StringComparer.OrdinalIgnoreCase)); } From b6e21fd614fb7e90f1f9fb027ea485e7b7e53b72 Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 16:04:54 +0100 Subject: [PATCH 19/21] make keys case-sensitive --- .../KeycloakRealmSettingsExtensions.cs | 11 +- .../Extensions/KeycloakRealmSettingsTests.cs | 13 +- .../SeederConfigurationExtensionsTests.cs | 172 +++++++++--------- 3 files changed, 104 insertions(+), 92 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs index 669f340f0c..de48e354fa 100644 --- a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs +++ b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs @@ -30,9 +30,8 @@ public static class KeycloakRealmSettingsExtensions Enum.GetValues(), config => config.Key, key => key.ToString(), - (SeederConfiguration config, ConfigurationKey key) => KeyValuePair.Create(key, GetFlat(config)), - StringComparer.OrdinalIgnoreCase - ).ToImmutableDictionary(); + (SeederConfiguration config, ConfigurationKey key) => KeyValuePair.Create(key, GetFlat(config))) + .ToImmutableDictionary(); private static bool GetFlat(SeederConfiguration config) => config.Create || config.Update || config.Delete || (config.SeederConfigurations != null && config.SeederConfigurations.Any(GetFlat)); @@ -44,8 +43,7 @@ public static SeederConfigurationModel GetConfigurationDictionaries(this Keycloa realmSettings.Delete, realmSettings.SeederConfigurations?.ToImmutableDictionary(sc => sc.Key, - ConvertSeederConfigToSeederConfigurationModel, - StringComparer.OrdinalIgnoreCase)); + ConvertSeederConfigToSeederConfigurationModel)); private static SeederConfigurationModel ConvertSeederConfigToSeederConfigurationModel(this SeederConfiguration seederConfig) => new( @@ -54,6 +52,5 @@ private static SeederConfigurationModel ConvertSeederConfigToSeederConfiguration seederConfig.Delete, seederConfig.SeederConfigurations?.ToImmutableDictionary(sc => sc.Key, - ConvertSeederConfigToSeederConfigurationModel, - StringComparer.OrdinalIgnoreCase)); + ConvertSeederConfigToSeederConfigurationModel)); } diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs index 4f11a9f4d8..7152596a6e 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs @@ -40,14 +40,14 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() [ new() { - Key = "roles", + Key = "Roles", Create = true, Update = false, Delete = false }, new() { - Key = "localiZations", + Key = "Localizations", Create = false, Update = true, Delete = false @@ -67,6 +67,13 @@ public void GetFlatDictionary_WithInDepthConfiguration_DeeperOneIsTaken() Delete = false }, new() + { + Key = "FEDERATEDIdentities", + Create = true, + Update = true, + Delete = true + }, + new() { Key = "Users", Create = false, @@ -178,7 +185,5 @@ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpecte !x.Value.SeederConfigurations.Single().Value.Create && x.Value.SeederConfigurations.Single().Value.Update && !x.Value.SeederConfigurations.Single().Value.Delete); - result.SeederConfigurations!.TryGetValue("users", out var _).Should().BeTrue(); - result.SeederConfigurations["uSeRs"].SeederConfigurations!.TryGetValue("TESTUSER", out var _).Should().BeTrue(); } } diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs index 3fef86d106..5693adfcdb 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs @@ -92,8 +92,8 @@ public void ModificationAllowed_WithSpecificConfigurationOverwrites_ReturnsSpeci Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), @@ -101,10 +101,12 @@ public void ModificationAllowed_WithSpecificConfigurationOverwrites_ReturnsSpeci Update = false, Delete = false } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + defaultSettings.SeederConfigurations.Should().NotBeNull(); + + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed(ModificationType.Create); @@ -119,27 +121,28 @@ public void ModificationAllowed_WithEntityKeySpecificConfig_ReturnsEntitySpecifi Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = true, Update = false, Delete = false } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed(ModificationType.Create, "testUser"); @@ -154,27 +157,28 @@ public void ModificationAllowed_EntityKeyNotInSpecificConfig_UsesSpecificConfig( Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = true, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed(ModificationType.Create, "nonexistent"); @@ -189,21 +193,21 @@ public void ModificationAllowed_NestedEntityConfigAllowsCreate_ReturnsTrue() Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.FederatedIdentities.ToString(), @@ -211,15 +215,16 @@ public void ModificationAllowed_NestedEntityConfigAllowsCreate_ReturnsTrue() Update = false, Delete = false, } - } + ] } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create); @@ -234,32 +239,32 @@ public void ModificationAllowed_NestedSpecificEntityConfigAllowsCreate_ReturnsTr Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.FederatedIdentities.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "fi1", @@ -267,17 +272,18 @@ public void ModificationAllowed_NestedSpecificEntityConfigAllowsCreate_ReturnsTr Update = false, Delete = false, } - } + ] } - } + ] } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "fi1"); @@ -292,29 +298,29 @@ public void ModificationAllowed_NestedSpecificEntityNotFoundConfigAllowsCreate_R Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.FederatedIdentities.ToString(), Create = true, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "fi1", @@ -322,17 +328,18 @@ public void ModificationAllowed_NestedSpecificEntityNotFoundConfigAllowsCreate_R Update = false, Delete = false, } - } + ] } - } + ] } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "finotfound"); @@ -347,21 +354,21 @@ public void ModificationAllowed_WithoutNestedConfigAndSpecificEntry_ReturnsTopLe Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false } - } + ] }, new() { @@ -369,8 +376,8 @@ public void ModificationAllowed_WithoutNestedConfigAndSpecificEntry_ReturnsTopLe Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "fi", @@ -378,13 +385,14 @@ public void ModificationAllowed_WithoutNestedConfigAndSpecificEntry_ReturnsTopLe Update = false, Delete = false, } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "fi"); @@ -399,21 +407,21 @@ public void ModificationAllowed_WithoutNestedConfig_ReturnsTopLevel() Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false } - } + ] }, new() { @@ -421,8 +429,8 @@ public void ModificationAllowed_WithoutNestedConfig_ReturnsTopLevel() Create = true, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "fi", @@ -430,13 +438,14 @@ public void ModificationAllowed_WithoutNestedConfig_ReturnsTopLevel() Update = false, Delete = false, } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("testUser", ConfigurationKey.FederatedIdentities, ModificationType.Create, "missing"); @@ -451,27 +460,28 @@ public void ModificationAllowed_WithoutConfig_ReturnsDefault() Create = true, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = ConfigurationKey.Users.ToString(), Create = false, Update = false, Delete = false, - SeederConfigurations = new SeederConfiguration[] - { + SeederConfigurations = + [ new() { Key = "testUser", Create = false, Update = false, Delete = false } - } + ] } - } + ] }; var defaultSettings = settings.GetConfigurationDictionaries(); + defaultSettings.SeederConfigurations.Should().NotBeNull(); - var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations["users"]); + var config = new KeycloakSeederConfigModel(defaultSettings, defaultSettings.SeederConfigurations!["Users"]); var result = config.ModificationAllowed("xy", ConfigurationKey.FederatedIdentities, ModificationType.Create, "missing"); From f1e0178bb67796f00faae1ff5ba650b9c163c692 Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 16:15:03 +0100 Subject: [PATCH 20/21] fix formating --- .../SeederConfigurationExtensionsTests.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs index 5693adfcdb..3c3a0f1f8c 100644 --- a/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs +++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs @@ -133,7 +133,10 @@ public void ModificationAllowed_WithEntityKeySpecificConfig_ReturnsEntitySpecifi [ new() { - Key = "testUser", Create = true, Update = false, Delete = false + Key = "testUser", + Create = true, + Update = false, + Delete = false } ] } @@ -169,7 +172,10 @@ public void ModificationAllowed_EntityKeyNotInSpecificConfig_UsesSpecificConfig( [ new() { - Key = "testUser", Create = false, Update = false, Delete = false + Key = "testUser", + Create = false, + Update = false, + Delete = false } ] } @@ -205,7 +211,10 @@ public void ModificationAllowed_NestedEntityConfigAllowsCreate_ReturnsTrue() [ new() { - Key = "testUser", Create = false, Update = false, Delete = false, + Key = "testUser", + Create = false, + Update = false, + Delete = false, SeederConfigurations = [ new() @@ -310,7 +319,10 @@ public void ModificationAllowed_NestedSpecificEntityNotFoundConfigAllowsCreate_R [ new() { - Key = "testUser", Create = false, Update = false, Delete = false, + Key = "testUser", + Create = false, + Update = false, + Delete = false, SeederConfigurations = [ new() @@ -366,7 +378,10 @@ public void ModificationAllowed_WithoutNestedConfigAndSpecificEntry_ReturnsTopLe [ new() { - Key = "testUser", Create = false, Update = false, Delete = false + Key = "testUser", + Create = false, + Update = false, + Delete = false } ] }, @@ -419,7 +434,10 @@ public void ModificationAllowed_WithoutNestedConfig_ReturnsTopLevel() [ new() { - Key = "testUser", Create = false, Update = false, Delete = false + Key = "testUser", + Create = false, + Update = false, + Delete = false } ] }, @@ -472,7 +490,10 @@ public void ModificationAllowed_WithoutConfig_ReturnsDefault() [ new() { - Key = "testUser", Create = false, Update = false, Delete = false + Key = "testUser", + Create = false, + Update = false, + Delete = false } ] } From a78180c1d40d278bb593beb3f18fc66fca301d6c Mon Sep 17 00:00:00 2001 From: Norbert Truchsess Date: Thu, 28 Nov 2024 16:35:17 +0100 Subject: [PATCH 21/21] update readme --- src/keycloak/Keycloak.Seeding/README.md | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md index 723d42adc2..c9e95685b6 100644 --- a/src/keycloak/Keycloak.Seeding/README.md +++ b/src/keycloak/Keycloak.Seeding/README.md @@ -43,22 +43,22 @@ with this example configuration all entities would be created, updated and delet The following types can be configured: -- `ROLES` -- `LOCALIZATIONS` -- `USERPROFILE` -- `CLIENTSCOPES` -- `CLIENTS` -- `IDENTITYPROVIDERS` -- `IDENTITYPROVIDERMAPPERS` -- `USERS` -- `FEDERATEDIDENTITIES` -- `CLIENTSCOPEMAPPERS` -- `PROTOCOLMAPPERS` -- `AUTHENTICATIONFLOWS` -- `CLIENTPROTOCOLMAPPER` -- `CLIENTROLES` -- `AUTHENTICATIONFLOWEXECUTION` -- `AUTHENTICATORCONFIG` +- `Roles` +- `Localizations` +- `UserProfile` +- `ClientScopes` +- `Clients` +- `IdentityProviders` +- `IdentityProviderMappers` +- `Users` +- `FederatedIdentities` +- `ClientScopeMappers` +- `ProtocolMappers` +- `AuthenticationFlows` +- `ClientProtocolMapper` +- `ClientRoles` +- `AuthenticationFlowExecution` +- `AuthenticatorConfig` ## Entry Specific Configuration @@ -88,7 +88,7 @@ To be able to enable or disable the seeding for specific values the configuratio In the example above you can see that the default settings as well as the specific type settings for update are disabled. But for localizations with the key `profile.attributes.organisation` the update is enabled. With this option you can enable the modification specifically for only the entities you want to modify with the seeding. -**Note**: The key defers for the specific types e.g. for `Localization` it is a string for `User` it is a uuid. +**Note**: The key defers for the specific types e.g. for `Localization` it is a string for `User` it is a uuid. Keys are case-sensitive. ## Entity Specific Type Configurations @@ -148,4 +148,4 @@ This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LIC - SPDX-License-Identifier: Apache-2.0 - SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation -- Source URL: https://github.com/eclipse-tractusx/portal-backend +- Source URL: