Skip to content

Commit

Permalink
feat(seeding): adjust configuration for seeding
Browse files Browse the repository at this point in the history
Refs: #1172
  • Loading branch information
Phil91 committed Nov 27, 2024
1 parent 6690aed commit 7e45f50
Show file tree
Hide file tree
Showing 11 changed files with 875 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public interface ISeedDataHandler

IEnumerable<(string ProviderType, ComponentModel ComponentModel)> RealmComponents { get; }
IEnumerable<(string Locale, IEnumerable<KeyValuePair<string, string>> Translations)> RealmLocalizations { get; }
SeederConfiguration Configuration { get; }

Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds);

Expand All @@ -63,4 +62,5 @@ public interface ISeedDataHandler

AuthenticatorConfigModel GetAuthenticatorConfig(string? alias);
KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey);
bool IsModificationAllowed(ConfigurationKey configKey);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public async Task Seed(CancellationToken cancellationToken)
}

private Task CheckAndExecuteUpdater(ConfigurationKey configKey, string instanceName, Func<string, CancellationToken, Task> updaterExecution, CancellationToken cancellationToken) =>
seedDataHandler.Configuration.IsModificationAllowed(configKey)
seedDataHandler.IsModificationAllowed(configKey)
? updaterExecution(instanceName, cancellationToken)
: Task.CompletedTask;
}
202 changes: 122 additions & 80 deletions src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,98 +114,140 @@ public async Task UpdateCompositeRoles(string keycloakInstanceName, Cancellation
var id = seedDataHandler.GetIdOfClient(clientId);

await UpdateCompositeRolesInner(
() => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken),
keycloak,
realm,
seederConfig,
updateRoles,
() => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken),
(name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, id, name, roles, cancellationToken),
(name, roles) => keycloak.AddCompositesToRoleAsync(realm, id, name, roles, cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.None);
(name, roles) => keycloak.AddCompositesToRoleAsync(realm, id, name, roles, cancellationToken),
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}

await UpdateCompositeRolesInner(
() => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken),
keycloak,
realm,
seederConfig,
seedDataHandler.RealmRoles,
() => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken),
(name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, name, roles, cancellationToken),
(name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken)).ConfigureAwait(ConfigureAwaitOptions.None);
(name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken),
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None);
}

public async Task UpdateCompositeRolesInner(
KeycloakClient keycloak,
string realm,
KeycloakSeederConfigModel seederConfig,
IEnumerable<RoleModel> updateRoles,
Func<Task<IEnumerable<Role>>> getRoles,
Func<string, IEnumerable<Role>, Task> removeCompositeRoles,
Func<string, IEnumerable<Role>, 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<T>(
KeycloakClient keycloak,
string realm,
KeycloakSeederConfigModel seederConfig,
IEnumerable<RoleModel> updateRoles,
IEnumerable<Role> roles,
Func<string, IEnumerable<Role>, Task> removeCompositeRoles,
Func<string, IEnumerable<Role>, Task> addCompositeRoles,
Func<RoleModel, bool> compositeRolesUpdatePredicate,
Func<Role, bool> compositeRolesPredicate,
Func<Role, bool> rolePredicate,
Func<RoleModel, IEnumerable<T>> joinUpdateSelector,
Func<Role, T> joinUpdateKey,
Func<T, ValueTask<Role>> getRoleByName,
CancellationToken cancellationToken)
{
var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x));
var removeComposites = roles.Where(x => compositeRolesPredicate(x)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name);

async Task UpdateCompositeRolesInner(
Func<Task<IEnumerable<Role>>> getRoles,
IEnumerable<RoleModel> updateRoles,
Func<string, IEnumerable<Role>, Task> removeCompositeRoles,
Func<string, IEnumerable<Role>, Task> addCompositeRoles)
await RemoveRoles<T>(keycloak, realm, removeCompositeRoles, rolePredicate, cancellationToken, removeComposites);

var joinedComposites = roles.Join(
updateComposites,
role => role.Name,
roleModel => roleModel.Name,
(role, roleModel) => (
Role: role,
Update: joinUpdateSelector(roleModel)));

