diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentExportRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentExportRepresentation.java new file mode 100755 index 0000000000..7f834c3c4c --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentExportRepresentation.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 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.representations.idm; + +import org.keycloak.common.util.MultivaluedHashMap; + +/** + * @author Marek Posolda + */ +public class ComponentExportRepresentation { + + private String id; + private String name; + private String providerId; + private MultivaluedHashMap subComponents = new MultivaluedHashMap<>(); + private MultivaluedHashMap config = new MultivaluedHashMap<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } + + public MultivaluedHashMap getSubComponents() { + return subComponents; + } + + public void setSubComponents(MultivaluedHashMap subComponents) { + this.subComponents = subComponents; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index cb6aa323ea..67c1678ad1 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -18,6 +18,7 @@ package org.keycloak.representations.idm; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.keycloak.common.util.MultivaluedHashMap; import java.util.*; @@ -109,6 +110,7 @@ public class RealmRepresentation { private List identityProviders; private List identityProviderMappers; private List protocolMappers; + private MultivaluedHashMap components; protected Boolean internationalizationEnabled; protected Set supportedLocales; protected String defaultLocale; @@ -849,6 +851,14 @@ public class RealmRepresentation { this.clientTemplates = clientTemplates; } + public MultivaluedHashMap getComponents() { + return components; + } + + public void setComponents(MultivaluedHashMap components) { + this.components = components; + } + @JsonIgnore public boolean isIdentityFederationEnabled() { return identityProviders != null && !identityProviders.isEmpty(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index ffaf46911f..50e1b696db 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1438,7 +1438,15 @@ public class RealmAdapter implements RealmModel { @Override public List getComponents(String parentId, String providerType) { if (isUpdated()) return updated.getComponents(parentId, providerType); - List components = cached.getComponentsByParent().getList(parentId + providerType); + List components = cached.getComponentsByParentAndType().getList(parentId + providerType); + if (components == null) return Collections.EMPTY_LIST; + return Collections.unmodifiableList(components); + } + + @Override + public List getComponents(String parentId) { + if (isUpdated()) return updated.getComponents(parentId); + List components = cached.getComponentsByParent().getList(parentId); if (components == null) return Collections.EMPTY_LIST; return Collections.unmodifiableList(components); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 80997c3e77..7eab0ebac2 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -107,6 +107,7 @@ public class CachedRealm extends AbstractRevisioned { protected List requiredCredentials; protected List userFederationProviders; protected MultivaluedHashMap componentsByParent = new MultivaluedHashMap<>(); + protected MultivaluedHashMap componentsByParentAndType = new MultivaluedHashMap<>(); protected Map components = new HashMap<>(); protected MultivaluedHashMap userFederationMappers = new MultivaluedHashMap(); protected Set userFederationMapperSet; @@ -275,7 +276,10 @@ public class CachedRealm extends AbstractRevisioned { clientAuthenticationFlow = model.getClientAuthenticationFlow(); for (ComponentModel component : model.getComponents()) { - componentsByParent.add(component.getParentId() + component.getProviderType(), component); + componentsByParentAndType.add(component.getParentId() + component.getProviderType(), component); + } + for (ComponentModel component : model.getComponents()) { + componentsByParent.add(component.getParentId(), component); } for (ComponentModel component : model.getComponents()) { components.put(component.getId(), component); @@ -608,6 +612,10 @@ public class CachedRealm extends AbstractRevisioned { return componentsByParent; } + public MultivaluedHashMap getComponentsByParentAndType() { + return componentsByParentAndType; + } + public Map getComponents() { return components; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 093ebf342d..68aa02c254 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -2195,6 +2195,21 @@ public class RealmAdapter implements RealmModel, JpaModel { return rtn; } + @Override + public List getComponents(String parentId) { + TypedQuery query = em.createNamedQuery("getComponentsByParent", ComponentEntity.class) + .setParameter("realm", realm) + .setParameter("parentId", parentId); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (ComponentEntity c : results) { + ComponentModel model = entityToModel(c); + rtn.add(model); + + } + return rtn; + } + protected ComponentModel entityToModel(ComponentEntity c) { ComponentModel model = new ComponentModel(); model.setId(c.getId()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java index 69dad0da7d..a857978818 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java @@ -44,6 +44,7 @@ import java.util.Map; @NamedQueries({ @NamedQuery(name="getComponents", query="select attr from ComponentEntity attr where attr.realm = :realm"), @NamedQuery(name="getComponentsByParentAndType", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.providerType = :providerType and attr.parentId = :parentId"), + @NamedQuery(name="getComponentByParent", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"), @NamedQuery(name="getComponentIdsByParent", query="select attr.id from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"), @NamedQuery(name="deleteComponentByRealm", query="delete from ComponentEntity c where c.realm = :realm"), @NamedQuery(name="deleteComponentByParent", query="delete from ComponentEntity c where c.parentId = :parentId") diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 551f6fdc76..701cfb0b02 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -2135,6 +2135,19 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return results; } + @Override + public List getComponents(String parentId) { + List results = new LinkedList<>(); + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getParentId().equals(parentId)) { + ComponentModel model = entityToModel(entity); + results.add(model); + } + + } + return results; + } + protected ComponentModel entityToModel(ComponentEntity entity) { ComponentModel model = new ComponentModel(); model.setId(entity.getId()); diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index 47692ac652..04f1476716 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -291,6 +291,9 @@ public interface RealmModel extends RoleContainerModel { void removeComponent(ComponentModel component); void removeComponents(String parentId); List getComponents(String parentId, String providerType); + + List getComponents(String parentId); + List getComponents(); ComponentModel getComponent(String id); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index a20adcf4f2..7e813d6459 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -20,6 +20,7 @@ package org.keycloak.models.utils; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; import org.keycloak.hash.Pbkdf2PasswordHashProvider; import org.keycloak.migration.migrators.MigrationUtils; @@ -63,6 +64,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentExportRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; @@ -285,6 +287,12 @@ public class RepresentationToModel { newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders); } + if (rep.getComponents() != null) { + MultivaluedHashMap components = rep.getComponents(); + String parentId = newRealm.getId(); + importComponents(newRealm, components, parentId); + } + List providerModels = null; if (rep.getUserFederationProviders() != null) { providerModels = convertFederationProviders(rep.getUserFederationProviders()); @@ -346,6 +354,25 @@ public class RepresentationToModel { } } + protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { + for (Map.Entry> entry : components.entrySet()) { + String providerType = entry.getKey(); + for (ComponentExportRepresentation compRep : entry.getValue()) { + ComponentModel component = new ComponentModel(); + component.setId(compRep.getId()); + component.setName(compRep.getName()); + component.setConfig(compRep.getConfig()); + component.setProviderType(providerType); + component.setProviderId(compRep.getProviderId()); + component.setParentId(parentId); + component = newRealm.addComponentModel(component); + if (compRep.getSubComponents() != null) { + importComponents(newRealm, compRep.getSubComponents(), component.getId()); + } + } + } + } + public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) { if (realmRoles == null) return; diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 01f1186462..9665a50a49 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.keycloak.common.Version; import org.keycloak.common.util.Base64; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.*; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.*; @@ -178,9 +180,28 @@ public class ExportUtils { } } + // components + MultivaluedHashMap components = exportComponents(realm, realm.getId()); + rep.setComponents(components); + return rep; } + public static MultivaluedHashMap exportComponents(RealmModel realm, String parentId) { + List componentList = realm.getComponents(parentId); + MultivaluedHashMap components = new MultivaluedHashMap<>(); + for (ComponentModel component : componentList) { + ComponentExportRepresentation compRep = new ComponentExportRepresentation(); + compRep.setId(component.getId()); + compRep.setProviderId(component.getProviderId()); + compRep.setConfig(component.getConfig()); + compRep.setName(component.getName()); + compRep.setSubComponents(exportComponents(realm, component.getId())); + components.add(component.getProviderType(), compRep); + } + return components; + } + /** * Full export of application including claims and secret * @param client