Importing a representation by first creating the defaults, importing a representation and then copying it over to the real store.
This is the foundation for a setup that's needed when importing the new file store for which importing the representation serves as a placeholder. Closes #14583
This commit is contained in:
parent
a4f6134ba3
commit
b6b6d01a8a
12 changed files with 927 additions and 43 deletions
|
@ -60,6 +60,7 @@ import javax.lang.model.type.TypeKind;
|
|||
public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
|
||||
|
||||
private static final Collection<String> autogenerated = new TreeSet<>();
|
||||
private static final String ID_FIELD_NAME = "Id";
|
||||
|
||||
private final Generator[] generators = new Generator[] {
|
||||
new ClonerGenerator(),
|
||||
|
@ -260,7 +261,7 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
|||
String simpleClassName = className.substring(lastDot + 1);
|
||||
String mapImplClassName = className + "Impl";
|
||||
String mapSimpleClassName = simpleClassName + "Impl";
|
||||
boolean hasId = methodsPerAttribute.containsKey("Id") || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||
boolean hasId = methodsPerAttribute.containsKey(ID_FIELD_NAME) || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
|
||||
boolean hasDeepClone = allParentMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
||||
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
||||
.map(ExecutableElement::getReturnType)
|
||||
|
@ -706,32 +707,28 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
|||
pw.println("import " + FQN_DEEP_CLONER + ";");
|
||||
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
|
||||
pw.println("public class " + clonerSimpleClassName + " {");
|
||||
pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {");
|
||||
|
||||
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
|
||||
final String fieldName = me.getKey();
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(fieldName, methods);
|
||||
if (fieldType == null) {
|
||||
return;
|
||||
}
|
||||
if (methodsPerAttribute.containsKey(ID_FIELD_NAME)) {
|
||||
pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {");
|
||||
|
||||
cloneField(e, fieldName, methods, fieldType, pw);
|
||||
});
|
||||
pw.println(" target.clearUpdatedFlag();");
|
||||
pw.println(" return target;");
|
||||
pw.println(" }");
|
||||
// If the entity has an ID, set the ID first and then set all other attributes.
|
||||
// This was important when working with Jpa storage as the ID is the one field needed to persist an entity.
|
||||
HashSet<ExecutableElement> idMethods = methodsPerAttribute.get(ID_FIELD_NAME);
|
||||
TypeMirror idFieldType = determineFieldType(ID_FIELD_NAME, idMethods);
|
||||
cloneField(e, ID_FIELD_NAME, idMethods, idFieldType, pw);
|
||||
|
||||
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
|
||||
pw.println(" return deepCloneNoId(original, target);");
|
||||
pw.println(" }");
|
||||
|
||||
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
|
||||
|
||||
if (methodsPerAttribute.containsKey("Id")) {
|
||||
pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {");
|
||||
|
||||
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
|
||||
final String fieldName = me.getKey();
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(fieldName, methods);
|
||||
if (fieldType == null || "Id".equals(fieldName)) {
|
||||
if (fieldType == null || ID_FIELD_NAME.equals(fieldName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -742,6 +739,24 @@ public class GenerateEntityImplementationsProcessor extends AbstractGenerateEnti
|
|||
pw.println(" }");
|
||||
|
||||
autogenerated.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
|
||||
} else {
|
||||
pw.println(" public static " + className + " deepClone(" + className + " original, " + className + " target) {");
|
||||
|
||||
methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(me -> {
|
||||
final String fieldName = me.getKey();
|
||||
HashSet<ExecutableElement> methods = me.getValue();
|
||||
TypeMirror fieldType = determineFieldType(fieldName, methods);
|
||||
if (fieldType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cloneField(e, fieldName, methods, fieldType, pw);
|
||||
});
|
||||
pw.println(" target.clearUpdatedFlag();");
|
||||
pw.println(" return target;");
|
||||
pw.println(" }");
|
||||
|
||||
autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
|
||||
}
|
||||
pw.println("}");
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E exte
|
|||
/**
|
||||
* Use the cache within the session to ensure that there is only one instance per entity within the current session.
|
||||
*/
|
||||
private E mapToEntityDelegateUnique(RE original) {
|
||||
protected E mapToEntityDelegateUnique(RE original) {
|
||||
if (original == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,13 @@ import javax.persistence.criteria.CriteriaBuilder;
|
|||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.map.role.MapRoleEntity;
|
||||
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_ROLE;
|
||||
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
|
||||
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
|
||||
|
@ -33,6 +36,8 @@ import org.keycloak.models.map.storage.jpa.role.entity.JpaRoleEntity;
|
|||
|
||||
public class JpaRoleMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRoleEntity, MapRoleEntity, RoleModel> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JpaRoleMapKeycloakTransaction.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public JpaRoleMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
|
||||
super(session, JpaRoleEntity.class, RoleModel.class, em);
|
||||
|
@ -61,6 +66,16 @@ public class JpaRoleMapKeycloakTransaction extends JpaMapKeycloakTransaction<Jpa
|
|||
return new JpaRoleModelCriteriaBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapRoleEntity create(MapRoleEntity mapEntity) {
|
||||
JpaRoleEntity jpaEntity = new JpaRoleEntity(CLONER);
|
||||
MapRoleEntity entity = new JpaMapRoleEntityDelegate(jpaEntity, em);
|
||||
CLONER.deepClone(mapEntity, entity);
|
||||
logger.tracef("tx %d: create entity %s", hashCode(), jpaEntity.getId());
|
||||
setEntityVersion(jpaEntity);
|
||||
return mapToEntityDelegateUnique(jpaEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapRoleEntity mapToEntityDelegate(JpaRoleEntity original) {
|
||||
return new JpaMapRoleEntityDelegate(original, em);
|
||||
|
|
|
@ -41,11 +41,25 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class JpaMapRoleEntityDelegate extends MapRoleEntityDelegate {
|
||||
private final EntityManager em;
|
||||
private final JpaRoleEntity original;
|
||||
|
||||
private Set<String> compositeRoles;
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
if (super.getId() == null) {
|
||||
super.setId(id);
|
||||
// As the entity will be used when creating the composite roles, it needs to be persisted before that.
|
||||
// The ID not being set indicates a new entity that hasn't been persisted yet, and the ID is the minimum field for persisting it.
|
||||
em.persist(original);
|
||||
} else {
|
||||
super.setId(id);
|
||||
}
|
||||
}
|
||||
|
||||
public JpaMapRoleEntityDelegate(JpaRoleEntity original, EntityManager em) {
|
||||
super(new JpaRoleDelegateProvider(original, em));
|
||||
this.original = original;
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
|
@ -64,7 +78,9 @@ public class JpaMapRoleEntityDelegate extends MapRoleEntityDelegate {
|
|||
Query query = em.createNamedQuery("deleteAllChildRolesFromCompositeRole");
|
||||
query.setParameter("roleId", StringKeyConverter.UUIDKey.INSTANCE.fromString(getId()));
|
||||
query.executeUpdate();
|
||||
compositeRoles.forEach(this::addCompositeRole);
|
||||
if (compositeRoles != null) {
|
||||
compositeRoles.forEach(this::addCompositeRole);
|
||||
}
|
||||
this.compositeRoles = compositeRoles;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ public abstract class AbstractMapProviderFactory<T extends Provider, V extends A
|
|||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
protected MapStorage<V, M> getStorage(KeycloakSession session) {
|
||||
public MapStorage<V, M> getStorage(KeycloakSession session) {
|
||||
ProviderFactory<MapStorageProvider> storageProviderFactory = getProviderFactoryOrComponentFactory(session, storageConfigScope);
|
||||
final MapStorageProvider factory = storageProviderFactory.create(session);
|
||||
session.enlistForClose(factory);
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.map.datastore;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.ClientScopeSpi;
|
||||
import org.keycloak.models.ClientSpi;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.GroupSpi;
|
||||
import org.keycloak.models.KeyManager;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RealmSpi;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
import org.keycloak.models.RoleSpi;
|
||||
import org.keycloak.models.ThemeManager;
|
||||
import org.keycloak.models.TokenManager;
|
||||
import org.keycloak.models.UserCredentialManager;
|
||||
import org.keycloak.models.UserLoginFailureProvider;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.UserSpi;
|
||||
import org.keycloak.models.map.client.MapClientProvider;
|
||||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeProvider;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.models.map.group.MapGroupProvider;
|
||||
import org.keycloak.models.map.group.MapGroupProviderFactory;
|
||||
import org.keycloak.models.map.realm.MapRealmProvider;
|
||||
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
||||
import org.keycloak.models.map.role.MapRoleProvider;
|
||||
import org.keycloak.models.map.role.MapRoleProviderFactory;
|
||||
import org.keycloak.models.map.user.MapUserProvider;
|
||||
import org.keycloak.models.map.user.MapUserProviderFactory;
|
||||
import org.keycloak.provider.InvalidationHandler;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||
import org.keycloak.vault.VaultTranscriber;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
|
||||
/**
|
||||
* This implementation of {@link KeycloakSession} wraps an existing session and directs all calls to the datastore provider
|
||||
* to a separate {@link KeycloakSessionFactory}.
|
||||
* This allows it to create instantiate different storage providers during import.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public class ImportKeycloakSession implements KeycloakSession {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ImportKeycloakSession.class);
|
||||
|
||||
private final KeycloakSessionFactory factory;
|
||||
private final KeycloakSession session;
|
||||
private final MapRealmProvider realmProvider;
|
||||
private final MapClientProvider clientProvider;
|
||||
private final MapClientScopeProvider clientScopeProvider;
|
||||
private final MapGroupProvider groupProvider;
|
||||
private final MapRoleProvider roleProvider;
|
||||
private final MapUserProvider userProvider;
|
||||
|
||||
private final Set<AbstractMapProviderFactory<?, ?, ?>> providerFactories = new HashSet<>();
|
||||
|
||||
public ImportKeycloakSession(ImportSessionFactoryWrapper factory, KeycloakSession session) {
|
||||
this.factory = factory;
|
||||
this.session = session;
|
||||
realmProvider = createProvider(RealmSpi.class, MapRealmProviderFactory.class);
|
||||
clientProvider = createProvider(ClientSpi.class, MapClientProviderFactory.class);
|
||||
clientScopeProvider = createProvider(ClientScopeSpi.class, MapClientScopeProviderFactory.class);
|
||||
groupProvider = createProvider(GroupSpi.class, MapGroupProviderFactory.class);
|
||||
roleProvider = createProvider(RoleSpi.class, MapRoleProviderFactory.class);
|
||||
userProvider = createProvider(UserSpi.class, MapUserProviderFactory.class);
|
||||
}
|
||||
|
||||
private <P extends Provider, V extends AbstractEntity, M> P createProvider(Class<? extends Spi> spi, Class<? extends AbstractMapProviderFactory<P, V, M>> providerFactoryClass) {
|
||||
try {
|
||||
AbstractMapProviderFactory<P, V, M> providerFactory = providerFactoryClass.getConstructor().newInstance();
|
||||
providerFactory.init(Config.scope(spi.getDeclaredConstructor().newInstance().getName(), providerFactory.getId()));
|
||||
providerFactories.add(providerFactory);
|
||||
return providerFactory.create(this);
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException |
|
||||
InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakContext getContext() {
|
||||
return session.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakTransactionManager getTransactionManager() {
|
||||
return session.getTransactionManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> T getProvider(Class<T> clazz) {
|
||||
LOG.warnf("Calling getProvider(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace());
|
||||
return session.getProvider(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> T getProvider(Class<T> clazz, String id) {
|
||||
LOG.warnf("Calling getProvider(%s, %s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), id, getShortStackTrace());
|
||||
return session.getProvider(clazz, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> T getComponentProvider(Class<T> clazz, String componentId) {
|
||||
LOG.warnf("Calling getComponentProvider(%s, %s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), componentId, getShortStackTrace());
|
||||
return session.getComponentProvider(clazz, componentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> T getComponentProvider(Class<T> clazz, String componentId, Function<KeycloakSessionFactory, ComponentModel> modelGetter) {
|
||||
LOG.warnf("Calling getComponentProvider(%s, %s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), componentId, getShortStackTrace());
|
||||
return session.getComponentProvider(clazz, componentId, modelGetter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> T getProvider(Class<T> clazz, ComponentModel componentModel) {
|
||||
LOG.warnf("Calling getProvider(%s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace());
|
||||
return session.getProvider(clazz, componentModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> Set<String> listProviderIds(Class<T> clazz) {
|
||||
LOG.warnf("Calling listProviderIds(%s, ...) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace());
|
||||
return session.listProviderIds(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> Set<T> getAllProviders(Class<T> clazz) {
|
||||
LOG.warnf("Calling getAllProviders(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", clazz.getName(), getShortStackTrace());
|
||||
return session.getAllProviders(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass(String providerClassName) {
|
||||
LOG.warnf("Calling getProviderClass(%s) on the parent session. Revisit this to ensure it doesn't have side effects on the parent session.", providerClassName, getShortStackTrace());
|
||||
return session.getProviderClass(providerClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String attribute) {
|
||||
return session.getAttribute(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attribute, Class<T> clazz) {
|
||||
return session.getAttribute(attribute, clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getAttributeOrDefault(String attribute, T defaultValue) {
|
||||
return session.getAttributeOrDefault(attribute, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String attribute) {
|
||||
return session.removeAttribute(attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
session.setAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate(InvalidationHandler.InvalidableObjectType type, Object... ids) {
|
||||
// For now, forward the invalidations only to those providers created specifically for this import session.
|
||||
providerFactories.stream()
|
||||
.filter(InvalidationHandler.class::isInstance)
|
||||
.map(InvalidationHandler.class::cast)
|
||||
.forEach(ih -> ih.invalidate(this, type, ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enlistForClose(Provider provider) {
|
||||
session.enlistForClose(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSessionFactory getKeycloakSessionFactory() {
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmProvider realms() {
|
||||
return realmProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientProvider clients() {
|
||||
return clientProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider clientScopes() {
|
||||
return clientScopeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupProvider groups() {
|
||||
return groupProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleProvider roles() {
|
||||
return roleProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionProvider sessions() {
|
||||
throw new ModelException("not supported yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserLoginFailureProvider loginFailures() {
|
||||
throw new ModelException("not supported yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationSessionProvider authenticationSessions() {
|
||||
throw new ModelException("not supported yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public UserProvider userCache() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProvider users() {
|
||||
return userProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ClientProvider clientStorageManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ClientScopeProvider clientScopeStorageManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public RoleProvider roleStorageManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public GroupProvider groupStorageManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public UserProvider userStorageManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public UserCredentialManager userCredentialManager() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public UserProvider userLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public RealmProvider realmLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ClientProvider clientLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ClientScopeProvider clientScopeLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public GroupProvider groupLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public RoleProvider roleLocalStorage() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyManager keys() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeManager theme() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenManager tokens() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultTranscriber vault() {
|
||||
throw new ModelException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientPolicyManager clientPolicy() {
|
||||
return session.clientPolicy();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.map.datastore;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
import org.keycloak.models.map.storage.MapStorageSpi;
|
||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
import org.keycloak.provider.ProviderEventListener;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This wraps an existing KeycloakSessionFactory and redirects all calls to a {@link MapStorageProvider} to
|
||||
* {@link org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProvider}. This allows all operations to
|
||||
* be in-memory. The final contents of the store can then be copied over to the final store once the import is complete.
|
||||
* <p/>
|
||||
* For this to work, the CHM provider needs to be registered as a provider.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public class ImportSessionFactoryWrapper implements KeycloakSessionFactory {
|
||||
private ConcurrentHashMapStorageProviderFactory concurrentHashMapStorageProviderFactory;
|
||||
|
||||
@Override
|
||||
public KeycloakSession create() {
|
||||
concurrentHashMapStorageProviderFactory = new ConcurrentHashMapStorageProviderFactory();
|
||||
concurrentHashMapStorageProviderFactory.init(Config.scope(MapStorageSpi.NAME, concurrentHashMapStorageProviderFactory.getId()));
|
||||
return new ImportKeycloakSession(this, keycloakSessionFactory.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Spi> getSpis() {
|
||||
return keycloakSessionFactory.getSpis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spi getSpi(Class<? extends Provider> providerClass) {
|
||||
return keycloakSessionFactory.getSpi(providerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
|
||||
if (clazz == MapStorageProvider.class) {
|
||||
return keycloakSessionFactory.getProviderFactory(clazz, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||
}
|
||||
return keycloakSessionFactory.getProviderFactory(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
|
||||
if (clazz == MapStorageProvider.class) {
|
||||
return (ProviderFactory<T>) concurrentHashMapStorageProviderFactory;
|
||||
}
|
||||
return keycloakSessionFactory.getProviderFactory(clazz, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String realmId, String componentId, Function<KeycloakSessionFactory, ComponentModel> modelGetter) {
|
||||
return keycloakSessionFactory.getProviderFactory(clazz, realmId, componentId, modelGetter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ProviderFactory> getProviderFactoriesStream(Class<? extends Provider> clazz) {
|
||||
return keycloakSessionFactory.getProviderFactoriesStream(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getServerStartupTimestamp() {
|
||||
return keycloakSessionFactory.getServerStartupTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
keycloakSessionFactory.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
|
||||
keycloakSessionFactory.invalidate(session, type, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ProviderEventListener listener) {
|
||||
keycloakSessionFactory.register(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ProviderEventListener listener) {
|
||||
keycloakSessionFactory.unregister(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(ProviderEvent event) {
|
||||
keycloakSessionFactory.publish(event);
|
||||
}
|
||||
|
||||
private final KeycloakSessionFactory keycloakSessionFactory;
|
||||
|
||||
public ImportSessionFactoryWrapper(KeycloakSessionFactory keycloakSessionFactory) {
|
||||
this.keycloakSessionFactory = keycloakSessionFactory;
|
||||
}
|
||||
}
|
|
@ -18,9 +18,15 @@
|
|||
package org.keycloak.models.map.datastore;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.ImpersonationConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.exportimport.ExportAdapter;
|
||||
import org.keycloak.exportimport.ExportOptions;
|
||||
|
@ -43,18 +49,29 @@ import org.keycloak.models.OTPPolicy;
|
|||
import org.keycloak.models.ParConfig;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
import org.keycloak.models.ScopeContainerModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.WebAuthnPolicy;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.models.map.realm.MapRealmEntity;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.DefaultKeyProviders;
|
||||
import org.keycloak.models.utils.DefaultRequiredActions;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
|
||||
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||
|
@ -76,12 +93,14 @@ import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.ScopeMappingRepresentation;
|
||||
import org.keycloak.representations.idm.UserConsentRepresentation;
|
||||
import org.keycloak.representations.idm.UserFederationMapperRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ExportImportManager;
|
||||
import org.keycloak.storage.ImportRealmFromRepresentation;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
import org.keycloak.storage.SetDefaultsForNewRealm;
|
||||
import org.keycloak.userprofile.UserProfileProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.utils.ReservedCharValidator;
|
||||
import org.keycloak.validation.ValidationUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -93,10 +112,14 @@ import java.util.HashSet;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
|
||||
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||
import static org.keycloak.models.utils.RepresentationToModel.createCredentials;
|
||||
import static org.keycloak.models.utils.RepresentationToModel.createFederatedIdentities;
|
||||
import static org.keycloak.models.utils.RepresentationToModel.createGroups;
|
||||
|
@ -119,8 +142,21 @@ public class MapExportImportManager implements ExportImportManager {
|
|||
private final KeycloakSession session;
|
||||
private static final Logger logger = Logger.getLogger(MapExportImportManager.class);
|
||||
|
||||
/**
|
||||
* Use the old import via the logical layer vs. the new method importing to CHM first and then copying over
|
||||
* This is a temporary to test the functionality with the old representations until the new file store arrives in main,
|
||||
* and will then be removed.
|
||||
*/
|
||||
private final boolean useNewImportMethod;
|
||||
|
||||
public MapExportImportManager(KeycloakSession session) {
|
||||
this.session = session;
|
||||
useNewImportMethod = Boolean.parseBoolean(System.getProperty(MapExportImportManager.class.getName(), "false"));
|
||||
}
|
||||
|
||||
public MapExportImportManager(KeycloakSession session, boolean useNewImportMethod) {
|
||||
this.session = session;
|
||||
this.useNewImportMethod = useNewImportMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -403,11 +439,32 @@ public class MapExportImportManager implements ExportImportManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (newRealm.getComponentsStream(newRealm.getId(), KeyProvider.class.getName()).count() == 0) {
|
||||
if (!useNewImportMethod) {
|
||||
if (newRealm.getComponentsStream(newRealm.getId(), KeyProvider.class.getName()).count() == 0) {
|
||||
if (rep.getPrivateKey() != null) {
|
||||
DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate());
|
||||
} else {
|
||||
DefaultKeyProviders.createProviders(newRealm);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (rep.getPrivateKey() != null) {
|
||||
DefaultKeyProviders.createProviders(newRealm, rep.getPrivateKey(), rep.getCertificate());
|
||||
} else {
|
||||
DefaultKeyProviders.createProviders(newRealm);
|
||||
|
||||
ComponentModel rsa = new ComponentModel();
|
||||
rsa.setName("rsa");
|
||||
rsa.setParentId(newRealm.getId());
|
||||
rsa.setProviderId("rsa");
|
||||
rsa.setProviderType(KeyProvider.class.getName());
|
||||
|
||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||
config.putSingle("priority", "100");
|
||||
config.putSingle("privateKey", rep.getPrivateKey());
|
||||
if (rep.getCertificate() != null) {
|
||||
config.putSingle("certificate", rep.getCertificate());
|
||||
}
|
||||
rsa.setConfig(config);
|
||||
|
||||
newRealm.addComponentModel(rsa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -437,11 +494,230 @@ public class MapExportImportManager implements ExportImportManager {
|
|||
}
|
||||
logger.debugv("importRealm: {0}", rep.getRealm());
|
||||
|
||||
/* The import for the JSON representation might be called from the Admin UI, where it will be empty except for
|
||||
the realm name and if the realm is enabled. For that scenario, it would need to create all missing elements,
|
||||
which is done by firing an event to call the existing implementation in the RealmManager. */
|
||||
if (!useNewImportMethod) {
|
||||
/* The import for the JSON representation might be called from the Admin UI, where it will be empty except for
|
||||
the realm name and if the realm is enabled. For that scenario, it would need to create all missing elements,
|
||||
which is done by firing an event to call the existing implementation in the RealmManager. */
|
||||
return ImportRealmFromRepresentation.fire(session, rep);
|
||||
} else {
|
||||
/* This makes use of the representation to mimic the future setup: Some kind of import into a ConcurrentHashMap in-memory and then copying
|
||||
that over to the real store. This is the basis for future file store import. Results are different
|
||||
when importing, for example, an empty list of roles vs a non-existing list of roles, and possibility in other ways.
|
||||
Importing from a classic representation will eventually be removed and replaced when the new file store arrived. */
|
||||
return importToChmAndThenCopyOver(rep);
|
||||
}
|
||||
}
|
||||
|
||||
private RealmModel importToChmAndThenCopyOver(RealmRepresentation rep) {
|
||||
String id = rep.getId();
|
||||
if (id == null || id.trim().isEmpty()) {
|
||||
id = KeycloakModelUtils.generateId();
|
||||
} else {
|
||||
ReservedCharValidator.validate(id);
|
||||
}
|
||||
|
||||
ReservedCharValidator.validate(rep.getRealm());
|
||||
|
||||
RealmModel realm;
|
||||
RealmModel currentRealm = session.getContext().getRealm();
|
||||
|
||||
try {
|
||||
|
||||
String _id = id;
|
||||
KeycloakModelUtils.runJobInTransaction(new ImportSessionFactoryWrapper(session.getKeycloakSessionFactory()), chmSession -> {
|
||||
// import the representation
|
||||
fillRealm(chmSession, _id, rep);
|
||||
|
||||
// copy over the realm from in-memory to the real
|
||||
copyRealm(_id, chmSession);
|
||||
copyEntities(_id, chmSession, ClientProvider.class, ClientModel.class, ClientModel.SearchableFields.REALM_ID);
|
||||
copyEntities(_id, chmSession, ClientScopeProvider.class, ClientScopeModel.class, ClientScopeModel.SearchableFields.REALM_ID);
|
||||
copyEntities(_id, chmSession, GroupProvider.class, GroupModel.class, GroupModel.SearchableFields.REALM_ID);
|
||||
copyEntities(_id, chmSession, UserProvider.class, UserModel.class, UserModel.SearchableFields.REALM_ID);
|
||||
copyEntities(_id, chmSession, RoleProvider.class, RoleModel.class, RoleModel.SearchableFields.REALM_ID);
|
||||
|
||||
// clear the CHM store
|
||||
chmSession.getTransactionManager().setRollbackOnly();
|
||||
});
|
||||
|
||||
realm = session.realms().getRealm(id);
|
||||
session.getContext().setRealm(realm);
|
||||
setupMasterAdminManagement(realm);
|
||||
ImpersonationConstants.setupImpersonationService(session, realm);
|
||||
fireRealmPostCreate(realm);
|
||||
} finally {
|
||||
session.getContext().setRealm(currentRealm);
|
||||
}
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
private void copyRealm(String realmId, KeycloakSession sessionChm) {
|
||||
MapRealmEntity realmEntityChm = (MapRealmEntity) getTransaction(sessionChm, RealmProvider.class).read(realmId);
|
||||
getTransaction(session, RealmProvider.class).create(realmEntityChm);
|
||||
}
|
||||
|
||||
private static <P extends Provider, E extends AbstractEntity, M> MapKeycloakTransaction<E, M> getTransaction(KeycloakSession session, Class<P> provider) {
|
||||
ProviderFactory<P> factoryChm = session.getKeycloakSessionFactory().getProviderFactory(provider);
|
||||
return ((AbstractMapProviderFactory<P, E, M>) factoryChm).getStorage(session).createTransaction(session);
|
||||
}
|
||||
|
||||
private <P extends Provider, M> void copyEntities(String realmId, KeycloakSession sessionChm, Class<P> provider, Class<M> model, SearchableModelField<M> field) {
|
||||
MapKeycloakTransaction<AbstractEntity, M> txChm = getTransaction(sessionChm, provider);
|
||||
MapKeycloakTransaction<AbstractEntity, M> txOrig = getTransaction(session, provider);
|
||||
|
||||
DefaultModelCriteria<M> mcb = criteria();
|
||||
mcb = mcb.compare(field, ModelCriteriaBuilder.Operator.EQ, realmId);
|
||||
|
||||
txChm.read(withCriteria(mcb)).forEach(txOrig::create);
|
||||
}
|
||||
|
||||
private static void fillRealm(KeycloakSession session, String id, RealmRepresentation rep) {
|
||||
RealmModel realm = session.realms().createRealm(id, rep.getRealm());
|
||||
session.getContext().setRealm(realm);
|
||||
SetDefaultsForNewRealm.fire(session, realm);
|
||||
MapExportImportManager mapExportImportManager = new MapExportImportManager(session);
|
||||
mapExportImportManager.clearDefaultsThatConflictWithRepresentation(rep, realm);
|
||||
mapExportImportManager.importRealm(rep, realm, false);
|
||||
}
|
||||
|
||||
private void clearDefaultsThatConflictWithRepresentation(RealmRepresentation rep, RealmModel newRealm) {
|
||||
if (rep.getDefaultRole() != null) {
|
||||
if (newRealm.getDefaultRole() != null) {
|
||||
newRealm.removeRole(newRealm.getDefaultRole());
|
||||
// set the new role here already as the legacy code expects it this way
|
||||
newRealm.setDefaultRole(RepresentationToModel.createRole(newRealm, rep.getDefaultRole()));
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getRequiredActions() != null) {
|
||||
for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) {
|
||||
RequiredActionProviderModel requiredActionProviderByAlias = newRealm.getRequiredActionProviderByAlias(action.getAlias());
|
||||
if (requiredActionProviderByAlias != null) {
|
||||
newRealm.removeRequiredActionProvider(requiredActionProviderByAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getRoles() != null) {
|
||||
for (RoleRepresentation representation : rep.getRoles().getRealm()) {
|
||||
RoleModel role = newRealm.getRole(representation.getName());
|
||||
if (role != null && (newRealm.getDefaultRole() == null || newRealm.getDefaultRole() != null && !Objects.equals(role.getId(), newRealm.getDefaultRole().getId()))) {
|
||||
newRealm.removeRole(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getPrivateKey() != null) {
|
||||
newRealm.getComponentsStream(newRealm.getId(), KeyProvider.class.getName())
|
||||
.filter(component -> Objects.equals(component.getProviderId(), "rsa-generated") || Objects.equals(component.getProviderId(), "rsa-enc-generated"))
|
||||
.collect(Collectors.toList()).forEach(newRealm::removeComponent);
|
||||
// will later create the "rsa" provider
|
||||
}
|
||||
|
||||
if (rep.getClients() != null) {
|
||||
for (ClientRepresentation resourceRep : rep.getClients()) {
|
||||
ClientModel clientByClientId = newRealm.getClientByClientId(resourceRep.getClientId());
|
||||
if (clientByClientId != null) {
|
||||
newRealm.removeClient(clientByClientId.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getClientScopes() != null) {
|
||||
for (ClientScopeRepresentation resourceRep : rep.getClientScopes()) {
|
||||
Optional<ClientScopeModel> existingClientScope = newRealm.getClientScopesStream().filter(clientScopeModel -> clientScopeModel.getName().equals(resourceRep.getName())).findFirst();
|
||||
if (existingClientScope.isPresent()) {
|
||||
newRealm.removeClientScope(existingClientScope.get().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getComponents() != null) {
|
||||
clearExistingComponents(newRealm, rep.getComponents());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void clearExistingComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components) {
|
||||
for (Map.Entry<String, List<ComponentExportRepresentation>> entry : components.entrySet()) {
|
||||
String providerType = entry.getKey();
|
||||
for (ComponentExportRepresentation compRep : entry.getValue()) {
|
||||
newRealm.getComponentsStream(newRealm.getId(), providerType)
|
||||
.filter(component -> Objects.equals(component.getProviderId(), compRep.getProviderId())).findAny().ifPresent(newRealm::removeComponent);
|
||||
if (compRep.getSubComponents() != null) {
|
||||
clearExistingComponents(newRealm, compRep.getSubComponents());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setupMasterAdminManagement(RealmModel realm) {
|
||||
// Need to refresh masterApp for current realm
|
||||
String adminRealmName = Config.getAdminRealm();
|
||||
RealmModel adminRealm = session.realms().getRealmByName(adminRealmName);
|
||||
ClientModel masterApp = adminRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName()));
|
||||
if (masterApp == null) {
|
||||
createMasterAdminManagement(realm);
|
||||
return;
|
||||
}
|
||||
realm.setMasterAdminClient(masterApp);
|
||||
}
|
||||
|
||||
private void createMasterAdminManagement(RealmModel realm) {
|
||||
RealmModel adminRealm;
|
||||
RoleModel adminRole;
|
||||
|
||||
if (realm.getName().equals(Config.getAdminRealm())) {
|
||||
adminRealm = realm;
|
||||
|
||||
adminRole = realm.addRole(AdminRoles.ADMIN);
|
||||
|
||||
RoleModel createRealmRole = realm.addRole(AdminRoles.CREATE_REALM);
|
||||
adminRole.addCompositeRole(createRealmRole);
|
||||
createRealmRole.setDescription("${role_" + AdminRoles.CREATE_REALM + "}");
|
||||
} else {
|
||||
adminRealm = session.realms().getRealmByName(Config.getAdminRealm());
|
||||
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
|
||||
}
|
||||
adminRole.setDescription("${role_" + AdminRoles.ADMIN + "}");
|
||||
|
||||
ClientModel realmAdminApp = KeycloakModelUtils.createManagementClient(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName()));
|
||||
// No localized name for now
|
||||
realmAdminApp.setName(realm.getName() + " Realm");
|
||||
realm.setMasterAdminClient(realmAdminApp);
|
||||
|
||||
for (String r : AdminRoles.ALL_REALM_ROLES) {
|
||||
RoleModel role = realmAdminApp.addRole(r);
|
||||
role.setDescription("${role_" + r + "}");
|
||||
adminRole.addCompositeRole(role);
|
||||
}
|
||||
addQueryCompositeRoles(realmAdminApp);
|
||||
}
|
||||
|
||||
public void addQueryCompositeRoles(ClientModel realmAccess) {
|
||||
RoleModel queryClients = realmAccess.getRole(AdminRoles.QUERY_CLIENTS);
|
||||
RoleModel queryUsers = realmAccess.getRole(AdminRoles.QUERY_USERS);
|
||||
RoleModel queryGroups = realmAccess.getRole(AdminRoles.QUERY_GROUPS);
|
||||
|
||||
RoleModel viewClients = realmAccess.getRole(AdminRoles.VIEW_CLIENTS);
|
||||
viewClients.addCompositeRole(queryClients);
|
||||
RoleModel viewUsers = realmAccess.getRole(AdminRoles.VIEW_USERS);
|
||||
viewUsers.addCompositeRole(queryUsers);
|
||||
viewUsers.addCompositeRole(queryGroups);
|
||||
}
|
||||
|
||||
private void fireRealmPostCreate(RealmModel realm) {
|
||||
session.getKeycloakSessionFactory().publish(new RealmModel.RealmPostCreateEvent() {
|
||||
@Override
|
||||
public RealmModel getCreatedRealm() {
|
||||
return realm;
|
||||
}
|
||||
@Override
|
||||
public KeycloakSession getKeycloakSession() {
|
||||
return session;
|
||||
}
|
||||
});
|
||||
|
||||
return ImportRealmFromRepresentation.fire(session, rep);
|
||||
}
|
||||
|
||||
private static void convertDeprecatedDefaultRoles(RealmRepresentation rep, RealmModel newRealm) {
|
||||
|
@ -1005,20 +1281,6 @@ public class MapExportImportManager implements ExportImportManager {
|
|||
}
|
||||
|
||||
|
||||
public static ComponentModel convertFedMapperToComponent(RealmModel realm, ComponentModel parent, UserFederationMapperRepresentation rep, String newMapperType) {
|
||||
ComponentModel mapper = new ComponentModel();
|
||||
mapper.setId(rep.getId());
|
||||
mapper.setName(rep.getName());
|
||||
mapper.setProviderId(rep.getFederationMapperType());
|
||||
mapper.setProviderType(newMapperType);
|
||||
mapper.setParentId(parent.getId());
|
||||
if (rep.getConfig() != null) {
|
||||
for (Map.Entry<String, String> entry : rep.getConfig().entrySet()) {
|
||||
mapper.getConfig().putSingle(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
|
||||
for (Map.Entry<String, List<ComponentExportRepresentation>> entry : components.entrySet()) {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
|
||||
/**
|
||||
* Event to trigger that will add defaults for a realm after it has been imported.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public class SetDefaultsForNewRealm implements ProviderEvent {
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realmModel;
|
||||
|
||||
public SetDefaultsForNewRealm(KeycloakSession session, RealmModel realmModel) {
|
||||
this.session = session;
|
||||
this.realmModel = realmModel;
|
||||
}
|
||||
|
||||
public static void fire(KeycloakSession session, RealmModel realm) {
|
||||
SetDefaultsForNewRealm event = new SetDefaultsForNewRealm(session, realm);
|
||||
session.getKeycloakSessionFactory().publish(event);
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public RealmModel getRealmModel() {
|
||||
return realmModel;
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.DefaultClientScopes;
|
||||
import org.keycloak.models.utils.DefaultKeyProviders;
|
||||
import org.keycloak.models.utils.DefaultRequiredActions;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
|
@ -778,4 +779,23 @@ public class RealmManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void setDefaultsForNewRealm(RealmModel realm) {
|
||||
// setup defaults
|
||||
setupRealmDefaults(realm);
|
||||
KeycloakModelUtils.setupDefaultRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName().toLowerCase());
|
||||
setupRealmAdminManagement(realm);
|
||||
setupAccountManagement(realm);
|
||||
setupBrokerService(realm);
|
||||
setupAdminConsole(realm);
|
||||
setupAdminConsoleLocaleMapper(realm);
|
||||
setupAdminCli(realm);
|
||||
setupAuthenticationFlows(realm);
|
||||
setupRequiredActions(realm);
|
||||
setupOfflineTokens(realm, null);
|
||||
createDefaultClientScopes(realm);
|
||||
setupAuthorizationServices(realm);
|
||||
setupClientRegistrations(realm);
|
||||
session.clientPolicy().setupClientPoliciesOnCreatedRealm(realm);
|
||||
DefaultKeyProviders.createProviders(realm);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.storage.ImportRealmFromRepresentation;
|
||||
import org.keycloak.storage.SetDefaultsForNewRealm;
|
||||
|
||||
/**
|
||||
* Provider to listen for {@link org.keycloak.storage.ImportRealmFromRepresentation} events.
|
||||
|
@ -50,6 +51,9 @@ public class RealmManagerProviderFactory implements ProviderFactory<RealmManager
|
|||
ImportRealmFromRepresentation importRealmFromRepresentation = (ImportRealmFromRepresentation) event;
|
||||
RealmModel realmModel = new RealmManager(importRealmFromRepresentation.getSession()).importRealm(importRealmFromRepresentation.getRealmRepresentation());
|
||||
importRealmFromRepresentation.setRealmModel(realmModel);
|
||||
} else if (event instanceof SetDefaultsForNewRealm) {
|
||||
SetDefaultsForNewRealm setDefaultsForNewRealm = (SetDefaultsForNewRealm) event;
|
||||
new RealmManager(setDefaultsForNewRealm.getSession()).setDefaultsForNewRealm(setDefaultsForNewRealm.getRealmModel());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue