Skip to content

Commit

Permalink
feat(seeding): make seeder configureable (#1174)
Browse files Browse the repository at this point in the history
Refs: #1172
Co-authored-by: Norbert Truchsess <norbert.truchsess@t-online.de>
Reviewed-By: Evelyn Gurschler <evelyn.gurschler@bmw.de>
Reviewed-By: Norbert Truchsess <norbert.truchsess@t-online.de>
  • Loading branch information
Phil91 and ntruchsess authored Nov 28, 2024
1 parent d8633e3 commit c38d4e9
Show file tree
Hide file tree
Showing 27 changed files with 2,214 additions and 367 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<Role>();
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<Role> roles, IEnumerable<Role> updateRoles, CancellationToken cancellationToken)
private static async Task AddAndDeleteRoles(KeycloakClient keycloak, string realm, string clientScopeId, string clientId, IEnumerable<Role> roles, IEnumerable<Role> 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);
}
}
85 changes: 49 additions & 36 deletions src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientScope> clientScopes, IEnumerable<ClientScopeModel> seedClientScopes, CancellationToken cancellationToken)
private static Task CheckAndExecute(ModificationType modificationType, KeycloakClient keycloak, string realm, IEnumerable<ClientScope> clientScopes, IEnumerable<ClientScopeModel> seedClientScopes, KeycloakSeederConfigModel seederConfig, CancellationToken cancellationToken, Func<KeycloakClient, string, IEnumerable<ClientScope>, IEnumerable<ClientScopeModel>, 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<ClientScope> clientScopes, IEnumerable<ClientScopeModel> 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,
Expand All @@ -61,32 +62,38 @@ await keycloak.DeleteClientScopeAsync(
}
}

private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable<ClientScope> clientScopes, IEnumerable<ClientScopeModel> seedClientScopes, CancellationToken cancellationToken)
private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable<ClientScope> clientScopes, IEnumerable<ClientScopeModel> 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<ClientScope> clientScopes, IEnumerable<ClientScopeModel> seedClientScopes, CancellationToken cancellationToken)
private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable<ClientScope> clientScopes, IEnumerable<ClientScopeModel> 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(
Expand All @@ -99,14 +106,16 @@ await keycloak.UpdateClientScopeAsync(
var mappers = clientScope.ProtocolMappers ?? Enumerable.Empty<ProtocolMapper>();
var updateMappers = update.ProtocolMappers ?? Enumerable.Empty<ProtocolMapperModel>();

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<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> updateMappers, CancellationToken cancellationToken)
private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> 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,
Expand All @@ -116,9 +125,11 @@ await keycloak.DeleteProtocolMapperAsync(
}
}

private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> updateMappers, CancellationToken cancellationToken)
private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> 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,
Expand All @@ -128,13 +139,15 @@ await keycloak.CreateProtocolMapperAsync(
}
}

private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> updateMappers, CancellationToken cancellationToken)
private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeName, string clientScopeId, IEnumerable<ProtocolMapper> mappers, IEnumerable<ProtocolMapperModel> 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(
Expand Down
Loading

0 comments on commit c38d4e9

Please sign in to comment.