foreach (var (role, updates) in joinedComposites)
{
var roles = await getRoles().ConfigureAwait(ConfigureAwaitOptions.None);

await RemoveAddCompositeRolesInner<(string ContainerId, string Name)>(
roleModel => roleModel.Composites?.Client?.Any() ?? false,
role => role.Composites?.Client?.Any() ?? false,
role => role.ClientRole ?? false,
roleModel => roleModel.Composites?.Client?
.FilterNotNullValues()
.Select(x => (
Id: seedDataHandler.GetIdOfClient(x.Key),
Names: x.Value))
.SelectMany(x => x.Names.Select(name => (x.Id, name))) ?? throw new ConflictException($"roleModel.Composites.Client is null: {roleModel.Id} {roleModel.Name}"),
role => (
role.ContainerId ?? throw new ConflictException($"role.ContainerId is null: {role.Id} {role.Name}"),
role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}")),
async x => await keycloak.GetRoleByNameAsync(realm, x.ContainerId, x.Name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)
).ConfigureAwait(ConfigureAwaitOptions.None);

await RemoveAddCompositeRolesInner(
roleModel => roleModel.Composites?.Realm?.Any() ?? false,
role => role.Composites?.Realm?.Any() ?? false,
role => !(role.ClientRole ?? false),
roleModel => roleModel.Composites?.Realm ?? throw new ConflictException($"roleModel.Composites.Realm is null: {roleModel.Id} {roleModel.Name}"),
role => role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}"),
async name => await keycloak.GetRoleByNameAsync(realm, name, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)
).ConfigureAwait(ConfigureAwaitOptions.None);

async Task RemoveAddCompositeRolesInner<T>(
Func<RoleModel, bool> compositeRolesUpdatePredicate,
Func<Role, bool> compositeRolesPredicate,
Func<Role, bool> rolePredicate,
Func<RoleModel, IEnumerable<T>> joinUpdateSelector,
Func<Role, T> joinUpdateKey,
Func<T, ValueTask<Role>> getRoleByName)
{
var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x) && seederConfig.ModificationAllowed(ModificationType.Update, x.Name));
var removeComposites = roles.Where(x => compositeRolesPredicate(x) && seederConfig.ModificationAllowed(ModificationType.Delete, x.Id)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name);
if (role.Id == null || role.Name == null)
throw new ConflictException($"role.id or role.name must not be null {role.Id} {role.Name}");
var composites = (await keycloak.GetRoleChildrenAsync(realm, role.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role));
composites.Where(role => role.ContainerId == null || role.Name == null).IfAny(
invalid => throw new ConflictException($"composites roles containerId or name must not be null: {string.Join(" ", invalid.Select(x => $"[{string.Join(",", x.Id, x.Name, x.Description, x.ContainerId)}]"))}"));

foreach (var remove in removeComposites)
{
if (remove.Id == null || remove.Name == null)
throw new ConflictException($"role.id or role.name must not be null {remove.Id} {remove.Name}");
var remove = composites.ExceptBy(updates, role => joinUpdateKey(role)).Where(x => seederConfig.ModificationAllowed(ModificationType.Delete, role.Name) || seederConfig.ModificationAllowed(ModificationType.Delete, x.Name));
await removeCompositeRoles(role.Name, remove).ConfigureAwait(ConfigureAwaitOptions.None);

var composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role));
await removeCompositeRoles(remove.Name, composites).ConfigureAwait(ConfigureAwaitOptions.None);
}
var add = await updates.Except(composites.Select(role => joinUpdateKey(role)))
.ToAsyncEnumerable()
.SelectAwait(x => getRoleByName(x))
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ModificationType.Create, role.Name) || seederConfig.ModificationAllowed(ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None);
}
}

var joinedComposites = roles.Join(
updateComposites,
role => role.Name,
roleModel => roleModel.Name,
(role, roleModel) => (
Role: role,
Update: joinUpdateSelector(roleModel)));

