From 344a3b677506cc31cb0e9fc049998a082d4fd527 Mon Sep 17 00:00:00 2001 From: Craig McClendon Date: Wed, 19 Oct 2022 11:57:33 -0500 Subject: [PATCH] init idps before auth flows for dependency reasons; add support for postlogin flows for idps Signed-off-by: Craig McClendon --- .../keycloak/config/KeycloakConfigurator.java | 2143 +++++++++-------- .../keycloak/config/util/KeycloakConfig.java | 559 ++--- 2 files changed, 1353 insertions(+), 1349 deletions(-) diff --git a/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java b/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java index 5207192..cc1bb25 100644 --- a/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java +++ b/keycloak-config/src/main/java/org/alvearie/keycloak/config/KeycloakConfigurator.java @@ -2,7 +2,7 @@ (C) Copyright IBM Corp. 2021 SPDX-License-Identifier: Apache-2.0 -*/ + */ package org.alvearie.keycloak.config; import java.util.ArrayList; @@ -49,1073 +49,1076 @@ import jakarta.json.JsonValue; public class KeycloakConfigurator { - private final Keycloak adminClient; - - public KeycloakConfigurator(Keycloak client) { - this.adminClient = client; - } - - /** - * Initializes the realm. - * @param realmName the realm name - * @param realmPg the realm property group - * @throws Exception an Exception - */ - public void initializeRealm(String realmName, PropertyGroup realmPg) throws Exception { - System.out.println("initializing realm: " + realmName); - // Create realm if it does not exist - RealmsResource realms = adminClient.realms(); - RealmRepresentation realm = getRealmByName(realms, realmName); - if (realm == null) { - realm = new RealmRepresentation(); - realm.setRealm(realmName); - realms.create(realm); - realm = getRealmByName(realms, realmName); - if (realm == null) { - throw new RuntimeException("Unable to create realm"); - } - } - - // Initialize client scopes - PropertyGroup clientScopesPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPES); - if (clientScopesPg != null) { - for (PropertyEntry clientScopePe: clientScopesPg.getProperties()) { - String clientScopeName = clientScopePe.getName(); - PropertyGroup clientScopePg = clientScopesPg.getPropertyGroup(clientScopeName); - initializeClientScope(realms.realm(realmName).clientScopes(), clientScopeName, clientScopePg); - } - } - - // Update "default" default assigned client scopes - List defaultClientScopeNames = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_DEFAULT_CLIENT_SCOPES); - if (defaultClientScopeNames != null) { - List defaultClientScopeIds = getClientScopeIds(realms.realm(realmName).clientScopes(), defaultClientScopeNames); - if (defaultClientScopeIds != null) { - List existingDefaultClientScopes = realms.realm(realmName).getDefaultDefaultClientScopes(); - for (ClientScopeRepresentation existingDefaultClientScope : existingDefaultClientScopes) { - if (!defaultClientScopeIds.contains(existingDefaultClientScope.getId())) { - realms.realm(realmName).removeDefaultDefaultClientScope(existingDefaultClientScope.getId()); - } - else { - defaultClientScopeIds.remove(existingDefaultClientScope.getId()); - } - } - for (String defaultClientScopeId : defaultClientScopeIds) { - realms.realm(realmName).addDefaultDefaultClientScope(defaultClientScopeId); - } - } - } - - // Update "default" optional assigned client scopes - List optionalClientScopeNames = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_OPTIONAL_CLIENT_SCOPES); - if (optionalClientScopeNames != null) { - List optionalClientScopeIds = getClientScopeIds(realms.realm(realmName).clientScopes(), optionalClientScopeNames); - if (optionalClientScopeIds != null) { - List existingOptionalClientScopes = realms.realm(realmName).getDefaultOptionalClientScopes(); - for (ClientScopeRepresentation existingOptionalClientScope : existingOptionalClientScopes) { - if (!optionalClientScopeIds.contains(existingOptionalClientScope.getId())) { - realms.realm(realmName).removeDefaultOptionalClientScope(existingOptionalClientScope.getId()); - } - else { - optionalClientScopeIds.remove(existingOptionalClientScope.getId()); - } - } - for (String defaultClientScopeId : optionalClientScopeIds) { - realms.realm(realmName).addDefaultOptionalClientScope(defaultClientScopeId); - } - } - } - - // Initialize clients - PropertyGroup clientsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_CLIENTS); - if (clientsPg != null) { - for (PropertyEntry clientPe: clientsPg.getProperties()) { - String clientName = clientPe.getName(); - PropertyGroup clientPg = clientsPg.getPropertyGroup(clientName); - initializeClient(realms.realm(realmName).clients(), realms.realm(realmName).clientScopes(), clientName, clientPg); - } - } - - // Initialize identity providers - PropertyGroup identityProvidersPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDERS); - if (identityProvidersPg != null) { - for (PropertyEntry identityProviderPe: identityProvidersPg.getProperties()) { - String identityProviderAlias = identityProviderPe.getName(); - PropertyGroup identityProviderPg = identityProvidersPg.getPropertyGroup(identityProviderAlias); - initializeIdentityProvider(realms.realm(realmName).identityProviders(), identityProviderAlias, identityProviderPg); - } - } - - // Initialize authentication flows - PropertyGroup authenticationFlowsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_AUTHENTICATION_FLOWS); - if (authenticationFlowsPg != null) { - for (PropertyEntry authenticationFlowPe : authenticationFlowsPg.getProperties()) { - String authenticationFlowAlias = authenticationFlowPe.getName(); - PropertyGroup authenticationFlowPg = authenticationFlowsPg.getPropertyGroup(authenticationFlowAlias); - initializeAuthenticationFlow(realms.realm(realmName).flows(), authenticationFlowAlias, authenticationFlowPg); - } - } - - // Initialize groups - PropertyGroup groupsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_GROUPS); - if (groupsPg != null) { - for (PropertyEntry groupPe: groupsPg.getProperties()) { - String groupName = groupPe.getName(); - PropertyGroup groupPg = groupsPg.getPropertyGroup(groupName); - initializeGroup(realms.realm(realmName).groups(), groupName, groupPg); - } - } - - // Update "default" groups - List defaultGroups = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_GROUPS); - if (defaultGroups != null) { - List defaultGroupIds = getGroupIds(realms.realm(realmName).groups(), defaultGroups); - if (defaultGroupIds != null) { - List existingDefaultGroups = realms.realm(realmName).getDefaultGroups(); - for (GroupRepresentation existingDefaultGroup : existingDefaultGroups) { - if (!defaultGroupIds.contains(existingDefaultGroup.getId())) { - realms.realm(realmName).removeDefaultGroup(existingDefaultGroup.getId()); - } - else { - defaultGroupIds.remove(existingDefaultGroup.getId()); - } - } - for (String defaultGroupId : defaultGroupIds) { - realms.realm(realmName).addDefaultGroup(defaultGroupId); - } - } - } - - // Initialize users - PropertyGroup usersPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_USERS); - if (usersPg != null) { - for (PropertyEntry userPe: usersPg.getProperties()) { - String userName = userPe.getName(); - PropertyGroup userPg = usersPg.getPropertyGroup(userName); - initializeUser(realms.realm(realmName).users(), realms.realm(realmName).groups(), userName, userPg); - } - } - - // Initialize events config - PropertyGroup eventsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_EVENTS_CONFIG); - if (eventsPg != null) { - initializeEventsConfig(realm, eventsPg); - } - - // Update realm settings - String browserFlow = realmPg.getStringProperty(KeycloakConfig.PROP_BROWSER_FLOW); - if (browserFlow != null) { - realm.setBrowserFlow(browserFlow); - } - realm.setEnabled(realmPg.getBooleanProperty(KeycloakConfig.PROP_REALM_ENABLED)); - realms.realm(realmName).update(realm); - } - - /** - * @param realm - * @param eventsPg - */ - void initializeEventsConfig(RealmRepresentation realm, PropertyGroup eventsPg) { - System.out.println("initializing events config"); - - // Login events - Boolean eventsEnabled = eventsPg.getBooleanProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_LOGIN_EVENTS); - if (eventsEnabled != null) { - realm.setEventsEnabled(eventsEnabled); - } - - Integer eventsExpiration = eventsPg.getIntProperty(KeycloakConfig.PROP_EVENTS_CONFIG_EXPIRATION); - if (eventsExpiration != null) { - realm.setEventsExpiration(Long.valueOf(eventsExpiration)); - } - - List saveTypes = null; - try { - saveTypes = eventsPg.getStringListProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_TYPES); - } catch (Exception e) { - System.err.println("Error while reading event save types from the config file:"); - e.printStackTrace(); - } - if (saveTypes != null) { - realm.setEnabledEventTypes(saveTypes); - } - - // Admin events - Boolean adminEventsEnabled = eventsPg.getBooleanProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_ADMIN_EVENTS); - if (adminEventsEnabled != null) { - realm.setAdminEventsEnabled(adminEventsEnabled); - } - } - - /** - * Initializes the client scopes. - * @param clientScopes the client scopes resource - * @param clientScopeName the client scope name - * @param clientScopePg the client scope property group - * @throws Exception an Exception - */ - void initializeClientScope(ClientScopesResource clientScopes, String clientScopeName, PropertyGroup clientScopePg) throws Exception { - System.out.println("initializing client scope: " + clientScopeName); - // Create client scope if it does not exist - ClientScopeRepresentation clientScope = getClientScopeByName(clientScopes, clientScopeName); - if (clientScope == null) { - clientScope = new ClientScopeRepresentation(); - clientScope.setName(clientScopeName); - clientScopes.create(clientScope); - clientScope = getClientScopeByName(clientScopes, clientScopeName); - if (clientScope == null) { - throw new RuntimeException("Unable to create client scope"); - } - } - - // Update client scope settings - clientScope.setDescription(clientScopePg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_DESCRIPTION)); - clientScope.setProtocol(clientScopePg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_PROTOCOL)); - PropertyGroup attributesPg = clientScopePg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_ATTRIBUTES); - if (attributesPg != null) { - Map attributes = clientScope.getAttributes(); - if (attributes == null) { - attributes = new HashMap<>(); - } - for (PropertyEntry attributePe: attributesPg.getProperties()) { - String attributeKey = attributePe.getName(); - attributes.put(attributeKey, attributePe.getValue() != null ? attributePe.getValue().toString() : null); - } - clientScope.setAttributes(attributes); - } - clientScopes.get(clientScope.getId()).update(clientScope); - - // Initialize protocol mappers - PropertyGroup mappersPg = clientScopePg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPERS); - if (mappersPg != null) { - for (PropertyEntry mapperPe: mappersPg.getProperties()) { - String mapperName = mapperPe.getName(); - PropertyGroup mapperPg = mappersPg.getPropertyGroup(mapperName); - initializeProtocolMapper(clientScopes.get(clientScope.getId()).getProtocolMappers(), mapperName, mapperPg); - } - } - } - - /** - * Initializes the protocol mappers of the client scope. - * @param protocolMappers the protocol mappers - * @param mapperName the protocol mapper name - * @param mapperPg the protocol mapper property group - * @throws Exception an Exception - */ - void initializeProtocolMapper(ProtocolMappersResource protocolMappers, String mapperName, PropertyGroup mapperPg) throws Exception { - System.out.println("initializing protocol mapper: " + mapperName); - // Create protocol mapper if it does not exist - ProtocolMapperRepresentation protocolMapper = getProtocolMapperByName(protocolMappers, mapperName); - if (protocolMapper == null) { - protocolMapper = new ProtocolMapperRepresentation(); - protocolMapper.setName(mapperName); - protocolMapper.setProtocol(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL)); - protocolMapper.setProtocolMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER)); - Response response = protocolMappers.createMapper(protocolMapper); - protocolMapper = getProtocolMapperByName(protocolMappers, mapperName); - if (protocolMapper == null) { - throw new RuntimeException("Unable to create protocol mapper: " + response.readEntity(String.class)); - } - } - - // Update protocol mapper settings - protocolMapper.setProtocol(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL)); - protocolMapper.setProtocolMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER)); - PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER_CONFIG); - if (configPg != null) { - Map config = protocolMapper.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - protocolMapper.setConfig(config); - } - protocolMappers.update(protocolMapper.getId(), protocolMapper); - } - - /** - * Initializes the client. - * @param clients the clients resource - * @param clientScopes the client scopes resource - * @param clientId the client id - * @param clientPg the client property group - * @throws Exception an Exception - */ - void initializeClient(ClientsResource clients, ClientScopesResource clientScopes, String clientId, PropertyGroup clientPg) throws Exception { - System.out.println("initializing client: " + clientId); - // Create client if it does not exist - ClientRepresentation client = getClientByClientId(clients, clientId); - if (client == null) { - client = new ClientRepresentation(); - client.setClientId(clientId); - clients.create(client); - client = getClientByClientId(clients, clientId); - if (client == null) { - throw new RuntimeException("Unable to create client"); - } - } - - // Update client settings - client.setName(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_NAME)); - client.setDescription(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_DESCRIPTION)); - client.setConsentRequired(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_CONSENT_REQUIRED)); - client.setStandardFlowEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_STANDARD_FLOW_ENABLED, true)); - client.setServiceAccountsEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_SERVICE_ACCOUNTS_ENABLED, false)); - - PropertyGroup attributePg = clientPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_ATTRIBUTES); - if (attributePg != null) { - setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_DEVICE_AUTH_GRANT_ENABLED); - } - - Boolean publicClient = clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_PUBLIC_CLIENT, false); - client.setPublicClient(publicClient); - - if (!publicClient) { - String clientAuthType = clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_AUTHENTICATOR_TYPE); - client.setClientAuthenticatorType(clientAuthType); - - if ("client-jwt".equals(clientAuthType) && attributePg != null) { - boolean useJwksUrl = Boolean.parseBoolean(attributePg.getStringProperty(KeycloakConfig.PROP_CLIENT_ATTR_USE_JWKS_URL, "false")); - if (useJwksUrl) { - setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_USE_JWKS_URL); - setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_JWKS_URL); - } - } - } - - client.setDirectAccessGrantsEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_DIRECT_ACCESS_ENABLED)); - client.setBearerOnly(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_BEARER_ONLY)); - client.setRootUrl(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_ROOT_URL)); - client.setRedirectUris(clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_REDIRECT_URIS)); - client.setAdminUrl(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_ADMIN_URL)); - client.setWebOrigins(clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_WEB_ORIGINS)); - clients.get(client.getId()).update(client); - - ClientResource cr = clients.get(client.getId()); - - // Remove default client scopes that no longer apply and collect the ones to add - List defaultClientScopeIdsToAdd = new ArrayList<>(); - List defaultClientScopeNameStrings = clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_DEFAULT_CLIENT_SCOPES); - if (defaultClientScopeNameStrings != null) { - List defaultClientScopeIds = getClientScopeIds(clientScopes, defaultClientScopeNameStrings); - if (defaultClientScopeIds != null) { - List existingDefaultClientScopes = cr.getDefaultClientScopes(); - for (ClientScopeRepresentation existingDefaultClientScope : existingDefaultClientScopes) { - if (!defaultClientScopeIds.contains(existingDefaultClientScope.getId())) { - cr.removeDefaultClientScope(existingDefaultClientScope.getId()); - } - else { - defaultClientScopeIds.remove(existingDefaultClientScope.getId()); - } - } - defaultClientScopeIdsToAdd.addAll(defaultClientScopeIds); - } - } - - // Remove optional client scopes that no longer apply and collect the ones to add - List optionalClientScopeIdsToAdd = new ArrayList<>(); - List optionalClientScopeNameStrings = clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_OPTIONAL_CLIENT_SCOPES); - if (optionalClientScopeNameStrings != null) { - List optionalClientScopeIds = getClientScopeIds(clientScopes, optionalClientScopeNameStrings); - if (optionalClientScopeIds != null) { - List existingOptionalClientScopes = cr.getOptionalClientScopes(); - for (ClientScopeRepresentation existingOptionalClientScope : existingOptionalClientScopes) { - if (!optionalClientScopeIds.contains(existingOptionalClientScope.getId())) { - cr.removeDefaultClientScope(existingOptionalClientScope.getId()); - } - else { - optionalClientScopeIds.remove(existingOptionalClientScope.getId()); - } - } - optionalClientScopeIdsToAdd.addAll(optionalClientScopeIds); - } - } - - // Note: if a scope already exists in either list on the server, the add call will be ignored - for (String clientScopeId : defaultClientScopeIdsToAdd) { - cr.addDefaultClientScope(clientScopeId); - } - for (String clientScopeId : optionalClientScopeIdsToAdd) { - cr.addOptionalClientScope(clientScopeId); - } - } - - /** - * Client attributes are set a little differently, so this method encapsulates the logic to get the attribute map - * and set a given property from a PropertyGroup that contains that attributes value in a property by the same name. - * @param attributesPg - * @param client - * @param propName - * @throws Exception - */ - private void setAttribute(PropertyGroup attributesPg, ClientRepresentation client, String propName) throws Exception { - client.getAttributes().put(propName, attributesPg.getStringProperty(propName)); - } - - /** - * Initializes the identity provider. - * @param identityProviders the identity providers resource - * @param identityProviderAlias the identity provider alias - * @param identityProviderPg the identity provider property group - * @throws Exception an Exception - */ - void initializeIdentityProvider(IdentityProvidersResource identityProviders, String identityProviderAlias, PropertyGroup identityProviderPg) throws Exception { - System.out.println("initializing identity provider: " + identityProviderAlias); - // Create identity provider if it does not exist - IdentityProviderRepresentation identityProvider = getIdentityProviderByAlias(identityProviders, identityProviderAlias); - if (identityProvider == null) { - identityProvider = new IdentityProviderRepresentation(); - identityProvider.setAlias(identityProviderAlias); - identityProvider.setProviderId(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_PROVIDER_ID)); - PropertyGroup configPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_CONFIG); - if (configPg != null) { - Map config = identityProvider.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - config.remove(KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET); - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - identityProvider.setConfig(config); - } - identityProviders.create(identityProvider); - identityProvider = getIdentityProviderByAlias(identityProviders, identityProviderAlias); - if (identityProvider == null) { - throw new RuntimeException("Unable to create identity provider"); - } - } - - // Update identity provider settings - identityProvider.setProviderId(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_PROVIDER_ID)); - identityProvider.setDisplayName(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_DISPLAY_NAME)); - identityProvider.setEnabled(identityProviderPg.getBooleanProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_ENABLED)); - identityProvider.setFirstBrokerLoginFlowAlias(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_FIRST_BROKER_LOGIN_FLOW_ALIAS)); - PropertyGroup configPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_CONFIG); - if (configPg != null) { - Map config = identityProvider.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - config.remove(KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET); - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - identityProvider.setConfig(config); - } - identityProviders.get(identityProvider.getAlias()).update(identityProvider); - - // Initialize identity provider mappers - PropertyGroup mappersPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPERS); - if (mappersPg != null) { - for (PropertyEntry mapperPe: mappersPg.getProperties()) { - String mapperName = mapperPe.getName(); - PropertyGroup mapperPg = mappersPg.getPropertyGroup(mapperName); - initializeIdentityProviderMapper(identityProviders.get(identityProvider.getAlias()), identityProviderAlias, mapperName, mapperPg); - } - } - } - - /** - * Initializes the mappers of the identity provider. - * @param identityProvider the identity provider - * @param identityProviderAlias the identity provider alias - * @param mapperName the identity provider mapper name - * @param mapperPg the identity provider mapper property group - * @throws Exception an Exception - */ - void initializeIdentityProviderMapper(IdentityProviderResource identityProvider, String identityProviderAlias, String mapperName, PropertyGroup mapperPg) throws Exception { - System.out.println("initializing identity provider mapper: " + mapperName); - // Create protocol mapper if it does not exist - IdentityProviderMapperRepresentation identityProviderMapper = getIdentityProvideMapperByName(identityProvider, mapperName); - if (identityProviderMapper == null) { - identityProviderMapper = new IdentityProviderMapperRepresentation(); - identityProviderMapper.setName(mapperName); - identityProviderMapper.setIdentityProviderAlias(identityProviderAlias); - identityProviderMapper.setIdentityProviderMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER)); - PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); - if (configPg != null) { - Map config = identityProviderMapper.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - identityProviderMapper.setConfig(config); - } - identityProvider.addMapper(identityProviderMapper); - identityProviderMapper = getIdentityProvideMapperByName(identityProvider, mapperName); - if (identityProviderMapper == null) { - throw new RuntimeException("Unable to create identity provider mapper"); - } - } - - // Update identity provider mapper settings - identityProviderMapper.setIdentityProviderAlias(identityProviderAlias); - identityProviderMapper.setIdentityProviderMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER)); - PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); - if (configPg != null) { - Map config = identityProviderMapper.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - identityProviderMapper.setConfig(config); - } - identityProvider.update(identityProviderMapper.getId(), identityProviderMapper); - } - - /** - * Initializes the authentication flow. - * @param authMgmt the authorization management - * @param authenticationFlowAlias the authentication flow alias - * @param authenticationFlowPg the authentication flow property group - * @throws Exception an Exception - */ - void initializeAuthenticationFlow(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, PropertyGroup authenticationFlowPg) throws Exception { - System.out.println("initializing authentication flow: " + authenticationFlowAlias); - // Get authentication flow - AuthenticationFlowRepresentation authenticationFlow = getAuthenticationFlowByAlias(authMgmt, authenticationFlowAlias); - if (authenticationFlow == null) { - authenticationFlow = new AuthenticationFlowRepresentation(); - authenticationFlow.setAlias(authenticationFlowAlias); - authenticationFlow.setTopLevel(true); - authenticationFlow.setProviderId(authenticationFlowPg.getStringProperty("providerId")); - authenticationFlow.setBuiltIn(authenticationFlowPg.getBooleanProperty("builtIn")); - - Response response = authMgmt.createFlow(authenticationFlow); - - if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { - String path = response.getLocation().getPath(); - String id = path.substring(path.lastIndexOf("/") + 1); - System.out.println("Created flow with id '" + id + "'"); - authenticationFlow.setId(id); - updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); - } else { - System.err.println("Failed to create flow; status code '" + response.getStatus() + "'"); - System.err.println(response.readEntity(String.class)); - } - } - - updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); - - // Update identity provider redirector - for (PropertyEntry authExecutionPe: authenticationFlowPg.getProperties()) { - String authExecutionType = authExecutionPe.getName(); - if (KeycloakConfig.PROP_IDENTITY_REDIRECTOR.equals(authExecutionType)) { - PropertyGroup identityProviderRedirectorPg = authenticationFlowPg.getPropertyGroup(authExecutionType); - String identityProviderRedirectorAlias = identityProviderRedirectorPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_REDIRECTOR_ALIAS); - initializeIdentityProviderRedirector(authMgmt, authenticationFlowAlias, identityProviderRedirectorAlias, identityProviderRedirectorPg); - } - } - } - - private void updateFlowWithExecutions(AuthenticationManagementResource authMgmt, PropertyGroup authenticationFlowPg, - AuthenticationFlowRepresentation authenticationFlow) throws Exception { - PropertyGroup authenticationExecutionsPg = authenticationFlowPg.getPropertyGroup("authenticationExecutions"); - JsonObject jsonObject = authenticationFlowPg.getJsonValue("authenticationExecutions").asJsonObject(); - for (String entry : jsonObject.keySet()) { - PropertyGroup entryProps = authenticationExecutionsPg.getPropertyGroup(entry); - - HashMap executionParams = new HashMap(); - - String description = entryProps.getStringProperty("description"); - executionParams.put("description", description); - - Boolean isFlow = entryProps.getBooleanProperty("authenticatorFlow", false); - if (isFlow) { - executionParams.put("alias", entry); - executionParams.put("type", "basic-flow"); - - AuthenticationExecutionInfoRepresentation executionFlow = getOrCreateExecution(authMgmt, - authenticationFlow.getAlias(), entry, isFlow, executionParams); - - // the above "alias" actually gets saved as the display name for some reason, but the alias is what we need to add subflow executions - executionFlow.setAlias(entry); - executionFlow.setRequirement(entryProps.getStringProperty("requirement")); - authMgmt.updateExecutions(authenticationFlow.getAlias(), executionFlow); - - PropertyGroup childExecutions = entryProps.getPropertyGroup("authenticationExecutions"); - for (PropertyEntry childEntry : childExecutions.getProperties()) { - // TODO: see if we can get the display name from the authenticator provider_id somehow, instead of requiring it in our config - String displayName = childEntry.getName(); - PropertyGroup childEntryPg = childExecutions.getPropertyGroup(displayName); - String authenticator = childEntryPg.getStringProperty("authenticator"); - - Boolean childIsFlow = childEntryPg.getBooleanProperty("authenticatorFlow", false); - if (childIsFlow) { - throw new UnsupportedOperationException("Nest subflows are not yet supported"); - } - - HashMap childExecutionParams = new HashMap(); - childExecutionParams.put("provider", authenticator); - AuthenticationExecutionInfoRepresentation childExecution = getOrCreateExecution(authMgmt, entry, displayName, childIsFlow, childExecutionParams); - - String configAlias = childEntryPg.getStringProperty("configAlias"); - JsonValue configJson = childEntryPg.getJsonValue("config"); - if (configJson != null) { - Map config = buildConfigMap(configJson, configAlias); - - AuthenticatorConfigRepresentation authenticatorConfig = getOrCreateAuthenticatorConfig(authMgmt, childExecution, configAlias, config); - authenticatorConfig.setConfig(config); - authMgmt.updateAuthenticatorConfig(authenticatorConfig.getId(), authenticatorConfig); - - childExecution.setAuthenticationConfig(configAlias); - } - - childExecution.setRequirement(childEntryPg.getStringProperty("requirement")); - authMgmt.updateExecutions(authenticationFlow.getAlias(), childExecution); - } - } else { - executionParams.put("authenticator", entry); - getOrCreateExecution(authMgmt, authenticationFlow.getAlias(), entry, isFlow, executionParams); - - // TODO authenticatorConfig - executionParams.put("priority", Integer.toString(entryProps.getIntProperty("priority"))); - } - - } - } - - private AuthenticatorConfigRepresentation getOrCreateAuthenticatorConfig(AuthenticationManagementResource authMgmt, - AuthenticationExecutionInfoRepresentation execution, String configAlias, Map config) { - - AuthenticatorConfigRepresentation authenticatorConfig = null; - - String configId = execution.getAuthenticationConfig(); - if (configId != null) { - authenticatorConfig = authMgmt.getAuthenticatorConfig(configId); - } else { - authenticatorConfig = new AuthenticatorConfigRepresentation(); - authenticatorConfig.setAlias(configAlias); - Response response = authMgmt.newExecutionConfig(execution.getId(), authenticatorConfig); - - if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { - String path = response.getLocation().getPath(); - String id = path.substring(path.lastIndexOf("/") + 1); - System.out.println("Created authenticator config with id '" + id + "'"); - authenticatorConfig.setId(id); - } else { - System.err.println("Failed to create authenticator config; status code '" + response.getStatus() + "'"); - System.err.println(response.readEntity(String.class)); - } - } - - return authenticatorConfig; - } - - private Map buildConfigMap(JsonValue configJson, String configAlias) { - Map config = new HashMap(); - Set> entrySet = configJson.asJsonObject().entrySet(); - for (Entry configEntry : entrySet) { - JsonValue value = configEntry.getValue(); - if (value instanceof JsonString) { - config.put(configEntry.getKey(), ((JsonString) value).getString()); - } else { - System.err.println("Expected config of type String, but found " + value.getValueType()); - } - } - return config; - } - - private AuthenticationExecutionInfoRepresentation getOrCreateExecution(AuthenticationManagementResource authMgmt, - String flowAlias, String displayName, boolean isFlow, HashMap executionParams) { - AuthenticationExecutionInfoRepresentation savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); - if (savedExecution == null) { - if (isFlow) { - authMgmt.addExecutionFlow(flowAlias, executionParams); - } else { - authMgmt.addExecution(flowAlias, executionParams); - } - savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); - } - if (savedExecution == null) { - throw new RuntimeException("Unable to create execution '" + displayName + "'"); - } - return savedExecution; - } - - /** - * Initializes the identity provider redirector. - * @param authMgmt the authorization management - * @param authenticationFlowAlias the authentication flow alias - * @param identityProviderRedirectorAlias the identity provider redirector alias - * @param identityProviderRedirectorPg the identity provider redirector property group - * @throws Exception an Exception - */ - void initializeIdentityProviderRedirector(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, String identityProviderRedirectorAlias, PropertyGroup identityProviderRedirectorPg) throws Exception { - System.out.println("initializing identity provider redirector: " + identityProviderRedirectorAlias); - // Get identity provider redirector - AuthenticationExecutionInfoRepresentation identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); - if (identityProviderRedirector == null) { - throw new RuntimeException("Identity provider redirector does not exist"); - } - - // Update identity provider redirector - identityProviderRedirector.setRequirement(identityProviderRedirectorPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_REDIRECTOR_REQUIREMENT)); - authMgmt.updateExecutions(authenticationFlowAlias, identityProviderRedirector); - identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); - if (identityProviderRedirector == null) { - throw new RuntimeException("Identity provider redirector does not exist"); - } - - // Create config representation if it does not exist - AuthenticatorConfigRepresentation configRepresentation = identityProviderRedirector.getAuthenticationConfig() != null ? authMgmt.getAuthenticatorConfig(identityProviderRedirector.getAuthenticationConfig()) : null; - if (configRepresentation == null) { - configRepresentation = new AuthenticatorConfigRepresentation(); - configRepresentation.setAlias(identityProviderRedirectorAlias); - PropertyGroup configPg = identityProviderRedirectorPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); - if (configPg != null) { - Map config = configRepresentation.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - configRepresentation.setConfig(config); - } - authMgmt.newExecutionConfig(identityProviderRedirector.getId(), configRepresentation); - identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); - if (identityProviderRedirector == null) { - throw new RuntimeException("Identity provider redirector does not exist"); - } - configRepresentation = identityProviderRedirector.getAuthenticationConfig() != null ? authMgmt.getAuthenticatorConfig(identityProviderRedirector.getAuthenticationConfig()) : null; - if (configRepresentation == null) { - throw new RuntimeException("Unable to create identity provider redirector"); - } - } - - // Update config representation - configRepresentation.setAlias(identityProviderRedirectorAlias); - PropertyGroup configPg = identityProviderRedirectorPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); - if (configPg != null) { - Map config = configRepresentation.getConfig(); - if (config == null) { - config = new HashMap<>(); - } - for (PropertyEntry configPe: configPg.getProperties()) { - String configKey = configPe.getName(); - config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); - } - configRepresentation.setConfig(config); - } - authMgmt.updateAuthenticatorConfig(configRepresentation.getId(), configRepresentation); - } - - /** - * Initializes the group. - * @param groups the groups resource - * @param groupName the group name - * @param groupPg the group property group - * @throws Exception an Exception - */ - void initializeGroup(GroupsResource groups, String groupName, PropertyGroup groupPg) throws Exception { - System.out.println("initializing group: " + groupName); - // Create group if it does not exist - GroupRepresentation group = getGroupByName(groups, groupName); - if (group == null) { - group = new GroupRepresentation(); - group.setName(groupName); - groups.add(group); - group = getGroupByName(groups, groupName); - if (group == null) { - throw new RuntimeException("Unable to create group"); - } - } - - // Update group settings - PropertyGroup attributesPg = groupPg.getPropertyGroup(KeycloakConfig.PROP_GROUP_ATTRIBUTES); - if (attributesPg != null) { - Map> attributes = group.getAttributes(); - if (attributes == null) { - attributes = new HashMap<>(); - } - for (PropertyEntry attributePe: attributesPg.getProperties()) { - String attributeKey = attributePe.getName(); - List attributeValue = PropertyGroup.convertToStringList(attributePe.getValue()); - attributes.put(attributeKey, attributeValue); - } - group.setAttributes(attributes); - } - groups.group(group.getId()).update(group); - } - - /** - * Initializes the user. - * @param users the users resource - * @param groups the groups resource - * @param userName the user name - * @param userPg the user property group - * @throws Exception an Exception - */ - void initializeUser(UsersResource users, GroupsResource groups, String userName, PropertyGroup userPg) throws Exception { - System.out.println("initializing user: " + userName); - // Create user if it does not exist - UserRepresentation user = getUserByName(users, userName); - if (user == null) { - user = new UserRepresentation(); - user.setUsername(userName); - users.create(user); - user = getUserByName(users, userName); - if (user == null) { - throw new RuntimeException("Unable to create user"); - } - } - - // Update user settings - user.setEnabled(userPg.getBooleanProperty(KeycloakConfig.PROP_USER_ENABLED)); - PropertyGroup attributesPg = userPg.getPropertyGroup(KeycloakConfig.PROP_USER_ATTRIBUTES); - if (attributesPg != null) { - Map> attributes = user.getAttributes(); - if (attributes == null) { - attributes = new HashMap<>(); - } - for (PropertyEntry attributePe: attributesPg.getProperties()) { - String attributeKey = attributePe.getName(); - List attributeValue = PropertyGroup.convertToStringList(attributePe.getValue()); - attributes.put(attributeKey, attributeValue); - } - user.setAttributes(attributes); - } - CredentialRepresentation credential = new CredentialRepresentation(); - credential.setType(KeycloakConfig.KEYCLOAK_USER_PASSWORD_TYPE); - credential.setTemporary(userPg.getBooleanProperty(KeycloakConfig.PROP_USER_PASSWORD_TEMPORARY)); - credential.setValue(userPg.getStringProperty(KeycloakConfig.PROP_USER_PASSWORD)); - user.setCredentials(Arrays.asList(credential)); - users.get(user.getId()).update(user); - - // Update user group memberships - List groupIds = getGroupIds(groups, userPg.getStringListProperty(KeycloakConfig.PROP_USER_GROUPS)); - if (groupIds != null) { - List existingGroupIds = getGroupIds(groups, user.getGroups()); - for (String existingGroupId : existingGroupIds) { - if (!groupIds.contains(existingGroupId)) { - users.get(user.getId()).leaveGroup(existingGroupId); - } - else { - groupIds.remove(existingGroupId); - } - } - for (String groupId : groupIds) { - users.get(user.getId()).joinGroup(groupId); - } - } - } - - /** - * Gets the realm by name. - * @param realmsResource the realms resource - * @param realmName the realm name - * @return the realm, or null if not found - */ - private RealmRepresentation getRealmByName(RealmsResource realmsResource, String realmName) { - for (RealmRepresentation realm : realmsResource.findAll()) { - if (realmName.equals(realm.getRealm())) { - return realm; - } - } - return null; - } - - /** - * Gets the client scope by name. - * @param clientScopes the client scopes - * @param clientScopeName the client scope name - * @return the client scope, or null if not found - */ - private ClientScopeRepresentation getClientScopeByName(ClientScopesResource clientScopes, String clientScopeName) { - for (ClientScopeRepresentation clientScope : clientScopes.findAll()) { - if (clientScopeName.equals(clientScope.getName())) { - return clientScope; - } - } - return null; - } - - /** - * Gets the client scope IDs by name. - * @param clientScopes the client scopes - * @param clientScopeNames the client scope names - * @return the client scope IDs - */ - private List getClientScopeIds(ClientScopesResource clientScopes, List clientScopeNames) { - List clientScopeIds = new ArrayList<>(); - Map nameToIdMap = clientScopes.findAll().stream().collect(Collectors.toMap(c -> c.getName(), c -> c.getId())); - - for (String clientScopeName : clientScopeNames) { - if (nameToIdMap.containsKey(clientScopeName)) { - clientScopeIds.add(nameToIdMap.get(clientScopeName)); - } else { - System.err.println("Skipping client scope '" + clientScopeName + "'; unable to find id for client scope with this name"); - } - } - return clientScopeIds; - } - - /** - * Gets the client by client ID. - * @param adminClient the clients - * @param clientName the client name - * @return the client, or null if not found - */ - private ClientRepresentation getClientByClientId(ClientsResource clients, String clientId) { - for (ClientRepresentation client : clients.findAll()) { - if (clientId.equals(client.getClientId())) { - return client; - } - } - return null; - } - - /** - * Gets the protocol mapper by name. - * @param protocolMappers the protocol mappers - * @param mapperName the mapper name - * @return the protocol mapper, or null if not found - */ - private ProtocolMapperRepresentation getProtocolMapperByName(ProtocolMappersResource protocolMappers, String mapperName) { - for (ProtocolMapperRepresentation protocolMapper : protocolMappers.getMappers()) { - if (mapperName.equals(protocolMapper.getName())) { - return protocolMapper; - } - } - return null; - } - - /** - * Gets the identity provider by provider alias. - * @param identityProviders the identity providers - * @param identityProviderAlias the identity provider alias - * @return the identity provider, or null if not found - */ - private IdentityProviderRepresentation getIdentityProviderByAlias(IdentityProvidersResource identityProviders, String identityProviderAlias) { - for (IdentityProviderRepresentation identityProvider : identityProviders.findAll()) { - if (identityProviderAlias.equals(identityProvider.getAlias())) { - return identityProvider; - } - } - return null; - } - - /** - * Gets the identity provider mapper by name. - * @param identity provider the identity provider - * @param mapperName the mapper name - * @return the identity provider mapper, or null if not found - */ - private IdentityProviderMapperRepresentation getIdentityProvideMapperByName(IdentityProviderResource identityProvider, String mapperName) { - for (IdentityProviderMapperRepresentation identityProviderMapper : identityProvider.getMappers()) { - if (mapperName.equals(identityProviderMapper.getName())) { - return identityProviderMapper; - } - } - return null; - } - - - /** - * Gets the authentication flow by alias. - * @param authMgmt the authorization management - * @param authenticationFlowAlias the authentication flow alias - * @return the authorization flow, or null if not found - */ - private AuthenticationFlowRepresentation getAuthenticationFlowByAlias(AuthenticationManagementResource authMgmt, String authenticationFlowAlias) { - for (AuthenticationFlowRepresentation flow : authMgmt.getFlows()) { - if (authenticationFlowAlias.equals(flow.getAlias())) { - return flow; - } - } - return null; - } - - /** - * Gets the authentication execution by alias. - * @param authMgmt the authorization management - * @param authenticationFlowAlias the authentication flow alias - * @return the execution info, or null if not found - */ - private AuthenticationExecutionInfoRepresentation getExecutionByDisplayName(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, - String executionDisplayName) { - for (AuthenticationExecutionInfoRepresentation execution : authMgmt.getExecutions(authenticationFlowAlias)) { - if (executionDisplayName.equals(execution.getDisplayName())) { - return execution; - } - } - return null; - } - - /** - * Gets the identity provider redirector by alias. - * @param authMgmt the authorization management - * @param authenticationFlowAlias the authentication flow alias - * @return the authorization flow, or null if not found - */ - private AuthenticationExecutionInfoRepresentation getIdentityProviderRedirector(AuthenticationManagementResource authMgmt, String authenticationFlowAlias) { - for (AuthenticationExecutionInfoRepresentation execution : authMgmt.getExecutions(authenticationFlowAlias)) { - if (KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_REDIRECTOR.equals(execution.getProviderId())) { - return execution; - } - } - return null; - } - - - /** - * Gets the group by name. - * @param groups the groups - * @param groupName the group name - * @return the group, or null if not found - */ - private GroupRepresentation getGroupByName(GroupsResource groups, String groupName) { - for (GroupRepresentation group : groups.groups()) { - if (groupName.equals(group.getName())) { - return group; - } - } - return null; - } - - /** - * Gets the group IDs by name. - * @param groups the groups - * @param groupNames the group names - * @return the group IDs - */ - private List getGroupIds(GroupsResource groups, List groupNames) { - List groupIds = new ArrayList<>(); - for (GroupRepresentation group : groups.groups()) { - if (groupNames != null && groupNames.contains(group.getName())) { - groupIds.add(group.getId()); - } - } - return groupIds; - } - - /** - * Gets the user by name. - * @param users the users - * @param userName the user name - * @return the user, or null if not found - */ - private UserRepresentation getUserByName(UsersResource users, String userName) { - for (UserRepresentation user : users.list()) { - if (userName.equals(user.getUsername())) { - return user; - } - } - return null; - } + private final Keycloak adminClient; + + public KeycloakConfigurator(Keycloak client) { + this.adminClient = client; + } + + /** + * Initializes the realm. + * @param realmName the realm name + * @param realmPg the realm property group + * @throws Exception an Exception + */ + public void initializeRealm(String realmName, PropertyGroup realmPg) throws Exception { + System.out.println("initializing realm: " + realmName); + // Create realm if it does not exist + RealmsResource realms = adminClient.realms(); + RealmRepresentation realm = getRealmByName(realms, realmName); + if (realm == null) { + realm = new RealmRepresentation(); + realm.setRealm(realmName); + realms.create(realm); + realm = getRealmByName(realms, realmName); + if (realm == null) { + throw new RuntimeException("Unable to create realm"); + } + } + + // Initialize client scopes + PropertyGroup clientScopesPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPES); + if (clientScopesPg != null) { + for (PropertyEntry clientScopePe: clientScopesPg.getProperties()) { + String clientScopeName = clientScopePe.getName(); + PropertyGroup clientScopePg = clientScopesPg.getPropertyGroup(clientScopeName); + initializeClientScope(realms.realm(realmName).clientScopes(), clientScopeName, clientScopePg); + } + } + + // Update "default" default assigned client scopes + List defaultClientScopeNames = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_DEFAULT_CLIENT_SCOPES); + if (defaultClientScopeNames != null) { + List defaultClientScopeIds = getClientScopeIds(realms.realm(realmName).clientScopes(), defaultClientScopeNames); + if (defaultClientScopeIds != null) { + List existingDefaultClientScopes = realms.realm(realmName).getDefaultDefaultClientScopes(); + for (ClientScopeRepresentation existingDefaultClientScope : existingDefaultClientScopes) { + if (!defaultClientScopeIds.contains(existingDefaultClientScope.getId())) { + realms.realm(realmName).removeDefaultDefaultClientScope(existingDefaultClientScope.getId()); + } + else { + defaultClientScopeIds.remove(existingDefaultClientScope.getId()); + } + } + for (String defaultClientScopeId : defaultClientScopeIds) { + realms.realm(realmName).addDefaultDefaultClientScope(defaultClientScopeId); + } + } + } + + // Update "default" optional assigned client scopes + List optionalClientScopeNames = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_OPTIONAL_CLIENT_SCOPES); + if (optionalClientScopeNames != null) { + List optionalClientScopeIds = getClientScopeIds(realms.realm(realmName).clientScopes(), optionalClientScopeNames); + if (optionalClientScopeIds != null) { + List existingOptionalClientScopes = realms.realm(realmName).getDefaultOptionalClientScopes(); + for (ClientScopeRepresentation existingOptionalClientScope : existingOptionalClientScopes) { + if (!optionalClientScopeIds.contains(existingOptionalClientScope.getId())) { + realms.realm(realmName).removeDefaultOptionalClientScope(existingOptionalClientScope.getId()); + } + else { + optionalClientScopeIds.remove(existingOptionalClientScope.getId()); + } + } + for (String defaultClientScopeId : optionalClientScopeIds) { + realms.realm(realmName).addDefaultOptionalClientScope(defaultClientScopeId); + } + } + } + + // Initialize clients + PropertyGroup clientsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_CLIENTS); + if (clientsPg != null) { + for (PropertyEntry clientPe: clientsPg.getProperties()) { + String clientName = clientPe.getName(); + PropertyGroup clientPg = clientsPg.getPropertyGroup(clientName); + initializeClient(realms.realm(realmName).clients(), realms.realm(realmName).clientScopes(), clientName, clientPg); + } + } + + // Initialize authentication flows + PropertyGroup authenticationFlowsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_AUTHENTICATION_FLOWS); + if (authenticationFlowsPg != null) { + for (PropertyEntry authenticationFlowPe : authenticationFlowsPg.getProperties()) { + String authenticationFlowAlias = authenticationFlowPe.getName(); + PropertyGroup authenticationFlowPg = authenticationFlowsPg.getPropertyGroup(authenticationFlowAlias); + initializeAuthenticationFlow(realms.realm(realmName).flows(), authenticationFlowAlias, + authenticationFlowPg); + } + } + + // Initialize identity providers + PropertyGroup identityProvidersPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDERS); + if (identityProvidersPg != null) { + for (PropertyEntry identityProviderPe: identityProvidersPg.getProperties()) { + String identityProviderAlias = identityProviderPe.getName(); + PropertyGroup identityProviderPg = identityProvidersPg.getPropertyGroup(identityProviderAlias); + initializeIdentityProvider(realms.realm(realmName).identityProviders(), identityProviderAlias, identityProviderPg); + } + } + + // Initialize groups + PropertyGroup groupsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_GROUPS); + if (groupsPg != null) { + for (PropertyEntry groupPe: groupsPg.getProperties()) { + String groupName = groupPe.getName(); + PropertyGroup groupPg = groupsPg.getPropertyGroup(groupName); + initializeGroup(realms.realm(realmName).groups(), groupName, groupPg); + } + } + + // Update "default" groups + List defaultGroups = realmPg.getStringListProperty(KeycloakConfig.PROP_DEFAULT_GROUPS); + if (defaultGroups != null) { + List defaultGroupIds = getGroupIds(realms.realm(realmName).groups(), defaultGroups); + if (defaultGroupIds != null) { + List existingDefaultGroups = realms.realm(realmName).getDefaultGroups(); + for (GroupRepresentation existingDefaultGroup : existingDefaultGroups) { + if (!defaultGroupIds.contains(existingDefaultGroup.getId())) { + realms.realm(realmName).removeDefaultGroup(existingDefaultGroup.getId()); + } + else { + defaultGroupIds.remove(existingDefaultGroup.getId()); + } + } + for (String defaultGroupId : defaultGroupIds) { + realms.realm(realmName).addDefaultGroup(defaultGroupId); + } + } + } + + // Initialize users + PropertyGroup usersPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_USERS); + if (usersPg != null) { + for (PropertyEntry userPe: usersPg.getProperties()) { + String userName = userPe.getName(); + PropertyGroup userPg = usersPg.getPropertyGroup(userName); + initializeUser(realms.realm(realmName).users(), realms.realm(realmName).groups(), userName, userPg); + } + } + + // Initialize events config + PropertyGroup eventsPg = realmPg.getPropertyGroup(KeycloakConfig.PROP_EVENTS_CONFIG); + if (eventsPg != null) { + initializeEventsConfig(realm, eventsPg); + } + + // Update realm settings + String browserFlow = realmPg.getStringProperty(KeycloakConfig.PROP_BROWSER_FLOW); + if (browserFlow != null) { + realm.setBrowserFlow(browserFlow); + } + realm.setEnabled(realmPg.getBooleanProperty(KeycloakConfig.PROP_REALM_ENABLED)); + realms.realm(realmName).update(realm); + } + + /** + * @param realm + * @param eventsPg + */ + void initializeEventsConfig(RealmRepresentation realm, PropertyGroup eventsPg) { + System.out.println("initializing events config"); + + // Login events + Boolean eventsEnabled = eventsPg.getBooleanProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_LOGIN_EVENTS); + if (eventsEnabled != null) { + realm.setEventsEnabled(eventsEnabled); + } + + Integer eventsExpiration = eventsPg.getIntProperty(KeycloakConfig.PROP_EVENTS_CONFIG_EXPIRATION); + if (eventsExpiration != null) { + realm.setEventsExpiration(Long.valueOf(eventsExpiration)); + } + + List saveTypes = null; + try { + saveTypes = eventsPg.getStringListProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_TYPES); + } catch (Exception e) { + System.err.println("Error while reading event save types from the config file:"); + e.printStackTrace(); + } + if (saveTypes != null) { + realm.setEnabledEventTypes(saveTypes); + } + + // Admin events + Boolean adminEventsEnabled = eventsPg.getBooleanProperty(KeycloakConfig.PROP_EVENTS_CONFIG_SAVE_ADMIN_EVENTS); + if (adminEventsEnabled != null) { + realm.setAdminEventsEnabled(adminEventsEnabled); + } + } + + /** + * Initializes the client scopes. + * @param clientScopes the client scopes resource + * @param clientScopeName the client scope name + * @param clientScopePg the client scope property group + * @throws Exception an Exception + */ + void initializeClientScope(ClientScopesResource clientScopes, String clientScopeName, PropertyGroup clientScopePg) throws Exception { + System.out.println("initializing client scope: " + clientScopeName); + // Create client scope if it does not exist + ClientScopeRepresentation clientScope = getClientScopeByName(clientScopes, clientScopeName); + if (clientScope == null) { + clientScope = new ClientScopeRepresentation(); + clientScope.setName(clientScopeName); + clientScopes.create(clientScope); + clientScope = getClientScopeByName(clientScopes, clientScopeName); + if (clientScope == null) { + throw new RuntimeException("Unable to create client scope"); + } + } + + // Update client scope settings + clientScope.setDescription(clientScopePg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_DESCRIPTION)); + clientScope.setProtocol(clientScopePg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_PROTOCOL)); + PropertyGroup attributesPg = clientScopePg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_ATTRIBUTES); + if (attributesPg != null) { + Map attributes = clientScope.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + } + for (PropertyEntry attributePe: attributesPg.getProperties()) { + String attributeKey = attributePe.getName(); + attributes.put(attributeKey, attributePe.getValue() != null ? attributePe.getValue().toString() : null); + } + clientScope.setAttributes(attributes); + } + clientScopes.get(clientScope.getId()).update(clientScope); + + // Initialize protocol mappers + PropertyGroup mappersPg = clientScopePg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPERS); + if (mappersPg != null) { + for (PropertyEntry mapperPe: mappersPg.getProperties()) { + String mapperName = mapperPe.getName(); + PropertyGroup mapperPg = mappersPg.getPropertyGroup(mapperName); + initializeProtocolMapper(clientScopes.get(clientScope.getId()).getProtocolMappers(), mapperName, mapperPg); + } + } + } + + /** + * Initializes the protocol mappers of the client scope. + * @param protocolMappers the protocol mappers + * @param mapperName the protocol mapper name + * @param mapperPg the protocol mapper property group + * @throws Exception an Exception + */ + void initializeProtocolMapper(ProtocolMappersResource protocolMappers, String mapperName, PropertyGroup mapperPg) throws Exception { + System.out.println("initializing protocol mapper: " + mapperName); + // Create protocol mapper if it does not exist + ProtocolMapperRepresentation protocolMapper = getProtocolMapperByName(protocolMappers, mapperName); + if (protocolMapper == null) { + protocolMapper = new ProtocolMapperRepresentation(); + protocolMapper.setName(mapperName); + protocolMapper.setProtocol(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL)); + protocolMapper.setProtocolMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER)); + Response response = protocolMappers.createMapper(protocolMapper); + protocolMapper = getProtocolMapperByName(protocolMappers, mapperName); + if (protocolMapper == null) { + throw new RuntimeException("Unable to create protocol mapper: " + response.readEntity(String.class)); + } + } + + // Update protocol mapper settings + protocolMapper.setProtocol(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL)); + protocolMapper.setProtocolMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER)); + PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER_CONFIG); + if (configPg != null) { + Map config = protocolMapper.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + protocolMapper.setConfig(config); + } + protocolMappers.update(protocolMapper.getId(), protocolMapper); + } + + /** + * Initializes the client. + * @param clients the clients resource + * @param clientScopes the client scopes resource + * @param clientId the client id + * @param clientPg the client property group + * @throws Exception an Exception + */ + void initializeClient(ClientsResource clients, ClientScopesResource clientScopes, String clientId, PropertyGroup clientPg) throws Exception { + System.out.println("initializing client: " + clientId); + // Create client if it does not exist + ClientRepresentation client = getClientByClientId(clients, clientId); + if (client == null) { + client = new ClientRepresentation(); + client.setClientId(clientId); + clients.create(client); + client = getClientByClientId(clients, clientId); + if (client == null) { + throw new RuntimeException("Unable to create client"); + } + } + + // Update client settings + client.setName(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_NAME)); + client.setDescription(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_DESCRIPTION)); + client.setConsentRequired(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_CONSENT_REQUIRED)); + client.setStandardFlowEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_STANDARD_FLOW_ENABLED, true)); + client.setServiceAccountsEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_SERVICE_ACCOUNTS_ENABLED, false)); + + PropertyGroup attributePg = clientPg.getPropertyGroup(KeycloakConfig.PROP_CLIENT_ATTRIBUTES); + if (attributePg != null) { + setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_DEVICE_AUTH_GRANT_ENABLED); + } + + Boolean publicClient = clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_PUBLIC_CLIENT, false); + client.setPublicClient(publicClient); + + if (!publicClient) { + String clientAuthType = clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_AUTHENTICATOR_TYPE); + client.setClientAuthenticatorType(clientAuthType); + + if ("client-jwt".equals(clientAuthType) && attributePg != null) { + boolean useJwksUrl = Boolean.parseBoolean(attributePg.getStringProperty(KeycloakConfig.PROP_CLIENT_ATTR_USE_JWKS_URL, "false")); + if (useJwksUrl) { + setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_USE_JWKS_URL); + setAttribute(attributePg, client, KeycloakConfig.PROP_CLIENT_ATTR_JWKS_URL); + } + } + } + + client.setDirectAccessGrantsEnabled(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_DIRECT_ACCESS_ENABLED)); + client.setBearerOnly(clientPg.getBooleanProperty(KeycloakConfig.PROP_CLIENT_BEARER_ONLY)); + client.setRootUrl(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_ROOT_URL)); + client.setRedirectUris(clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_REDIRECT_URIS)); + client.setAdminUrl(clientPg.getStringProperty(KeycloakConfig.PROP_CLIENT_ADMIN_URL)); + client.setWebOrigins(clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_WEB_ORIGINS)); + clients.get(client.getId()).update(client); + + ClientResource cr = clients.get(client.getId()); + + // Remove default client scopes that no longer apply and collect the ones to add + List defaultClientScopeIdsToAdd = new ArrayList<>(); + List defaultClientScopeNameStrings = clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_DEFAULT_CLIENT_SCOPES); + if (defaultClientScopeNameStrings != null) { + List defaultClientScopeIds = getClientScopeIds(clientScopes, defaultClientScopeNameStrings); + if (defaultClientScopeIds != null) { + List existingDefaultClientScopes = cr.getDefaultClientScopes(); + for (ClientScopeRepresentation existingDefaultClientScope : existingDefaultClientScopes) { + if (!defaultClientScopeIds.contains(existingDefaultClientScope.getId())) { + cr.removeDefaultClientScope(existingDefaultClientScope.getId()); + } + else { + defaultClientScopeIds.remove(existingDefaultClientScope.getId()); + } + } + defaultClientScopeIdsToAdd.addAll(defaultClientScopeIds); + } + } + + // Remove optional client scopes that no longer apply and collect the ones to add + List optionalClientScopeIdsToAdd = new ArrayList<>(); + List optionalClientScopeNameStrings = clientPg.getStringListProperty(KeycloakConfig.PROP_CLIENT_OPTIONAL_CLIENT_SCOPES); + if (optionalClientScopeNameStrings != null) { + List optionalClientScopeIds = getClientScopeIds(clientScopes, optionalClientScopeNameStrings); + if (optionalClientScopeIds != null) { + List existingOptionalClientScopes = cr.getOptionalClientScopes(); + for (ClientScopeRepresentation existingOptionalClientScope : existingOptionalClientScopes) { + if (!optionalClientScopeIds.contains(existingOptionalClientScope.getId())) { + cr.removeDefaultClientScope(existingOptionalClientScope.getId()); + } + else { + optionalClientScopeIds.remove(existingOptionalClientScope.getId()); + } + } + optionalClientScopeIdsToAdd.addAll(optionalClientScopeIds); + } + } + + // Note: if a scope already exists in either list on the server, the add call will be ignored + for (String clientScopeId : defaultClientScopeIdsToAdd) { + cr.addDefaultClientScope(clientScopeId); + } + for (String clientScopeId : optionalClientScopeIdsToAdd) { + cr.addOptionalClientScope(clientScopeId); + } + } + + /** + * Client attributes are set a little differently, so this method encapsulates the logic to get the attribute map + * and set a given property from a PropertyGroup that contains that attributes value in a property by the same name. + * @param attributesPg + * @param client + * @param propName + * @throws Exception + */ + private void setAttribute(PropertyGroup attributesPg, ClientRepresentation client, String propName) throws Exception { + client.getAttributes().put(propName, attributesPg.getStringProperty(propName)); + } + + /** + * Initializes the identity provider. + * @param identityProviders the identity providers resource + * @param identityProviderAlias the identity provider alias + * @param identityProviderPg the identity provider property group + * @throws Exception an Exception + */ + void initializeIdentityProvider(IdentityProvidersResource identityProviders, String identityProviderAlias, PropertyGroup identityProviderPg) throws Exception { + System.out.println("initializing identity provider: " + identityProviderAlias); + // Create identity provider if it does not exist + IdentityProviderRepresentation identityProvider = getIdentityProviderByAlias(identityProviders, identityProviderAlias); + if (identityProvider == null) { + identityProvider = new IdentityProviderRepresentation(); + identityProvider.setAlias(identityProviderAlias); + identityProvider.setProviderId(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_PROVIDER_ID)); + PropertyGroup configPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_CONFIG); + if (configPg != null) { + Map config = identityProvider.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + config.remove(KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET); + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + identityProvider.setConfig(config); + } + identityProviders.create(identityProvider); + identityProvider = getIdentityProviderByAlias(identityProviders, identityProviderAlias); + if (identityProvider == null) { + throw new RuntimeException("Unable to create identity provider"); + } + } + + // Update identity provider settings + identityProvider.setProviderId(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_PROVIDER_ID)); + identityProvider.setDisplayName(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_DISPLAY_NAME)); + identityProvider.setEnabled(identityProviderPg.getBooleanProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_ENABLED)); + identityProvider.setFirstBrokerLoginFlowAlias(identityProviderPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_FIRST_BROKER_LOGIN_FLOW_ALIAS)); + identityProvider.setPostBrokerLoginFlowAlias(identityProviderPg + .getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_POST_BROKER_LOGIN_FLOW_ALIAS)); + PropertyGroup configPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_CONFIG); + if (configPg != null) { + Map config = identityProvider.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + config.remove(KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET); + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + identityProvider.setConfig(config); + } + identityProviders.get(identityProvider.getAlias()).update(identityProvider); + + // Initialize identity provider mappers + PropertyGroup mappersPg = identityProviderPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPERS); + if (mappersPg != null) { + for (PropertyEntry mapperPe: mappersPg.getProperties()) { + String mapperName = mapperPe.getName(); + PropertyGroup mapperPg = mappersPg.getPropertyGroup(mapperName); + initializeIdentityProviderMapper(identityProviders.get(identityProvider.getAlias()), identityProviderAlias, mapperName, mapperPg); + } + } + } + + /** + * Initializes the mappers of the identity provider. + * @param identityProvider the identity provider + * @param identityProviderAlias the identity provider alias + * @param mapperName the identity provider mapper name + * @param mapperPg the identity provider mapper property group + * @throws Exception an Exception + */ + void initializeIdentityProviderMapper(IdentityProviderResource identityProvider, String identityProviderAlias, String mapperName, PropertyGroup mapperPg) throws Exception { + System.out.println("initializing identity provider mapper: " + mapperName); + // Create protocol mapper if it does not exist + IdentityProviderMapperRepresentation identityProviderMapper = getIdentityProvideMapperByName(identityProvider, mapperName); + if (identityProviderMapper == null) { + identityProviderMapper = new IdentityProviderMapperRepresentation(); + identityProviderMapper.setName(mapperName); + identityProviderMapper.setIdentityProviderAlias(identityProviderAlias); + identityProviderMapper.setIdentityProviderMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER)); + PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); + if (configPg != null) { + Map config = identityProviderMapper.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + identityProviderMapper.setConfig(config); + } + identityProvider.addMapper(identityProviderMapper); + identityProviderMapper = getIdentityProvideMapperByName(identityProvider, mapperName); + if (identityProviderMapper == null) { + throw new RuntimeException("Unable to create identity provider mapper"); + } + } + + // Update identity provider mapper settings + identityProviderMapper.setIdentityProviderAlias(identityProviderAlias); + identityProviderMapper.setIdentityProviderMapper(mapperPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER)); + PropertyGroup configPg = mapperPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); + if (configPg != null) { + Map config = identityProviderMapper.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + identityProviderMapper.setConfig(config); + } + identityProvider.update(identityProviderMapper.getId(), identityProviderMapper); + } + + /** + * Initializes the authentication flow. + * @param authMgmt the authorization management + * @param authenticationFlowAlias the authentication flow alias + * @param authenticationFlowPg the authentication flow property group + * @throws Exception an Exception + */ + void initializeAuthenticationFlow(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, PropertyGroup authenticationFlowPg) throws Exception { + System.out.println("initializing authentication flow: " + authenticationFlowAlias); + // Get authentication flow + AuthenticationFlowRepresentation authenticationFlow = getAuthenticationFlowByAlias(authMgmt, authenticationFlowAlias); + if (authenticationFlow == null) { + authenticationFlow = new AuthenticationFlowRepresentation(); + authenticationFlow.setAlias(authenticationFlowAlias); + authenticationFlow.setTopLevel(true); + authenticationFlow.setProviderId(authenticationFlowPg.getStringProperty("providerId")); + authenticationFlow.setBuiltIn(authenticationFlowPg.getBooleanProperty("builtIn")); + + Response response = authMgmt.createFlow(authenticationFlow); + + if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + String path = response.getLocation().getPath(); + String id = path.substring(path.lastIndexOf("/") + 1); + System.out.println("Created flow with id '" + id + "'"); + authenticationFlow.setId(id); + updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); + } else { + System.err.println("Failed to create flow; status code '" + response.getStatus() + "'"); + System.err.println(response.readEntity(String.class)); + } + } + + updateFlowWithExecutions(authMgmt, authenticationFlowPg, authenticationFlow); + + // Update identity provider redirector + for (PropertyEntry authExecutionPe: authenticationFlowPg.getProperties()) { + String authExecutionType = authExecutionPe.getName(); + if (KeycloakConfig.PROP_IDENTITY_REDIRECTOR.equals(authExecutionType)) { + PropertyGroup identityProviderRedirectorPg = authenticationFlowPg.getPropertyGroup(authExecutionType); + String identityProviderRedirectorAlias = identityProviderRedirectorPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_REDIRECTOR_ALIAS); + initializeIdentityProviderRedirector(authMgmt, authenticationFlowAlias, identityProviderRedirectorAlias, identityProviderRedirectorPg); + } + } + } + + private void updateFlowWithExecutions(AuthenticationManagementResource authMgmt, PropertyGroup authenticationFlowPg, + AuthenticationFlowRepresentation authenticationFlow) throws Exception { + PropertyGroup authenticationExecutionsPg = authenticationFlowPg.getPropertyGroup("authenticationExecutions"); + JsonObject jsonObject = authenticationFlowPg.getJsonValue("authenticationExecutions").asJsonObject(); + for (String entry : jsonObject.keySet()) { + PropertyGroup entryProps = authenticationExecutionsPg.getPropertyGroup(entry); + + HashMap executionParams = new HashMap(); + + String description = entryProps.getStringProperty("description"); + executionParams.put("description", description); + + Boolean isFlow = entryProps.getBooleanProperty("authenticatorFlow", false); + if (isFlow) { + executionParams.put("alias", entry); + executionParams.put("type", "basic-flow"); + + AuthenticationExecutionInfoRepresentation executionFlow = getOrCreateExecution(authMgmt, + authenticationFlow.getAlias(), entry, isFlow, executionParams); + + // the above "alias" actually gets saved as the display name for some reason, but the alias is what we need to add subflow executions + executionFlow.setAlias(entry); + executionFlow.setRequirement(entryProps.getStringProperty("requirement")); + authMgmt.updateExecutions(authenticationFlow.getAlias(), executionFlow); + + PropertyGroup childExecutions = entryProps.getPropertyGroup("authenticationExecutions"); + for (PropertyEntry childEntry : childExecutions.getProperties()) { + // TODO: see if we can get the display name from the authenticator provider_id somehow, instead of requiring it in our config + String displayName = childEntry.getName(); + PropertyGroup childEntryPg = childExecutions.getPropertyGroup(displayName); + String authenticator = childEntryPg.getStringProperty("authenticator"); + + Boolean childIsFlow = childEntryPg.getBooleanProperty("authenticatorFlow", false); + if (childIsFlow) { + throw new UnsupportedOperationException("Nest subflows are not yet supported"); + } + + HashMap childExecutionParams = new HashMap(); + childExecutionParams.put("provider", authenticator); + AuthenticationExecutionInfoRepresentation childExecution = getOrCreateExecution(authMgmt, entry, displayName, childIsFlow, childExecutionParams); + + String configAlias = childEntryPg.getStringProperty("configAlias"); + JsonValue configJson = childEntryPg.getJsonValue("config"); + if (configJson != null) { + Map config = buildConfigMap(configJson, configAlias); + + AuthenticatorConfigRepresentation authenticatorConfig = getOrCreateAuthenticatorConfig(authMgmt, childExecution, configAlias, config); + authenticatorConfig.setConfig(config); + authMgmt.updateAuthenticatorConfig(authenticatorConfig.getId(), authenticatorConfig); + + childExecution.setAuthenticationConfig(configAlias); + } + + childExecution.setRequirement(childEntryPg.getStringProperty("requirement")); + authMgmt.updateExecutions(authenticationFlow.getAlias(), childExecution); + } + } else { + executionParams.put("authenticator", entry); + getOrCreateExecution(authMgmt, authenticationFlow.getAlias(), entry, isFlow, executionParams); + + // TODO authenticatorConfig + executionParams.put("priority", Integer.toString(entryProps.getIntProperty("priority"))); + } + + } + } + + private AuthenticatorConfigRepresentation getOrCreateAuthenticatorConfig(AuthenticationManagementResource authMgmt, + AuthenticationExecutionInfoRepresentation execution, String configAlias, Map config) { + + AuthenticatorConfigRepresentation authenticatorConfig = null; + + String configId = execution.getAuthenticationConfig(); + if (configId != null) { + authenticatorConfig = authMgmt.getAuthenticatorConfig(configId); + } else { + authenticatorConfig = new AuthenticatorConfigRepresentation(); + authenticatorConfig.setAlias(configAlias); + Response response = authMgmt.newExecutionConfig(execution.getId(), authenticatorConfig); + + if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + String path = response.getLocation().getPath(); + String id = path.substring(path.lastIndexOf("/") + 1); + System.out.println("Created authenticator config with id '" + id + "'"); + authenticatorConfig.setId(id); + } else { + System.err.println("Failed to create authenticator config; status code '" + response.getStatus() + "'"); + System.err.println(response.readEntity(String.class)); + } + } + + return authenticatorConfig; + } + + private Map buildConfigMap(JsonValue configJson, String configAlias) { + Map config = new HashMap(); + Set> entrySet = configJson.asJsonObject().entrySet(); + for (Entry configEntry : entrySet) { + JsonValue value = configEntry.getValue(); + if (value instanceof JsonString) { + config.put(configEntry.getKey(), ((JsonString) value).getString()); + } else { + System.err.println("Expected config of type String, but found " + value.getValueType()); + } + } + return config; + } + + private AuthenticationExecutionInfoRepresentation getOrCreateExecution(AuthenticationManagementResource authMgmt, + String flowAlias, String displayName, boolean isFlow, HashMap executionParams) { + AuthenticationExecutionInfoRepresentation savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); + if (savedExecution == null) { + if (isFlow) { + authMgmt.addExecutionFlow(flowAlias, executionParams); + } else { + authMgmt.addExecution(flowAlias, executionParams); + } + savedExecution = getExecutionByDisplayName(authMgmt, flowAlias, displayName); + } + if (savedExecution == null) { + throw new RuntimeException("Unable to create execution '" + displayName + "'"); + } + return savedExecution; + } + + /** + * Initializes the identity provider redirector. + * @param authMgmt the authorization management + * @param authenticationFlowAlias the authentication flow alias + * @param identityProviderRedirectorAlias the identity provider redirector alias + * @param identityProviderRedirectorPg the identity provider redirector property group + * @throws Exception an Exception + */ + void initializeIdentityProviderRedirector(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, String identityProviderRedirectorAlias, PropertyGroup identityProviderRedirectorPg) throws Exception { + System.out.println("initializing identity provider redirector: " + identityProviderRedirectorAlias); + // Get identity provider redirector + AuthenticationExecutionInfoRepresentation identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); + if (identityProviderRedirector == null) { + throw new RuntimeException("Identity provider redirector does not exist"); + } + + // Update identity provider redirector + identityProviderRedirector.setRequirement(identityProviderRedirectorPg.getStringProperty(KeycloakConfig.PROP_IDENTITY_PROVIDER_REDIRECTOR_REQUIREMENT)); + authMgmt.updateExecutions(authenticationFlowAlias, identityProviderRedirector); + identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); + if (identityProviderRedirector == null) { + throw new RuntimeException("Identity provider redirector does not exist"); + } + + // Create config representation if it does not exist + AuthenticatorConfigRepresentation configRepresentation = identityProviderRedirector.getAuthenticationConfig() != null ? authMgmt.getAuthenticatorConfig(identityProviderRedirector.getAuthenticationConfig()) : null; + if (configRepresentation == null) { + configRepresentation = new AuthenticatorConfigRepresentation(); + configRepresentation.setAlias(identityProviderRedirectorAlias); + PropertyGroup configPg = identityProviderRedirectorPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); + if (configPg != null) { + Map config = configRepresentation.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + configRepresentation.setConfig(config); + } + authMgmt.newExecutionConfig(identityProviderRedirector.getId(), configRepresentation); + identityProviderRedirector = getIdentityProviderRedirector(authMgmt, authenticationFlowAlias); + if (identityProviderRedirector == null) { + throw new RuntimeException("Identity provider redirector does not exist"); + } + configRepresentation = identityProviderRedirector.getAuthenticationConfig() != null ? authMgmt.getAuthenticatorConfig(identityProviderRedirector.getAuthenticationConfig()) : null; + if (configRepresentation == null) { + throw new RuntimeException("Unable to create identity provider redirector"); + } + } + + // Update config representation + configRepresentation.setAlias(identityProviderRedirectorAlias); + PropertyGroup configPg = identityProviderRedirectorPg.getPropertyGroup(KeycloakConfig.PROP_IDENTITY_PROVIDER_MAPPER_CONFIG); + if (configPg != null) { + Map config = configRepresentation.getConfig(); + if (config == null) { + config = new HashMap<>(); + } + for (PropertyEntry configPe: configPg.getProperties()) { + String configKey = configPe.getName(); + config.put(configKey, configPe.getValue() != null ? configPe.getValue().toString() : null); + } + configRepresentation.setConfig(config); + } + authMgmt.updateAuthenticatorConfig(configRepresentation.getId(), configRepresentation); + } + + /** + * Initializes the group. + * @param groups the groups resource + * @param groupName the group name + * @param groupPg the group property group + * @throws Exception an Exception + */ + void initializeGroup(GroupsResource groups, String groupName, PropertyGroup groupPg) throws Exception { + System.out.println("initializing group: " + groupName); + // Create group if it does not exist + GroupRepresentation group = getGroupByName(groups, groupName); + if (group == null) { + group = new GroupRepresentation(); + group.setName(groupName); + groups.add(group); + group = getGroupByName(groups, groupName); + if (group == null) { + throw new RuntimeException("Unable to create group"); + } + } + + // Update group settings + PropertyGroup attributesPg = groupPg.getPropertyGroup(KeycloakConfig.PROP_GROUP_ATTRIBUTES); + if (attributesPg != null) { + Map> attributes = group.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + } + for (PropertyEntry attributePe: attributesPg.getProperties()) { + String attributeKey = attributePe.getName(); + List attributeValue = PropertyGroup.convertToStringList(attributePe.getValue()); + attributes.put(attributeKey, attributeValue); + } + group.setAttributes(attributes); + } + groups.group(group.getId()).update(group); + } + + /** + * Initializes the user. + * @param users the users resource + * @param groups the groups resource + * @param userName the user name + * @param userPg the user property group + * @throws Exception an Exception + */ + void initializeUser(UsersResource users, GroupsResource groups, String userName, PropertyGroup userPg) throws Exception { + System.out.println("initializing user: " + userName); + // Create user if it does not exist + UserRepresentation user = getUserByName(users, userName); + if (user == null) { + user = new UserRepresentation(); + user.setUsername(userName); + users.create(user); + user = getUserByName(users, userName); + if (user == null) { + throw new RuntimeException("Unable to create user"); + } + } + + // Update user settings + user.setEnabled(userPg.getBooleanProperty(KeycloakConfig.PROP_USER_ENABLED)); + PropertyGroup attributesPg = userPg.getPropertyGroup(KeycloakConfig.PROP_USER_ATTRIBUTES); + if (attributesPg != null) { + Map> attributes = user.getAttributes(); + if (attributes == null) { + attributes = new HashMap<>(); + } + for (PropertyEntry attributePe: attributesPg.getProperties()) { + String attributeKey = attributePe.getName(); + List attributeValue = PropertyGroup.convertToStringList(attributePe.getValue()); + attributes.put(attributeKey, attributeValue); + } + user.setAttributes(attributes); + } + CredentialRepresentation credential = new CredentialRepresentation(); + credential.setType(KeycloakConfig.KEYCLOAK_USER_PASSWORD_TYPE); + credential.setTemporary(userPg.getBooleanProperty(KeycloakConfig.PROP_USER_PASSWORD_TEMPORARY)); + credential.setValue(userPg.getStringProperty(KeycloakConfig.PROP_USER_PASSWORD)); + user.setCredentials(Arrays.asList(credential)); + users.get(user.getId()).update(user); + + // Update user group memberships + List groupIds = getGroupIds(groups, userPg.getStringListProperty(KeycloakConfig.PROP_USER_GROUPS)); + if (groupIds != null) { + List existingGroupIds = getGroupIds(groups, user.getGroups()); + for (String existingGroupId : existingGroupIds) { + if (!groupIds.contains(existingGroupId)) { + users.get(user.getId()).leaveGroup(existingGroupId); + } + else { + groupIds.remove(existingGroupId); + } + } + for (String groupId : groupIds) { + users.get(user.getId()).joinGroup(groupId); + } + } + } + + /** + * Gets the realm by name. + * @param realmsResource the realms resource + * @param realmName the realm name + * @return the realm, or null if not found + */ + private RealmRepresentation getRealmByName(RealmsResource realmsResource, String realmName) { + for (RealmRepresentation realm : realmsResource.findAll()) { + if (realmName.equals(realm.getRealm())) { + return realm; + } + } + return null; + } + + /** + * Gets the client scope by name. + * @param clientScopes the client scopes + * @param clientScopeName the client scope name + * @return the client scope, or null if not found + */ + private ClientScopeRepresentation getClientScopeByName(ClientScopesResource clientScopes, String clientScopeName) { + for (ClientScopeRepresentation clientScope : clientScopes.findAll()) { + if (clientScopeName.equals(clientScope.getName())) { + return clientScope; + } + } + return null; + } + + /** + * Gets the client scope IDs by name. + * @param clientScopes the client scopes + * @param clientScopeNames the client scope names + * @return the client scope IDs + */ + private List getClientScopeIds(ClientScopesResource clientScopes, List clientScopeNames) { + List clientScopeIds = new ArrayList<>(); + Map nameToIdMap = clientScopes.findAll().stream().collect(Collectors.toMap(c -> c.getName(), c -> c.getId())); + + for (String clientScopeName : clientScopeNames) { + if (nameToIdMap.containsKey(clientScopeName)) { + clientScopeIds.add(nameToIdMap.get(clientScopeName)); + } else { + System.err.println("Skipping client scope '" + clientScopeName + "'; unable to find id for client scope with this name"); + } + } + return clientScopeIds; + } + + /** + * Gets the client by client ID. + * @param adminClient the clients + * @param clientName the client name + * @return the client, or null if not found + */ + private ClientRepresentation getClientByClientId(ClientsResource clients, String clientId) { + for (ClientRepresentation client : clients.findAll()) { + if (clientId.equals(client.getClientId())) { + return client; + } + } + return null; + } + + /** + * Gets the protocol mapper by name. + * @param protocolMappers the protocol mappers + * @param mapperName the mapper name + * @return the protocol mapper, or null if not found + */ + private ProtocolMapperRepresentation getProtocolMapperByName(ProtocolMappersResource protocolMappers, String mapperName) { + for (ProtocolMapperRepresentation protocolMapper : protocolMappers.getMappers()) { + if (mapperName.equals(protocolMapper.getName())) { + return protocolMapper; + } + } + return null; + } + + /** + * Gets the identity provider by provider alias. + * @param identityProviders the identity providers + * @param identityProviderAlias the identity provider alias + * @return the identity provider, or null if not found + */ + private IdentityProviderRepresentation getIdentityProviderByAlias(IdentityProvidersResource identityProviders, String identityProviderAlias) { + for (IdentityProviderRepresentation identityProvider : identityProviders.findAll()) { + if (identityProviderAlias.equals(identityProvider.getAlias())) { + return identityProvider; + } + } + return null; + } + + /** + * Gets the identity provider mapper by name. + * @param identity provider the identity provider + * @param mapperName the mapper name + * @return the identity provider mapper, or null if not found + */ + private IdentityProviderMapperRepresentation getIdentityProvideMapperByName(IdentityProviderResource identityProvider, String mapperName) { + for (IdentityProviderMapperRepresentation identityProviderMapper : identityProvider.getMappers()) { + if (mapperName.equals(identityProviderMapper.getName())) { + return identityProviderMapper; + } + } + return null; + } + + + /** + * Gets the authentication flow by alias. + * @param authMgmt the authorization management + * @param authenticationFlowAlias the authentication flow alias + * @return the authorization flow, or null if not found + */ + private AuthenticationFlowRepresentation getAuthenticationFlowByAlias(AuthenticationManagementResource authMgmt, String authenticationFlowAlias) { + for (AuthenticationFlowRepresentation flow : authMgmt.getFlows()) { + if (authenticationFlowAlias.equals(flow.getAlias())) { + return flow; + } + } + return null; + } + + /** + * Gets the authentication execution by alias. + * @param authMgmt the authorization management + * @param authenticationFlowAlias the authentication flow alias + * @return the execution info, or null if not found + */ + private AuthenticationExecutionInfoRepresentation getExecutionByDisplayName(AuthenticationManagementResource authMgmt, String authenticationFlowAlias, + String executionDisplayName) { + for (AuthenticationExecutionInfoRepresentation execution : authMgmt.getExecutions(authenticationFlowAlias)) { + if (executionDisplayName.equals(execution.getDisplayName())) { + return execution; + } + } + return null; + } + + /** + * Gets the identity provider redirector by alias. + * @param authMgmt the authorization management + * @param authenticationFlowAlias the authentication flow alias + * @return the authorization flow, or null if not found + */ + private AuthenticationExecutionInfoRepresentation getIdentityProviderRedirector(AuthenticationManagementResource authMgmt, String authenticationFlowAlias) { + for (AuthenticationExecutionInfoRepresentation execution : authMgmt.getExecutions(authenticationFlowAlias)) { + if (KeycloakConfig.KEYCLOAK_IDENTITY_PROVIDER_REDIRECTOR.equals(execution.getProviderId())) { + return execution; + } + } + return null; + } + + + /** + * Gets the group by name. + * @param groups the groups + * @param groupName the group name + * @return the group, or null if not found + */ + private GroupRepresentation getGroupByName(GroupsResource groups, String groupName) { + for (GroupRepresentation group : groups.groups()) { + if (groupName.equals(group.getName())) { + return group; + } + } + return null; + } + + /** + * Gets the group IDs by name. + * @param groups the groups + * @param groupNames the group names + * @return the group IDs + */ + private List getGroupIds(GroupsResource groups, List groupNames) { + List groupIds = new ArrayList<>(); + for (GroupRepresentation group : groups.groups()) { + if (groupNames != null && groupNames.contains(group.getName())) { + groupIds.add(group.getId()); + } + } + return groupIds; + } + + /** + * Gets the user by name. + * @param users the users + * @param userName the user name + * @return the user, or null if not found + */ + private UserRepresentation getUserByName(UsersResource users, String userName) { + for (UserRepresentation user : users.list()) { + if (userName.equals(user.getUsername())) { + return user; + } + } + return null; + } } diff --git a/keycloak-config/src/main/java/org/alvearie/keycloak/config/util/KeycloakConfig.java b/keycloak-config/src/main/java/org/alvearie/keycloak/config/util/KeycloakConfig.java index d6cdfba..69f31d5 100644 --- a/keycloak-config/src/main/java/org/alvearie/keycloak/config/util/KeycloakConfig.java +++ b/keycloak-config/src/main/java/org/alvearie/keycloak/config/util/KeycloakConfig.java @@ -2,7 +2,7 @@ (C) Copyright IBM Corp. 2021 SPDX-License-Identifier: Apache-2.0 -*/ + */ package org.alvearie.keycloak.config.util; import java.io.File; @@ -29,302 +29,303 @@ */ public class KeycloakConfig { - // Keycloak configuration property names (top-level) - public static final String PROP_KEYCLOAK_SERVER_URL = "keycloak|serverUrl"; - public static final String PROP_KEYCLOAK_ADMIN_USER = "keycloak|adminUser"; - public static final String PROP_KEYCLOAK_ADMIN_PW = "keycloak|adminPassword"; - public static final String PROP_KEYCLOAK_ADMIN_CLIENT_ID = "keycloak|adminClientId"; - public static final String PROP_KEYCLOAK_REALMS = "keycloak|realms"; + // Keycloak configuration property names (top-level) + public static final String PROP_KEYCLOAK_SERVER_URL = "keycloak|serverUrl"; + public static final String PROP_KEYCLOAK_ADMIN_USER = "keycloak|adminUser"; + public static final String PROP_KEYCLOAK_ADMIN_PW = "keycloak|adminPassword"; + public static final String PROP_KEYCLOAK_ADMIN_CLIENT_ID = "keycloak|adminClientId"; + public static final String PROP_KEYCLOAK_REALMS = "keycloak|realms"; - // Keycloak configuration property names (relative) - public static final String PROP_REALM_ENABLED = "enabled"; - public static final String PROP_CLIENT_SCOPES = "clientScopes"; - public static final String PROP_CLIENT_SCOPE_DESCRIPTION = "description"; - public static final String PROP_CLIENT_SCOPE_PROTOCOL = "protocol"; - public static final String PROP_CLIENT_SCOPE_ATTRIBUTES = "attributes"; - public static final String PROP_CLIENT_SCOPE_MAPPERS = "mappers"; - public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL = "protocol"; - public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER = "protocolmapper"; - public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER_CONFIG = "config"; - public static final String PROP_DEFAULT_DEFAULT_CLIENT_SCOPES = "defaultDefaultClientScopes"; - public static final String PROP_DEFAULT_OPTIONAL_CLIENT_SCOPES = "defaultOptionalClientScopes"; - public static final String PROP_IDENTITY_PROVIDERS = "identityProviders"; - public static final String PROP_IDENTITY_PROVIDER_DISPLAY_NAME = "displayName"; - public static final String PROP_IDENTITY_PROVIDER_ENABLED = "enabled"; - public static final String PROP_IDENTITY_PROVIDER_FIRST_BROKER_LOGIN_FLOW_ALIAS = "firstBrokerLoginFlowAlias"; - public static final String PROP_IDENTITY_PROVIDER_CONFIG = "config"; - public static final String PROP_IDENTITY_PROVIDER_PROVIDER_ID = "providerId"; - public static final String PROP_IDENTITY_PROVIDER_MAPPERS = "mappers"; - public static final String PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER = "identityProviderMapper"; - public static final String PROP_IDENTITY_PROVIDER_MAPPER_CONFIG = "config"; - public static final String PROP_CLIENTS = "clients"; - public static final String PROP_CLIENT_NAME = "name"; - public static final String PROP_CLIENT_DESCRIPTION = "description"; - public static final String PROP_CLIENT_CONSENT_REQUIRED = "consentRequired"; - public static final String PROP_CLIENT_PUBLIC_CLIENT = "publicClient"; - public static final String PROP_CLIENT_AUTHENTICATOR_TYPE = "clientAuthenticatorType"; - public static final String PROP_CLIENT_BEARER_ONLY = "bearerOnly"; - public static final String PROP_CLIENT_DIRECT_ACCESS_ENABLED = "enableDirectAccess"; - public static final String PROP_CLIENT_DEFAULT_CLIENT_SCOPES = "defaultClientScopes"; - public static final String PROP_CLIENT_OPTIONAL_CLIENT_SCOPES = "optionalClientScopes"; - public static final String PROP_CLIENT_ROOT_URL = "rootURL"; - public static final String PROP_CLIENT_REDIRECT_URIS = "redirectURIs"; - public static final String PROP_CLIENT_ADMIN_URL = "adminURL"; - public static final String PROP_CLIENT_WEB_ORIGINS = "webOrigins"; - public static final String PROP_CLIENT_STANDARD_FLOW_ENABLED = "standardFlowEnabled"; - public static final String PROP_CLIENT_SERVICE_ACCOUNTS_ENABLED = "serviceAccountsEnabled"; - public static final String PROP_CLIENT_ATTRIBUTES = "attributes"; - public static final String PROP_CLIENT_ATTR_DEVICE_AUTH_GRANT_ENABLED = "oauth2.device.authorization.grant.enabled"; - public static final String PROP_CLIENT_ATTR_USE_JWKS_URL = "use.jwks.url"; - public static final String PROP_CLIENT_ATTR_JWKS_URL = "jwks.url"; - public static final String PROP_AUTHENTICATION_FLOWS = "authenticationFlows"; - public static final String PROP_BROWSER_FLOW = "browserFlow"; - public static final String PROP_IDENTITY_REDIRECTOR = "identityProviderRedirector"; - public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_ALIAS = "alias"; - public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_REQUIREMENT = "requirement"; - public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_DEFAULT_PROVIDER = "defaultProvider"; - public static final String PROP_GROUPS = "groups"; - public static final String PROP_GROUP_ATTRIBUTES = "attributes"; - public static final String PROP_DEFAULT_GROUPS = "defaultGroups"; - public static final String PROP_USERS = "users"; - public static final String PROP_USER_ENABLED = "enabled"; - public static final String PROP_USER_PASSWORD = "password"; - public static final String PROP_USER_PASSWORD_TEMPORARY = "passwordTemporary"; - public static final String PROP_USER_ATTRIBUTES = "attributes"; - public static final String PROP_USER_GROUPS = "groups"; - public static final String KEYCLOAK_USER_PASSWORD_TYPE = "password"; - public static final String KEYCLOAK_FIRST_BROKER_LOGIN = "first broker login"; - public static final String KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET = "clientSecret"; - public static final String KEYCLOAK_IDENTITY_PROVIDER_REDIRECTOR = "identity-provider-redirector"; - public static final String PROP_EVENTS_CONFIG = "eventsConfig"; - public static final String PROP_EVENTS_CONFIG_SAVE_LOGIN_EVENTS = "saveLoginEvents"; - public static final String PROP_EVENTS_CONFIG_EXPIRATION = "expiration"; - public static final String PROP_EVENTS_CONFIG_SAVE_TYPES = "types"; - public static final String PROP_EVENTS_CONFIG_SAVE_ADMIN_EVENTS = "saveAdminEvents"; + // Keycloak configuration property names (relative) + public static final String PROP_REALM_ENABLED = "enabled"; + public static final String PROP_CLIENT_SCOPES = "clientScopes"; + public static final String PROP_CLIENT_SCOPE_DESCRIPTION = "description"; + public static final String PROP_CLIENT_SCOPE_PROTOCOL = "protocol"; + public static final String PROP_CLIENT_SCOPE_ATTRIBUTES = "attributes"; + public static final String PROP_CLIENT_SCOPE_MAPPERS = "mappers"; + public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL = "protocol"; + public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER = "protocolmapper"; + public static final String PROP_CLIENT_SCOPE_MAPPER_PROTOCOL_MAPPER_CONFIG = "config"; + public static final String PROP_DEFAULT_DEFAULT_CLIENT_SCOPES = "defaultDefaultClientScopes"; + public static final String PROP_DEFAULT_OPTIONAL_CLIENT_SCOPES = "defaultOptionalClientScopes"; + public static final String PROP_IDENTITY_PROVIDERS = "identityProviders"; + public static final String PROP_IDENTITY_PROVIDER_DISPLAY_NAME = "displayName"; + public static final String PROP_IDENTITY_PROVIDER_ENABLED = "enabled"; + public static final String PROP_IDENTITY_PROVIDER_FIRST_BROKER_LOGIN_FLOW_ALIAS = "firstBrokerLoginFlowAlias"; + public static final String PROP_IDENTITY_PROVIDER_POST_BROKER_LOGIN_FLOW_ALIAS = "postBrokerLoginFlowAlias"; + public static final String PROP_IDENTITY_PROVIDER_CONFIG = "config"; + public static final String PROP_IDENTITY_PROVIDER_PROVIDER_ID = "providerId"; + public static final String PROP_IDENTITY_PROVIDER_MAPPERS = "mappers"; + public static final String PROP_IDENTITY_PROVIDER_MAPPER_IDENTITY_PROVIDER_MAPPER = "identityProviderMapper"; + public static final String PROP_IDENTITY_PROVIDER_MAPPER_CONFIG = "config"; + public static final String PROP_CLIENTS = "clients"; + public static final String PROP_CLIENT_NAME = "name"; + public static final String PROP_CLIENT_DESCRIPTION = "description"; + public static final String PROP_CLIENT_CONSENT_REQUIRED = "consentRequired"; + public static final String PROP_CLIENT_PUBLIC_CLIENT = "publicClient"; + public static final String PROP_CLIENT_AUTHENTICATOR_TYPE = "clientAuthenticatorType"; + public static final String PROP_CLIENT_BEARER_ONLY = "bearerOnly"; + public static final String PROP_CLIENT_DIRECT_ACCESS_ENABLED = "enableDirectAccess"; + public static final String PROP_CLIENT_DEFAULT_CLIENT_SCOPES = "defaultClientScopes"; + public static final String PROP_CLIENT_OPTIONAL_CLIENT_SCOPES = "optionalClientScopes"; + public static final String PROP_CLIENT_ROOT_URL = "rootURL"; + public static final String PROP_CLIENT_REDIRECT_URIS = "redirectURIs"; + public static final String PROP_CLIENT_ADMIN_URL = "adminURL"; + public static final String PROP_CLIENT_WEB_ORIGINS = "webOrigins"; + public static final String PROP_CLIENT_STANDARD_FLOW_ENABLED = "standardFlowEnabled"; + public static final String PROP_CLIENT_SERVICE_ACCOUNTS_ENABLED = "serviceAccountsEnabled"; + public static final String PROP_CLIENT_ATTRIBUTES = "attributes"; + public static final String PROP_CLIENT_ATTR_DEVICE_AUTH_GRANT_ENABLED = "oauth2.device.authorization.grant.enabled"; + public static final String PROP_CLIENT_ATTR_USE_JWKS_URL = "use.jwks.url"; + public static final String PROP_CLIENT_ATTR_JWKS_URL = "jwks.url"; + public static final String PROP_AUTHENTICATION_FLOWS = "authenticationFlows"; + public static final String PROP_BROWSER_FLOW = "browserFlow"; + public static final String PROP_IDENTITY_REDIRECTOR = "identityProviderRedirector"; + public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_ALIAS = "alias"; + public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_REQUIREMENT = "requirement"; + public static final String PROP_IDENTITY_PROVIDER_REDIRECTOR_DEFAULT_PROVIDER = "defaultProvider"; + public static final String PROP_GROUPS = "groups"; + public static final String PROP_GROUP_ATTRIBUTES = "attributes"; + public static final String PROP_DEFAULT_GROUPS = "defaultGroups"; + public static final String PROP_USERS = "users"; + public static final String PROP_USER_ENABLED = "enabled"; + public static final String PROP_USER_PASSWORD = "password"; + public static final String PROP_USER_PASSWORD_TEMPORARY = "passwordTemporary"; + public static final String PROP_USER_ATTRIBUTES = "attributes"; + public static final String PROP_USER_GROUPS = "groups"; + public static final String KEYCLOAK_USER_PASSWORD_TYPE = "password"; + public static final String KEYCLOAK_FIRST_BROKER_LOGIN = "first broker login"; + public static final String KEYCLOAK_IDENTITY_PROVIDER_CLIENT_SECRET = "clientSecret"; + public static final String KEYCLOAK_IDENTITY_PROVIDER_REDIRECTOR = "identity-provider-redirector"; + public static final String PROP_EVENTS_CONFIG = "eventsConfig"; + public static final String PROP_EVENTS_CONFIG_SAVE_LOGIN_EVENTS = "saveLoginEvents"; + public static final String PROP_EVENTS_CONFIG_EXPIRATION = "expiration"; + public static final String PROP_EVENTS_CONFIG_SAVE_TYPES = "types"; + public static final String PROP_EVENTS_CONFIG_SAVE_ADMIN_EVENTS = "saveAdminEvents"; - private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(null); + private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(null); - private String fileName; - private PropertyGroup config; + private String fileName; + private PropertyGroup config; - /** - * Instantiates configuration using the specified file name. - * - * @param fileName - * the JSON file containing keycloak configuration - */ - public KeycloakConfig(String fileName) { - this.fileName = fileName; - } + /** + * Instantiates configuration using the specified file name. + * + * @param fileName + * the JSON file containing keycloak configuration + */ + public KeycloakConfig(String fileName) { + this.fileName = fileName; + } - /** - * Gets the property group. - * - * @param propertyName - * the property name - * @return the property group - */ - public PropertyGroup getPropertyGroup(String propertyName) { - return getTypedProperty(PropertyGroup.class, propertyName, null); - } + /** + * Gets the property group. + * + * @param propertyName + * the property name + * @return the property group + */ + public PropertyGroup getPropertyGroup(String propertyName) { + return getTypedProperty(PropertyGroup.class, propertyName, null); + } - /** - * Gets the property value as a string. - * - * @param propertyName - * the property name - * @return the property value - */ - public String getStringProperty(String propertyName) { - return getTypedProperty(String.class, propertyName, null); - } + /** + * Gets the property value as a string. + * + * @param propertyName + * the property name + * @return the property value + */ + public String getStringProperty(String propertyName) { + return getTypedProperty(String.class, propertyName, null); + } - /** - * Gets the property value as a boolean. - * - * @param propertyName - * the property name - * @return the property value - */ - public Boolean getBooleanProperty(String propertyName) { - return getTypedProperty(Boolean.class, propertyName, null); - } + /** + * Gets the property value as a boolean. + * + * @param propertyName + * the property name + * @return the property value + */ + public Boolean getBooleanProperty(String propertyName) { + return getTypedProperty(Boolean.class, propertyName, null); + } - /** - * Gets the property value as an integer. - * - * @param propertyName - * the property name - * @return the property value - */ - public Integer getIntProperty(String propertyName) { - return getTypedProperty(Integer.class, propertyName, null); - } + /** + * Gets the property value as an integer. + * + * @param propertyName + * the property name + * @return the property value + */ + public Integer getIntProperty(String propertyName) { + return getTypedProperty(Integer.class, propertyName, null); + } - /** - * Gets the property value as a double. - * - * @param propertyName - * the property name - * @return the property value - */ - public Double getDoubleProperty(String propertyName) { - return getTypedProperty(Double.class, propertyName, null); - } + /** + * Gets the property value as a double. + * + * @param propertyName + * the property name + * @return the property value + */ + public Double getDoubleProperty(String propertyName) { + return getTypedProperty(Double.class, propertyName, null); + } - /** - * Gets the property value as a list. - * - * @param propertyName - * the property name - * @return the property value - */ - @SuppressWarnings("unchecked") - public List getStringListProperty(String propertyName) { - return getTypedProperty(List.class, propertyName, null); - } + /** + * Gets the property value as a list. + * + * @param propertyName + * the property name + * @return the property value + */ + @SuppressWarnings("unchecked") + public List getStringListProperty(String propertyName) { + return getTypedProperty(List.class, propertyName, null); + } - /** - * This function retrieves the specified property as a generic JsonValue. - * - * @param propertyName - * the hierarchical name of the property to be retrieved (e.g. "level1/level2/prop1") - * @return a JsonValue representing the property's value or null if it wasn't found - */ - private JsonValue getProperty(String propertyName) { - JsonValue result = null; - PropertyGroup pg = loadConfiguration(); - if (pg != null) { - result = pg.getJsonValue(propertyName); - } - return result; - } + /** + * This function retrieves the specified property as a generic JsonValue. + * + * @param propertyName + * the hierarchical name of the property to be retrieved (e.g. "level1/level2/prop1") + * @return a JsonValue representing the property's value or null if it wasn't found + */ + private JsonValue getProperty(String propertyName) { + JsonValue result = null; + PropertyGroup pg = loadConfiguration(); + if (pg != null) { + result = pg.getJsonValue(propertyName); + } + return result; + } - /** - * Loads the specified file as a JSON file and returns a PropertyGroup containing the contents of the JSON file as - * the root property group. - * - * @param filename - * the name of the JSON file to be loaded - */ - private PropertyGroup loadConfiguration() { - if (config == null) { - try (InputStream is = resolveFile(fileName)) { - String templatedJson = IOUtils.toString(is, StandardCharsets.UTF_8); - String resolvedJson = StringSubstitutor.replace(templatedJson, EnvironmentVariables.get()); - try (JsonReader reader = JSON_READER_FACTORY.createReader(new StringReader(resolvedJson))) { - JsonObject jsonObj = reader.readObject(); - reader.close(); - config = new PropertyGroup(jsonObj); - } - } catch (IOException e) { - throw new RuntimeException("Unable to load configuration", e); - } - } - return config; - } + /** + * Loads the specified file as a JSON file and returns a PropertyGroup containing the contents of the JSON file as + * the root property group. + * + * @param filename + * the name of the JSON file to be loaded + */ + private PropertyGroup loadConfiguration() { + if (config == null) { + try (InputStream is = resolveFile(fileName)) { + String templatedJson = IOUtils.toString(is, StandardCharsets.UTF_8); + String resolvedJson = StringSubstitutor.replace(templatedJson, EnvironmentVariables.get()); + try (JsonReader reader = JSON_READER_FACTORY.createReader(new StringReader(resolvedJson))) { + JsonObject jsonObj = reader.readObject(); + reader.close(); + config = new PropertyGroup(jsonObj); + } + } catch (IOException e) { + throw new RuntimeException("Unable to load configuration", e); + } + } + return config; + } - /** - * Returns an InputStream for the specified filename. This function will first try to open the file using the - * filename as a relative or absolute filename. If that fails, then we'll try to find the file on the classpath. - * - * @param filename - * the name of the file to search for - * @return an InputStream to the file or throws a FileNotFoundException if not found - * @throws FileNotFoundException - */ - private static InputStream resolveFile(String filename) throws FileNotFoundException { - // First, try to use the filename as-is. - File f = new File(filename); - if (f.exists()) { - return new FileInputStream(f); - } + /** + * Returns an InputStream for the specified filename. This function will first try to open the file using the + * filename as a relative or absolute filename. If that fails, then we'll try to find the file on the classpath. + * + * @param filename + * the name of the file to search for + * @return an InputStream to the file or throws a FileNotFoundException if not found + * @throws FileNotFoundException + */ + private static InputStream resolveFile(String filename) throws FileNotFoundException { + // First, try to use the filename as-is. + File f = new File(filename); + if (f.exists()) { + return new FileInputStream(f); + } - // Next, look on the classpath. - InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); - if (is != null) { - return is; - } + // Next, look on the classpath. + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); + if (is != null) { + return is; + } - throw new FileNotFoundException("File '" + filename + "' was not found."); - } + throw new FileNotFoundException("File '" + filename + "' was not found."); + } - /** - * This generic function will perform the work of retrieving a property and then converting the resulting value to - * the appropriate type. - * - * @param propertyName - * the name of the property to retrieve - * @param defaultValue - * the default value to return in the event that the property is not found - * @return the typed property - */ - @SuppressWarnings("unchecked") - private T getTypedProperty(Class expectedDataType, String propertyName, T defaultValue) { - T result = null; + /** + * This generic function will perform the work of retrieving a property and then converting the resulting value to + * the appropriate type. + * + * @param propertyName + * the name of the property to retrieve + * @param defaultValue + * the default value to return in the event that the property is not found + * @return the typed property + */ + @SuppressWarnings("unchecked") + private T getTypedProperty(Class expectedDataType, String propertyName, T defaultValue) { + T result = null; - // Find the property as a generic JsonValue from either the current tenant's config or the default config. - JsonValue jsonValue = getProperty(propertyName); + // Find the property as a generic JsonValue from either the current tenant's config or the default config. + JsonValue jsonValue = getProperty(propertyName); - // If found, then convert the value to the expected type. - if (jsonValue != null) { - Object obj = null; - try { - obj = PropertyGroup.convertJsonValue(jsonValue); - if (obj != null) { - // If the property was of the expected type, then just do the assignment. - // Otherwise, we'll try to do some simple conversions (e.g. String --> Boolean). - if (expectedDataType.isAssignableFrom(obj.getClass())) { - result = (T) obj; - } else { - if (obj instanceof String) { - if (Boolean.class.equals(expectedDataType)) { - result = (T) Boolean.valueOf((String) obj); - } else if (Integer.class.equals(expectedDataType)) { - result = (T) Integer.valueOf((String) obj); - } else if (Double.class.equals(expectedDataType)) { - result = (T) Double.valueOf((String) obj); - } else { - throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() - + ", but was of type " - + obj.getClass().getName()); - } - } else if (obj instanceof Boolean) { - if (String.class.equals(expectedDataType)) { - result = (T) ((Boolean) obj).toString(); - } else { - throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() - + ", but was of type " - + obj.getClass().getName()); - } - } else { - throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() - + ", but was of type " - + obj.getClass().getName()); - } - } - } - } catch (Exception e) { - throw new RuntimeException("Unexpected error converting property '" + propertyName + "' to native type.", e); - } - } + // If found, then convert the value to the expected type. + if (jsonValue != null) { + Object obj = null; + try { + obj = PropertyGroup.convertJsonValue(jsonValue); + if (obj != null) { + // If the property was of the expected type, then just do the assignment. + // Otherwise, we'll try to do some simple conversions (e.g. String --> Boolean). + if (expectedDataType.isAssignableFrom(obj.getClass())) { + result = (T) obj; + } else { + if (obj instanceof String) { + if (Boolean.class.equals(expectedDataType)) { + result = (T) Boolean.valueOf((String) obj); + } else if (Integer.class.equals(expectedDataType)) { + result = (T) Integer.valueOf((String) obj); + } else if (Double.class.equals(expectedDataType)) { + result = (T) Double.valueOf((String) obj); + } else { + throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() + + ", but was of type " + + obj.getClass().getName()); + } + } else if (obj instanceof Boolean) { + if (String.class.equals(expectedDataType)) { + result = (T) ((Boolean) obj).toString(); + } else { + throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() + + ", but was of type " + + obj.getClass().getName()); + } + } else { + throw new RuntimeException("Expected property " + propertyName + " to be of type " + expectedDataType.getName() + + ", but was of type " + + obj.getClass().getName()); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Unexpected error converting property '" + propertyName + "' to native type.", e); + } + } - return (result != null ? result : defaultValue); - } + return (result != null ? result : defaultValue); + } - /** - * Utility class that allows mocking system environment variables retrieval in test classes (as Mockito disallows - * mocking static methods of {@link System}). - */ - public static class EnvironmentVariables { - /** - * Simple proxy method for {@link System#getenv()} that returns an unmodifiable string map view of the current - * system environment. - * - * @return the environment as a map of variable names to values - */ - public static Map get() { - return System.getenv(); - } - } + /** + * Utility class that allows mocking system environment variables retrieval in test classes (as Mockito disallows + * mocking static methods of {@link System}). + */ + public static class EnvironmentVariables { + /** + * Simple proxy method for {@link System#getenv()} that returns an unmodifiable string map view of the current + * system environment. + * + * @return the environment as a map of variable names to values + */ + public static Map get() { + return System.getenv(); + } + } }