diff --git a/.tractusx b/.tractusx
index 4717e14410..3472b596cb 100644
--- a/.tractusx
+++ b/.tractusx
@@ -19,8 +19,8 @@
leadingRepository: "https://github.com/eclipse-tractusx/portal"
openApiSpecs:
-- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC3/docs/api/administration-service.yaml"
-- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC3/docs/api/apps-service.yaml"
-- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC3/docs/api/notifications-service.yaml"
-- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC3/docs/api/registration-service.yaml"
-- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC3/docs/api/services-service.yaml"
+- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC4/docs/api/administration-service.yaml"
+- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC4/docs/api/apps-service.yaml"
+- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC4/docs/api/notifications-service.yaml"
+- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC4/docs/api/registration-service.yaml"
+- "https://raw.githubusercontent.com/eclipse-tractusx/portal-backend/refs/tags/v2.3.0-RC4/docs/api/services-service.yaml"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8238223992..bfd02bba64 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ New features, fixed bugs, known defects and other noteworthy changes to each rel
## Unreleased
+## 2.3.0-RC4
+
+### Change
+
+* **Keycloak realm seeding job**: made seeder configurable [#1174](https://github.com/eclipse-tractusx/portal-backend/pull/1174)
+
## 2.3.0-RC3
### Technical Support
diff --git a/docs/api/administration-service.yaml b/docs/api/administration-service.yaml
index 7307356085..438a8cd4ac 100644
--- a/docs/api/administration-service.yaml
+++ b/docs/api/administration-service.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: Org.Eclipse.TractusX.Portal.Backend.Administration.Service
- version: v2.3.0-RC3
+ version: v2.3.0-RC4
paths:
/api/administration/companydata/ownCompanyDetails:
get:
diff --git a/docs/api/apps-service.yaml b/docs/api/apps-service.yaml
index f3f08e4eab..3877dccc8f 100644
--- a/docs/api/apps-service.yaml
+++ b/docs/api/apps-service.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: Org.Eclipse.TractusX.Portal.Backend.Apps.Service
- version: v2.3.0-RC3
+ version: v2.3.0-RC4
paths:
'/api/apps/AppChange/{appId}/role/activeapp':
post:
diff --git a/docs/api/notifications-service.yaml b/docs/api/notifications-service.yaml
index 2bc85ce85d..269763bff8 100644
--- a/docs/api/notifications-service.yaml
+++ b/docs/api/notifications-service.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: Org.Eclipse.TractusX.Portal.Backend.Notifications.Service
- version: v2.3.0-RC3
+ version: v2.3.0-RC4
paths:
/api/notification/errormessage:
get:
diff --git a/docs/api/registration-service.yaml b/docs/api/registration-service.yaml
index ed6898922f..64ad9c4d99 100644
--- a/docs/api/registration-service.yaml
+++ b/docs/api/registration-service.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: Org.Eclipse.TractusX.Portal.Backend.Registration.Service
- version: v2.3.0-RC3
+ version: v2.3.0-RC4
paths:
/api/registration/errormessage:
get:
diff --git a/docs/api/services-service.yaml b/docs/api/services-service.yaml
index 23fb44d3df..56597210ea 100644
--- a/docs/api/services-service.yaml
+++ b/docs/api/services-service.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.1
info:
title: Org.Eclipse.TractusX.Portal.Backend.Services.Service
- version: v2.3.0-RC3
+ version: v2.3.0-RC4
paths:
/api/services/errormessage:
get:
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 7df3464fe3..b1a975343f 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -20,6 +20,6 @@
2.3.0
- RC3
+ RC4
diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs
index af6f2d65f4..0c9dda7aa7 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs
@@ -23,81 +23,76 @@
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 seederConfiguration = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticationFlows);
+ var authFlowExecutionConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticationFlowExecution);
+ var authenticatorConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.AuthenticatorConfig);
- 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, 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, 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))
+ 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)
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, 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}");
- await _keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
- await UpdateAuthenticationFlowExecutions(addFlow.Alias, cancellationToken);
+ if (seederConfig.ModificationAllowed(ModificationType.Create, addFlow.Alias))
+ {
+ await keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
+ }
+
+ await UpdateAuthenticationFlowExecutions(addFlow.Alias, authenticatorConfig, authFlowExecutionConfig, cancellationToken);
}
}
- private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken)
+ 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,
@@ -110,13 +105,14 @@ private async Task UpdateExistingAuthenticationFlows(IEnumerable new AuthenticationFlow
+ private static AuthenticationFlow CreateUpdateAuthenticationFlow(string? id, AuthenticationFlowModel update) => new()
{
Id = id,
Alias = update.Alias,
@@ -132,18 +128,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, KeycloakSeederConfigModel authenticatorConfig, KeycloakSeederConfigModel authFlowExecutionConfig, 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, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
+ await AddExecutionsRecursive(alias, updateExecutions, authFlowExecutionConfig, 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, authenticatorConfig, authFlowExecutionConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}
private bool CompareStructureRecursive(IReadOnlyList executions, IEnumerable updateExecutions) =>
@@ -153,44 +150,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, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken)
{
- foreach (var executionNode in executionNodes)
+ foreach (var executionNode in executionNodes.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Delete, x.Execution.Id)))
{
if (executionNode.Execution.AuthenticationFlow ?? false)
{
- await DeleteExecutionsRecursive(executionNode.Children, 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);
+
+ 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, KeycloakSeederConfigModel authFlowExecutionConfig, CancellationToken cancellationToken)
{
- foreach (var execution in seedExecutions)
+ foreach (var execution in seedExecutions.Where(x => authFlowExecutionConfig.ModificationAllowed(ModificationType.Create, 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), authFlowExecutionConfig, 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, 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))
+ 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");
@@ -202,19 +200,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 +317,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 +327,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..1593d9fb1e 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.GetSpecificConfiguration(ConfigurationKey.ClientScopes);
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,23 @@ 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, KeycloakSeederConfigModel 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
+ .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).IfAnyAwait(rolesToDelete =>
- keycloak.RemoveClientRolesFromClientScopeForClientAsync(realm, clientScopeId, clientId, rolesToDelete, cancellationToken)).ConfigureAwait(false);
+ 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 6419a4f844..a9ecfef54d 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs
@@ -22,37 +22,38 @@
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.GetSpecificConfiguration(ConfigurationKey.ClientScopes);
var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
- var seedClientScopes = _seedData.ClientScopes;
+ var seedClientScopes = seedDataHandler.ClientScopes;
- 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 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 RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken)
+ 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)
{
- foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => 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,
@@ -61,32 +62,38 @@ 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, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
- foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => 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);
}
}
- 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, 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)))
{
- 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, KeycloakSeederConfigModel 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(
@@ -99,14 +106,16 @@ 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.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, 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))
+ foreach (var mapper in mappers
+ .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKey.ProtocolMappers, ModificationType.Delete, x.Name))
+ .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name))
{
await keycloak.DeleteProtocolMapperAsync(
realm,
@@ -116,9 +125,11 @@ 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 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))
+ foreach (var update in updateMappers
+ .Where(x => seederConfig.ModificationAllowed(clientScopeName, ConfigurationKey.ProtocolMappers, ModificationType.Create, x.Name))
+ .ExceptBy(mappers.Select(x => x.Name), x => x.Name))
{
await keycloak.CreateProtocolMapperAsync(
realm,
@@ -128,13 +139,15 @@ 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 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))
+ 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(
diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs
index 92582fd248..9e7b9a11c6 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs
@@ -24,35 +24,31 @@
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.GetSpecificConfiguration(ConfigurationKey.Clients);
+ var clientScopesSeederConfig = seedDataHandler.GetSpecificConfiguration(ConfigurationKey.ClientScopes);
+
+ return seedDataHandler.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, seederConfig, clientScopesSeederConfig, cancellationToken));
}
- 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, 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");
- 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 +56,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(ModificationType.Create, update.ClientId))
+ {
+ continue;
+ }
+
client = await CreateClient(keycloak, realm, update, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}
else
{
- await UpdateClient(
+ if (seederConfig.ModificationAllowed(ModificationType.Update, update.ClientId))
+ {
+ 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 +93,7 @@ await UpdateDefaultClientScopes(
client,
update,
GetClientScopeId,
+ clientScopesSeederConfig,
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
await UpdateOptionalClientScopes(
@@ -97,6 +103,7 @@ await UpdateOptionalClientScopes(
client,
update,
GetClientScopeId,
+ clientScopesSeederConfig,
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
yield return (update.ClientId, client.Id);
@@ -110,6 +117,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,22 +135,31 @@ 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, 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).Select(x => x.Id ?? throw new ConflictException($"protocolMapper.Id is null {x.Name}")))
+ foreach (var mapperId in clientProtocolMappers
+ .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}")))
{
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
+ .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)))
{
await keycloak.CreateClientProtocolMapperAsync(realm, clientId, mapper, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}
foreach (var (mapperId, mapper) in clientProtocolMappers
+ .Where(x => seederConfig.ModificationAllowed(client.ClientId, ConfigurationKey.ClientProtocolMapper, ModificationType.Update, x.Name))
.Join(
updateProtocolMappers,
x => x.Name,
@@ -157,33 +174,45 @@ 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, 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))
+ 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))
+ 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);
}
}
- 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, 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))
+ 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))
+ 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 9a2beecac6..10dd81fe55 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs
@@ -61,4 +61,6 @@ public interface ISeedDataHandler
IEnumerable GetAuthenticationExecutions(string? alias);
AuthenticatorConfigModel GetAuthenticatorConfig(string? alias);
+ KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey);
+ bool IsModificationAllowed(ConfigurationKey configKey);
}
diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs
index d78892a450..63cbabc6e7 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.GetSpecificConfiguration(ConfigurationKey.IdentityProviders);
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 (seederConfig.ModificationAllowed(ModificationType.Update, updateIdentityProvider.Alias) && !CompareIdentityProvider(identityProvider, updateIdentityProvider))
{
UpdateIdentityProvider(identityProvider, updateIdentityProvider);
await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
@@ -51,23 +53,28 @@ 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(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, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
- foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name))
+ foreach (var mapper in updateMappers
+ .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Create, x.Name))
+ .ExceptBy(mappers.Select(x => x.Name), x => x.Name))
{
await keycloak.AddIdentityProviderMapperAsync(
realm,
@@ -83,16 +90,16 @@ 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, 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)))
+ .Where(x => !CompareIdentityProviderMapper(x.Mapper, x.Update)))
{
await keycloak.UpdateIdentityProviderMapperAsync(
realm,
@@ -103,21 +110,23 @@ 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, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
- if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name).IfAny(
- async deleteMappers =>
- {
- foreach (var mapper in deleteMappers)
+ if (mappers
+ .Where(x => seederConfig.ModificationAllowed(alias, ConfigurationKey.IdentityProviderMappers, ModificationType.Delete, x.Name))
+ .ExceptBy(updateMappers.Select(x => x.Name), x => x.Name)
+ .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..cd3ad2983e 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,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 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 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 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(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 Task CheckAndExecuteUpdater(ConfigurationKey configKey, string instanceName, Func updaterExecution, CancellationToken cancellationToken) =>
+ seedDataHandler.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 c6b8840ecc..b867723bed 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.GetSpecificConfiguration(ConfigurationKey.Localizations);
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);
- foreach (var deleteTranslation in
- localizations.ExceptBy(updateRealmLocalizations.Select(t => t.Locale), locale => locale))
+ await UpdateLocaleTranslations(keycloak, realm, localizations, updateRealmLocalizations, seederConfig, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
+ 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);
}
}
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,
+ KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
if (!await locales
.Join(
@@ -56,50 +61,55 @@ 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,
+ KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
- foreach (var deleteTranslation in
- localizations.ExceptBy(update.Translations.Select(t => t.Key),
- l => l.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);
}
}
- 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,
+ KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
- foreach (var missingTranslation in update.Translations.ExceptBy(localizations.Select(loc => loc.Key),
- locModel => locModel.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);
}
foreach (var updateTranslation in
- localizations.Join(
- update.Translations,
- l => l.Key,
- trans => trans.Key,
- (l, trans) => (Key: l.Key, Update: trans)))
+ 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);
}
}
private static async Task AddLocales(KeycloakClient keycloak, string realm, IEnumerable<(string Locale, IEnumerable> Translations)> translations,
- CancellationToken cancellationToken)
+ KeycloakSeederConfigModel 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(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..304c58b83d 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs
@@ -22,62 +22,64 @@
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.GetSpecificConfiguration(ConfigurationKey.ClientRoles);
- 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
+ .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, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
+ await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, seederConfig, 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.GetSpecificConfiguration(ConfigurationKey.Roles);
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
+ .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, 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, 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(
- updateRoles,
- role => role.Name,
- roleModel => roleModel.Name,
- (role, roleModel) => (Role: role, Update: roleModel)))
+ 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))
{
@@ -90,8 +92,9 @@ private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string r
}
}
- foreach (var deleteRole in
- roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.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}");
@@ -102,106 +105,154 @@ 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.GetSpecificConfiguration(ConfigurationKey.Roles);
- 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),
+ 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,
+ realm,
+ seederConfig,
+ seedDataHandler.RealmRoles,
() => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken),
- _seedData.RealmRoles,
(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);
+ }
- async Task UpdateCompositeRolesInner(
- Func>> getRoles,
- IEnumerable updateRoles,
- Func, Task> removeCompositeRoles,
- Func, Task> addCompositeRoles)
+ 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);
+
+ await RemoveRoles(keycloak, realm, removeCompositeRoles, rolePredicate, removeComposites, cancellationToken);
+
+ 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: _seedData.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));
- var removeComposites = roles.Where(x => compositeRolesPredicate(x)).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)}]"))}"));
+
+ 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);
- 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 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 composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role));
- await removeCompositeRoles(remove.Name, composites).ConfigureAwait(ConfigureAwaitOptions.None);
- }
+ private static async Task RemoveRoles(
+ KeycloakClient keycloak,
+ string realm,
+ Func, Task> removeCompositeRoles,
+ Func rolePredicate,
+ IEnumerable removeComposites,
+ CancellationToken cancellationToken)
+ {
+ 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 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).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);
}
}
diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs
index 49977033d8..6d6528022c 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;
@@ -37,6 +38,8 @@ public class SeedDataHandler : ISeedDataHandler
private KeycloakRealm? _keycloakRealm;
private IReadOnlyDictionary? _idOfClients;
+ private SeederConfigurationModel? _defaultConfiguration;
+ private IReadOnlyDictionary? _flatConfiguration;
public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken)
{
@@ -46,6 +49,8 @@ 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 = realmSettings.GetConfigurationDictionaries();
+ _flatConfiguration = realmSettings.GetFlatDictionary();
_idOfClients = null;
}
@@ -58,6 +63,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}");
@@ -142,6 +148,7 @@ public async Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string
{
clientIds[clientId] = id;
}
+
_idOfClients = clientIds.ToImmutableDictionary();
}
@@ -157,4 +164,14 @@ 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) =>
+ 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) =>
+ _flatConfiguration?.TryGetValue(configKey, out var result) ?? false
+ ? result
+ : (_defaultConfiguration ?? throw new ConflictException("configuration must not be null")).Create || _defaultConfiguration.Update || _defaultConfiguration.Delete;
}
diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UserProfileUpdater.cs
index 812abf9692..7dcb8c47b4 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.GetSpecificConfiguration(ConfigurationKey.UserProfile);
+ if (!defaultConfig.ModificationAllowed(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..af712e3304 100644
--- a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs
+++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs
@@ -24,35 +24,36 @@
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.GetSpecificConfiguration(ConfigurationKey.Users);
- 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 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)
+ 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)
@@ -60,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
+ else if (user != null && updateAllowed)
{
await UpdateUser(
keycloak,
@@ -68,17 +69,21 @@ 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, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken)
{
if (user.Id == null)
throw new ConflictException($"user.Id must not be null: userName {seedUser.Username}");
- if (!CompareUser(user, seedUser))
+ if (user.UserName == null)
+ throw new ConflictException($"user.UserName must not be null: userName {seedUser.Username}");
+
+ if (!CompareUser(user, seedUser) && seederConfig.ModificationAllowed(ModificationType.Update, user.UserName))
{
await keycloak.UpdateUserAsync(
realm,
@@ -98,8 +103,10 @@ await UpdateClientAndRealmRoles(
await UpdateFederatedIdentities(
keycloak,
realm,
+ user.UserName,
user.Id,
seedUser.FederatedIdentities ?? Enumerable.Empty(),
+ seederConfig,
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}
@@ -210,17 +217,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 username, string userId, IEnumerable updates, KeycloakSeederConfigModel 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, 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, 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))
+ foreach (var identity in identities
+ .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Delete, x.IdentityProvider))
+ .ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider))
{
await keycloak.RemoveUserSocialLoginProviderAsync(
realm,
@@ -230,9 +239,11 @@ 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 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))
+ foreach (var update in updates
+ .Where(x => seederConfig.ModificationAllowed(username, ConfigurationKey.FederatedIdentities, ModificationType.Create, x.IdentityProvider))
+ .ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider))
{
await keycloak.AddUserSocialLoginProviderAsync(
realm,
@@ -248,9 +259,10 @@ 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 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,
diff --git a/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs
new file mode 100644
index 0000000000..de48e354fa
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/Extensions/KeycloakRealmSettingsExtensions.cs
@@ -0,0 +1,56 @@
+/********************************************************************************
+ * 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;
+using System.Collections.Immutable;
+
+namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Extensions;
+
+public static class KeycloakRealmSettingsExtensions
+{
+ 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)))
+ .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?.ToImmutableDictionary(sc =>
+ sc.Key,
+ ConvertSeederConfigToSeederConfigurationModel));
+
+ private static SeederConfigurationModel ConvertSeederConfigToSeederConfigurationModel(this SeederConfiguration seederConfig) =>
+ new(
+ seederConfig.Create,
+ seederConfig.Update,
+ seederConfig.Delete,
+ seederConfig.SeederConfigurations?.ToImmutableDictionary(sc =>
+ sc.Key,
+ ConvertSeederConfigToSeederConfigurationModel));
+}
diff --git a/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs
new file mode 100644
index 0000000000..facbe9ff71
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/Extensions/SeederConfigurationExtensions.cs
@@ -0,0 +1,88 @@
+/********************************************************************************
+ * 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 KeycloakSeederConfigModel config, ModificationType modificationType) =>
+ config.ModificationAllowed(modificationType, null);
+
+ public static bool ModificationAllowed(this KeycloakSeederConfigModel config, ModificationType modificationType, string? entityKey)
+ {
+ 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, out var specificEntry) ?? false)
+ {
+ 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) ?? 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)
+ {
+ // 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, 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(), out var containingEntityTypeConfig) ?? false))
+ {
+ 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
+ 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
+ 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
+ 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) =>
+ 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/ConfigurationKey.cs b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKey.cs
new file mode 100644
index 0000000000..edbc1b6aae
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/Models/ConfigurationKey.cs
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * 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 ConfigurationKey
+{
+ 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 5ac852d022..bb4acfdf09 100644
--- a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs
+++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealmSettings.cs
@@ -32,6 +32,11 @@ public class KeycloakRealmSettings
[Required]
[DistinctValues]
public IEnumerable DataPaths { get; set; } = null!;
+ public bool Create { get; set; }
+ public bool Update { get; set; }
+ public bool Delete { get; set; }
+ [DistinctValues("x => x.Key")]
+ public IEnumerable? SeederConfigurations { get; set; }
public string? Id { get; set; }
public string? DisplayName { get; set; }
public string? DisplayNameHtml { 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
new file mode 100644
index 0000000000..9337636143
--- /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(
+ SeederConfigurationModel DefaultSettings,
+ SeederConfigurationModel? SpecificConfiguration
+);
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..dc66e7c713
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/Models/SeederConfiguration.cs
@@ -0,0 +1,35 @@
+/********************************************************************************
+ * 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;
+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; }
+ public bool Delete { get; set; }
+
+ [DistinctValues]
+ public IEnumerable? SeederConfigurations { get; set; }
+}
diff --git a/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs b/src/keycloak/Keycloak.Seeding/Models/SeederConfigurationModel.cs
new file mode 100644
index 0000000000..9cbe4f6cef
--- /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,
+ IReadOnlyDictionary? SeederConfigurations
+);
diff --git a/src/keycloak/Keycloak.Seeding/README.md b/src/keycloak/Keycloak.Seeding/README.md
new file mode 100644
index 0000000000..c9e95685b6
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/README.md
@@ -0,0 +1,151 @@
+# 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
+ "Realms": [
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ ]
+```
+
+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 SeederConfigurations array in the seeder configuration can be used.
+
+**Example**:
+
+```json
+ "SeederConfigurations": [
+ {
+ "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 `Localization` 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
+ "SeederConfigurations": [
+ {
+ "Key": "Localizations",
+ "Create": true,
+ "Update": false,
+ "Delete": true,
+ "SeederConfigurations": [
+ {
+ "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. Keys are case-sensitive.
+
+## 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
+ "SeederConfigurations": [
+ {
+ "Key": "Users",
+ "Create": true,
+ "Update": false,
+ "Delete": false,
+ "SeederConfigurations": [
+ {
+ "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd",
+ "Create": true,
+ "Update": true,
+ "Delete": false,
+ "SeederConfigurations": [
+ {
+ "Key": "FederatedIdentities",
+ "Create": false,
+ "Update": false,
+ "Delete": false,
+ "SeederConfigurations": [
+ {
+ "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).
+
+- SPDX-License-Identifier: Apache-2.0
+- SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation
+- Source URL:
diff --git a/src/keycloak/Keycloak.Seeding/appsettings.example.json b/src/keycloak/Keycloak.Seeding/appsettings.example.json
new file mode 100644
index 0000000000..b6193cba7f
--- /dev/null
+++ b/src/keycloak/Keycloak.Seeding/appsettings.example.json
@@ -0,0 +1,530 @@
+{
+ "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"
+ ],
+ "Create": true,
+ "Update": false,
+ "Delete": true,
+ "SeederConfigurations": [
+ {
+ "Key": "Roles",
+ "Create": false,
+ "Update": false,
+ "Delete": false,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "Localizations",
+ "Create": true,
+ "Update": false,
+ "Delete": true,
+ "SeederConfigurations": [
+ {
+ "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,
+ "SeederConfigurations": [
+ {
+ "Key": "e69c1397-eee8-434a-b83b-dc7944bb9bdd",
+ "Create": true,
+ "Update": true,
+ "Delete": false,
+ "SeederConfigurations": [
+ {
+ "Key": "FederatedIdentities",
+ "Create": false,
+ "Update": false,
+ "Delete": false,
+ "SeederConfigurations": [
+ {
+ "Key": "CX-Operator",
+ "Create": true,
+ "Update": true,
+ "Delete": true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "Key": "FederatedIdentities",
+ "Create": false,
+ "Update": false,
+ "Delete": false,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "ClientScopeMappers",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "ProtocolMappers",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "AuthenticationFlows",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "AuthenticationFlowExecution",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "ClientProtocolMappers",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "ClientRoles",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ },
+ {
+ "Key": "AuthenticatorConfig",
+ "Create": true,
+ "Update": true,
+ "Delete": true,
+ "SeederConfigurations": []
+ }
+ ],
+ "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 eba5322645..9d9c34c565 100644
--- a/src/keycloak/Keycloak.Seeding/appsettings.json
+++ b/src/keycloak/Keycloak.Seeding/appsettings.json
@@ -36,7 +36,10 @@
{
"Realm": "",
"InstanceName": "",
- "DataPaths": []
+ "DataPaths": [],
+ "Create": true,
+ "Update": true,
+ "Delete": true
}
]
}
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..7152596a6e
--- /dev/null
+++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/KeycloakRealmSettingsTests.cs
@@ -0,0 +1,189 @@
+/********************************************************************************
+ * 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;
+
+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()
+ {
+ 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",
+ Create = false,
+ Update = false,
+ Delete = false
+ },
+ new()
+ {
+ Key = "FEDERATEDIdentities",
+ Create = true,
+ Update = true,
+ Delete = true
+ },
+ new()
+ {
+ Key = "Users",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser1",
+ Create = false,
+ Update = false,
+ Delete = false
+ },
+ new()
+ {
+ Key = "testUser2",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "FederatedIdentities",
+ Create = true,
+ Update = true,
+ Delete = false
+ }
+ ]
+ }
+ ]
+ },
+ new()
+ {
+ Key = "Clients",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testClient",
+ Create = false,
+ Update = false,
+ Delete = false
+ }
+ ]
+ }
+ ]
+ };
+
+ // Act
+ var result = realmSettings.GetFlatDictionary();
+
+ // Assert
+ 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
+ );
+ }
+
+ [Fact]
+ public void GetConfigurationDictionaries_WithNestedConfigurations_ReturnsExpected()
+ {
+ // Arrange
+ var realmSettings = new KeycloakRealmSettings
+ {
+ Create = true,
+ Update = false,
+ Delete = true,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "Users",
+ Create = true,
+ Update = false,
+ Delete = true,
+ SeederConfigurations =
+ [
+ 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 && !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);
+ }
+}
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..3c3a0f1f8c
--- /dev/null
+++ b/tests/keycloak/Keycloak.Seeding.Tests/Extensions/SeederConfigurationExtensionsTests.cs
@@ -0,0 +1,511 @@
+/********************************************************************************
+ * 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;
+
+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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = true,
+ Update = false,
+ Delete = false
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ 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 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = true,
+ Update = false,
+ Delete = false,
+ 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 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = ConfigurationKey.FederatedIdentities.ToString(),
+ Create = true,
+ Update = false,
+ Delete = false,
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = ConfigurationKey.FederatedIdentities.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "fi1",
+ Create = true,
+ Update = false,
+ Delete = false,
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser",
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = ConfigurationKey.FederatedIdentities.ToString(),
+ Create = true,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "fi1",
+ Create = false,
+ Update = false,
+ Delete = false,
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser",
+ Create = false,
+ Update = false,
+ Delete = false
+ }
+ ]
+ },
+ new()
+ {
+ Key = ConfigurationKey.FederatedIdentities.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "fi",
+ Create = true,
+ Update = false,
+ Delete = false,
+ }
+ ]
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "testUser",
+ Create = false,
+ Update = false,
+ Delete = false
+ }
+ ]
+ },
+ new()
+ {
+ Key = ConfigurationKey.FederatedIdentities.ToString(),
+ Create = true,
+ Update = false,
+ Delete = false,
+ SeederConfigurations =
+ [
+ new()
+ {
+ Key = "fi",
+ Create = false,
+ Update = false,
+ Delete = false,
+ }
+ ]
+ }
+ ]
+ };
+ var defaultSettings = settings.GetConfigurationDictionaries();
+ defaultSettings.SeederConfigurations.Should().NotBeNull();
+
+ 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()
+ {
+ Key = ConfigurationKey.Users.ToString(),
+ Create = false,
+ Update = false,
+ Delete = false,
+ 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 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