foreach (var (role, updates) in joinedComposites)
{
if (role.Id == null || role.Name == null)
throw new ConflictException($"role.id or role.name must not be null {role.Id} {role.Name}");
var composites = (await keycloak.GetRoleChildrenAsync(realm, role.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role));
composites.Where(role => role.ContainerId == null || role.Name == null).IfAny(
invalid => throw new ConflictException($"composites roles containerId or name must not be null: {string.Join(" ", invalid.Select(x => $"[{string.Join(",", x.Id, x.Name, x.Description, x.ContainerId)}]"))}"));

var remove = composites.ExceptBy(updates, role => joinUpdateKey(role));
await removeCompositeRoles(role.Name, remove).ConfigureAwait(ConfigureAwaitOptions.None);

var add = await updates.Except(composites.Select(role => joinUpdateKey(role)))
.ToAsyncEnumerable()
.SelectAwait(x => getRoleByName(x))
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
await addCompositeRoles(role.Name, add.Where(x => seederConfig.ModificationAllowed(ModificationType.Create, x.Name))).ConfigureAwait(ConfigureAwaitOptions.None);
}
}
private static async Task RemoveRoles<T>(KeycloakClient keycloak, string realm, Func<string, IEnumerable<Role>, Task> removeCompositeRoles,
Func<Role, bool> rolePredicate, CancellationToken cancellationToken, IEnumerable<Role> removeComposites)
{
foreach (var remove in removeComposites)
{
if (remove.Id == null || remove.Name == null)
throw new ConflictException($"role.id or role.name must not be null {remove.Id} {remove.Name}");

var composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)).Where(role => rolePredicate(role));
await removeCompositeRoles(remove.Name, composites).ConfigureAwait(ConfigureAwaitOptions.None);
}
}

Expand Down
40 changes: 17 additions & 23 deletions src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,11 +36,10 @@ public class SeedDataHandler : ISeedDataHandler
PropertyNameCaseInsensitive = false
};

private readonly IDictionary<ConfigurationKey, SeederConfiguration?> _specificConfigurations = new Dictionary<ConfigurationKey, SeederConfiguration?>();

private KeycloakRealm? _keycloakRealm;
private IReadOnlyDictionary<string, string>? _idOfClients;
private SeederConfiguration? _defaultConfiguration;
private SeederConfigurationModel? _defaultConfiguration;
private Dictionary<string, (bool Create, bool Update, bool Delete)>? _flatConfiguration;

public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken cancellationToken)
{
Expand All @@ -49,16 +49,10 @@ public async Task Import(KeycloakRealmSettings realmSettings, CancellationToken
async (importRealm, path) => importRealm.Merge(await ReadJsonRealm(path, realmSettings.Realm, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None)),
cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None))
.Merge(realmSettings.ToModel());
_defaultConfiguration = new SeederConfiguration
{
Create = realmSettings.Create,
Update = realmSettings.Update,
Delete = realmSettings.Delete,
SeederConfigurations = realmSettings.SeederConfigurations
};
_defaultConfiguration = realmSettings.GetConfigurationDictionaries();
_flatConfiguration = realmSettings.GetFlatDictionary();

_idOfClients = null;
_specificConfigurations.Clear();
}

private static async Task<KeycloakRealm> ReadJsonRealm(string path, string realm, CancellationToken cancellationToken)
Expand All @@ -81,11 +75,6 @@ public string Realm
get => _keycloakRealm?.Realm ?? throw new ConflictException("realm must not be null");
}

public SeederConfiguration Configuration
{
get => _defaultConfiguration ?? throw new ConflictException("configuration must not be null");
}

public KeycloakRealm KeycloakRealm
{
get => _keycloakRealm ?? throw new InvalidOperationException("Import has not been called");
Expand Down Expand Up @@ -178,15 +167,20 @@ public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) =>

public KeycloakSeederConfigModel GetSpecificConfiguration(ConfigurationKey configKey)
{
if (_specificConfigurations.TryGetValue(configKey, out var specificConfiguration))
var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null");
config.SeederConfigurations.TryGetValue(configKey.ToString().ToLower(), out var specificConfiguration);
return new KeycloakSeederConfigModel(config, specificConfiguration);
}

public bool IsModificationAllowed(ConfigurationKey configKey)
{
var flatConfig = _flatConfiguration ?? throw new ConflictException("configuration must not be null");
var config = _defaultConfiguration ?? throw new ConflictException("configuration must not be null");
if (flatConfig.TryGetValue(configKey.ToString().ToLower(), out var result))
{
return new KeycloakSeederConfigModel(Configuration, specificConfiguration);
return result.Create || result.Update || result.Delete;
}

var configKeyString = configKey.ToString();

specificConfiguration = Configuration.SeederConfigurations?.SingleOrDefault(x => x.Key == configKeyString);
_specificConfigurations.Add(configKey, specificConfiguration);
return new KeycloakSeederConfigModel(Configuration, specificConfiguration);
return config.Create || config.Update || config.Delete;
}
}
Loading

0 comments on commit 7e45f50

Please sign in to comment.