From 1d2d3e5ca526a558ac7b320d290e5455c470133f Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Wed, 17 Aug 2022 10:12:09 +0200 Subject: [PATCH] Move UserFederatedStorageProvider into legacy module Closes #13627 --- .../exportimport/util/ExportUtils.java | 121 +----------------- .../datastore/LegacyExportImportManager.java | 16 +++ .../UserAttributeFederatedStorage.java | 0 .../UserBrokerLinkFederatedStorage.java | 0 .../UserConsentFederatedStorage.java | 0 .../UserFederatedStorageProvider.java | 0 .../UserFederatedStorageProviderFactory.java | 0 .../UserFederatedStorageProviderSpi.java | 0 .../UserFederatedUserCredentialStore.java | 0 .../UserGroupMembershipFederatedStorage.java | 0 .../UserNotBeforeFederatedStorage.java | 0 .../UserRequiredActionsFederatedStorage.java | 0 .../UserRoleMappingsFederatedStorage.java | 0 .../services/org.keycloak.provider.Spi | 1 + .../map/datastore/MapExportImportManager.java | 7 + .../keycloak/exportimport/ExportAdapter.java | 49 +++++++ .../keycloak/exportimport}/ExportOptions.java | 19 ++- .../models/utils/ModelToRepresentation.java | 99 ++++++++++++++ .../models/utils/RepresentationToModel.java | 1 - .../models/utils/StripSecretsUtils.java | 4 +- .../keycloak/storage/ExportImportManager.java | 4 + .../services/org.keycloak.provider.Spi | 1 - .../org/keycloak/models/KeycloakSession.java | 10 -- .../admin/ResourceServerService.java | 3 +- .../services/DefaultKeycloakSession.java | 11 -- .../resources/admin/RealmAdminResource.java | 29 ++++- .../DefaultClientValidationProvider.java | 2 - .../testsuite/admin/PermissionsTest.java | 3 + .../partialexport/PartialExportTest.java | 5 +- 29 files changed, 227 insertions(+), 158 deletions(-) rename {services => model/legacy-private}/src/main/java/org/keycloak/exportimport/util/ExportUtils.java (82%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java (100%) rename {server-spi => model/legacy}/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java (100%) create mode 100644 server-spi-private/src/main/java/org/keycloak/exportimport/ExportAdapter.java rename {services/src/main/java/org/keycloak/exportimport/util => server-spi-private/src/main/java/org/keycloak/exportimport}/ExportOptions.java (66%) diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java similarity index 82% rename from services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java rename to model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 54d6081310..444e004517 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -22,18 +22,11 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.AuthorizationProviderFactory; -import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; -import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; -import org.keycloak.authorization.store.PolicyStore; -import org.keycloak.authorization.store.StoreFactory; import org.keycloak.common.Profile; import org.keycloak.common.Version; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.credential.CredentialModel; +import org.keycloak.exportimport.ExportOptions; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.FederatedIdentityModel; @@ -53,18 +46,11 @@ import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.representations.idm.authorization.PolicyRepresentation; -import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; -import org.keycloak.representations.idm.authorization.ResourceRepresentation; -import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; -import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.storage.federated.UserFederatedStorageProvider; -import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -293,118 +279,15 @@ public class ExportUtils { ClientRepresentation clientRep = ModelToRepresentation.toRepresentation(client, session); clientRep.setSecret(client.getSecret()); if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { - clientRep.setAuthorizationSettings(exportAuthorizationSettings(session, client)); + clientRep.setAuthorizationSettings(ModelToRepresentation.toResourceServerRepresentation(session, client)); } return clientRep; } - public static ResourceServerRepresentation exportAuthorizationSettings(KeycloakSession session, ClientModel client) { - AuthorizationProviderFactory providerFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); - AuthorizationProvider authorization = providerFactory.create(session, client.getRealm()); - StoreFactory storeFactory = authorization.getStoreFactory(); - ResourceServer settingsModel = authorization.getStoreFactory().getResourceServerStore().findByClient(client); - - if (settingsModel == null) { - return null; - } - - ResourceServerRepresentation representation = toRepresentation(settingsModel, client); - - representation.setId(null); - representation.setName(null); - representation.setClientId(null); - - List resources = storeFactory.getResourceStore().findByResourceServer(settingsModel) - .stream().map(resource -> { - ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization); - - if (rep.getOwner().getId().equals(settingsModel.getClientId())) { - rep.setOwner((ResourceOwnerRepresentation) null); - } else { - rep.getOwner().setId(null); - } - rep.getScopes().forEach(scopeRepresentation -> { - scopeRepresentation.setId(null); - scopeRepresentation.setIconUri(null); - }); - - return rep; - }).collect(Collectors.toList()); - - representation.setResources(resources); - - List policies = new ArrayList<>(); - PolicyStore policyStore = storeFactory.getPolicyStore(); - - policies.addAll(policyStore.findByResourceServer(settingsModel) - .stream().filter(policy -> !policy.getType().equals("resource") && !policy.getType().equals("scope") && policy.getOwner() == null) - .map(policy -> createPolicyRepresentation(authorization, policy)).collect(Collectors.toList())); - policies.addAll(policyStore.findByResourceServer(settingsModel) - .stream().filter(policy -> (policy.getType().equals("resource") || policy.getType().equals("scope") && policy.getOwner() == null)) - .map(policy -> createPolicyRepresentation(authorization, policy)).collect(Collectors.toList())); - - representation.setPolicies(policies); - - List scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel).stream().map(scope -> { - ScopeRepresentation rep = toRepresentation(scope); - - rep.setPolicies(null); - rep.setResources(null); - - return rep; - }).collect(Collectors.toList()); - - representation.setScopes(scopes); - - return representation; - } - - private static PolicyRepresentation createPolicyRepresentation(AuthorizationProvider authorizationProvider, Policy policy) { - try { - PolicyRepresentation rep = toRepresentation(policy, authorizationProvider, true, true); - - Map config = new HashMap<>(rep.getConfig()); - - rep.setConfig(config); - - Set scopes = policy.getScopes(); - - if (!scopes.isEmpty()) { - List scopeNames = scopes.stream().map(Scope::getName).collect(Collectors.toList()); - config.put("scopes", JsonSerialization.writeValueAsString(scopeNames)); - } - - Set policyResources = policy.getResources(); - - if (!policyResources.isEmpty()) { - List resourceNames = policyResources.stream().map(Resource::getName).collect(Collectors.toList()); - config.put("resources", JsonSerialization.writeValueAsString(resourceNames)); - } - - Set associatedPolicies = policy.getAssociatedPolicies(); - - if (!associatedPolicies.isEmpty()) { - config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList()))); - } - - return rep; - } catch (Exception e) { - throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e); - } - } - public static List exportRoles(Stream roles) { return roles.map(ExportUtils::exportRole).collect(Collectors.toList()); } - public static List getRoleNames(Collection roles) { - List roleNames = new ArrayList<>(); - for (RoleModel role : roles) { - roleNames.add(role.getName()); - } - return roleNames; - } - /** * Full export of role including composite roles * @param role diff --git a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java index 9c2ad4227d..330ffa0a41 100644 --- a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java @@ -21,6 +21,9 @@ import org.jboss.logging.Logger; import org.keycloak.common.enums.SslRequired; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.exportimport.ExportAdapter; +import org.keycloak.exportimport.ExportOptions; +import org.keycloak.exportimport.util.ExportUtils; import org.keycloak.keys.KeyProvider; import org.keycloak.migration.MigrationProvider; import org.keycloak.migration.migrators.MigrateTo8_0_0; @@ -89,8 +92,10 @@ import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageUtil; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.userprofile.UserProfileProvider; +import org.keycloak.util.JsonSerialization; import org.keycloak.validation.ValidationUtil; +import javax.ws.rs.core.MediaType; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -108,6 +113,7 @@ import static org.keycloak.models.utils.RepresentationToModel.createGroups; import static org.keycloak.models.utils.RepresentationToModel.createRoleMappings; import static org.keycloak.models.utils.RepresentationToModel.importGroup; import static org.keycloak.models.utils.RepresentationToModel.importRoles; +import static org.keycloak.models.utils.StripSecretsUtils.stripForExport; /** * This wraps the functionality about export/import for legacy storage. This will be handled differently for the new map storage, @@ -123,6 +129,16 @@ public class LegacyExportImportManager implements ExportImportManager { this.session = session; } + public void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback) { + callback.setType(MediaType.APPLICATION_JSON); + callback.writeToOutputStream(outputStream -> { + RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options, false); + stripForExport(session, rep); + JsonSerialization.writeValueToStream(outputStream, rep); + outputStream.close(); + }); + } + @Override public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) { convertDeprecatedSocialProviders(rep); diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserNotBeforeFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java b/model/legacy/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java similarity index 100% rename from server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java rename to model/legacy/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java diff --git a/model/legacy/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/legacy/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 07b3ad75f5..ce5ebe63eb 100644 --- a/model/legacy/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/model/legacy/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -16,3 +16,4 @@ # org.keycloak.storage.UserStorageProviderSpi +org.keycloak.storage.federated.UserFederatedStorageProviderSpi diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java index 4463e1dc2b..c2a1bf3aac 100644 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java +++ b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java @@ -21,6 +21,9 @@ import org.jboss.logging.Logger; import org.keycloak.common.enums.SslRequired; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.models.ModelException; +import org.keycloak.exportimport.ExportAdapter; +import org.keycloak.exportimport.ExportOptions; import org.keycloak.keys.KeyProvider; import org.keycloak.migration.MigrationProvider; import org.keycloak.migration.migrators.MigrationUtils; @@ -413,6 +416,10 @@ public class MapExportImportManager implements ExportImportManager { return role; } + public void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback) { + throw new ModelException("exporting for map storage is currently not supported"); + } + private static void convertDeprecatedDefaultRoles(RealmRepresentation rep, RealmModel newRealm) { if (rep.getDefaultRole() == null) { diff --git a/server-spi-private/src/main/java/org/keycloak/exportimport/ExportAdapter.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportAdapter.java new file mode 100644 index 0000000000..173f136dcd --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportAdapter.java @@ -0,0 +1,49 @@ +/* + * 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.exportimport; + +import java.io.IOException; + +/** + * This adapter allows the exporter to act independent of APIs used to serve the exported data to the caller. + * + * @author Alexander Schwartz + */ +public interface ExportAdapter { + /** + * Set the mime type of the output. + * + * @param mediaType Mime Type + */ + void setType(String mediaType); + + /** + * Write to the output stream. Once writing is complete, close it. + * + * @param consumer A consumer to that accepts the output stream. + */ + void writeToOutputStream(ConsumerOfOutputStream consumer); + + /** + * Custom consumer that is allowed to throw an {@link IOException} as writing to an output stream might do this. + */ + @FunctionalInterface + interface ConsumerOfOutputStream { + void accept(java.io.OutputStream t) throws IOException; + } +} diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportOptions.java similarity index 66% rename from services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java rename to server-spi-private/src/main/java/org/keycloak/exportimport/ExportOptions.java index ba4704a29b..b930cca728 100644 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java +++ b/server-spi-private/src/main/java/org/keycloak/exportimport/ExportOptions.java @@ -1,4 +1,21 @@ -package org.keycloak.exportimport.util; +/* + * 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.exportimport; /** * @author Marko Strukelj diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 81a72bba7b..5eb035a4f7 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -19,12 +19,15 @@ package org.keycloak.models.utils; import org.jboss.logging.Logger; import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.store.StoreFactory; import org.keycloak.common.Profile; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; @@ -1073,4 +1076,100 @@ public class ModelToRepresentation { return representation; } + + public static ResourceServerRepresentation toResourceServerRepresentation(KeycloakSession session, ClientModel client) { + AuthorizationProviderFactory providerFactory = (AuthorizationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(AuthorizationProvider.class); + AuthorizationProvider authorization = providerFactory.create(session, client.getRealm()); + StoreFactory storeFactory = authorization.getStoreFactory(); + ResourceServer settingsModel = authorization.getStoreFactory().getResourceServerStore().findByClient(client); + + if (settingsModel == null) { + return null; + } + + ResourceServerRepresentation representation = toRepresentation(settingsModel, client); + + representation.setId(null); + representation.setName(null); + representation.setClientId(null); + + List resources = storeFactory.getResourceStore().findByResourceServer(settingsModel) + .stream().map(resource -> { + ResourceRepresentation rep = toRepresentation(resource, settingsModel, authorization); + + if (rep.getOwner().getId().equals(settingsModel.getClientId())) { + rep.setOwner((ResourceOwnerRepresentation) null); + } else { + rep.getOwner().setId(null); + } + rep.getScopes().forEach(scopeRepresentation -> { + scopeRepresentation.setId(null); + scopeRepresentation.setIconUri(null); + }); + + return rep; + }).collect(Collectors.toList()); + + representation.setResources(resources); + + List policies = new ArrayList<>(); + PolicyStore policyStore = storeFactory.getPolicyStore(); + + policies.addAll(policyStore.findByResourceServer(settingsModel) + .stream().filter(policy -> !policy.getType().equals("resource") && !policy.getType().equals("scope") && policy.getOwner() == null) + .map(policy -> toRepresentation(authorization, policy)).collect(Collectors.toList())); + policies.addAll(policyStore.findByResourceServer(settingsModel) + .stream().filter(policy -> (policy.getType().equals("resource") || policy.getType().equals("scope") && policy.getOwner() == null)) + .map(policy -> toRepresentation(authorization, policy)).collect(Collectors.toList())); + + representation.setPolicies(policies); + + List scopes = storeFactory.getScopeStore().findByResourceServer(settingsModel).stream().map(scope -> { + ScopeRepresentation rep = toRepresentation(scope); + + rep.setPolicies(null); + rep.setResources(null); + + return rep; + }).collect(Collectors.toList()); + + representation.setScopes(scopes); + + return representation; + } + + private static PolicyRepresentation toRepresentation(AuthorizationProvider authorizationProvider, Policy policy) { + try { + PolicyRepresentation rep = toRepresentation(policy, authorizationProvider, true, true); + + Map config = new HashMap<>(rep.getConfig()); + + rep.setConfig(config); + + Set scopes = policy.getScopes(); + + if (!scopes.isEmpty()) { + List scopeNames = scopes.stream().map(Scope::getName).collect(Collectors.toList()); + config.put("scopes", JsonSerialization.writeValueAsString(scopeNames)); + } + + Set policyResources = policy.getResources(); + + if (!policyResources.isEmpty()) { + List resourceNames = policyResources.stream().map(Resource::getName).collect(Collectors.toList()); + config.put("resources", JsonSerialization.writeValueAsString(resourceNames)); + } + + Set associatedPolicies = policy.getAssociatedPolicies(); + + if (!associatedPolicies.isEmpty()) { + config.put("applyPolicies", JsonSerialization.writeValueAsString(associatedPolicies.stream().map(associated -> associated.getName()).collect(Collectors.toList()))); + } + + return rep; + } catch (Exception e) { + throw new RuntimeException("Error while exporting policy [" + policy.getName() + "].", e); + } + } + } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index a8d06756ad..3d883afb3a 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -112,7 +112,6 @@ import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.storage.DatastoreProvider; -import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.util.JsonSerialization; import static org.keycloak.protocol.saml.util.ArtifactBindingUtils.computeArtifactBindingIdentifierString; diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java index 80e4089ef0..401d37d950 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java @@ -89,7 +89,7 @@ public class StripSecretsUtils { return rep; } - public static RealmRepresentation stripForExport(KeycloakSession session, RealmRepresentation rep) { + public static void stripForExport(KeycloakSession session, RealmRepresentation rep) { strip(rep); List clients = rep.getClients(); @@ -127,8 +127,6 @@ public class StripSecretsUtils { strip(u); } } - - return rep; } public static UserRepresentation strip(UserRepresentation user) { diff --git a/server-spi-private/src/main/java/org/keycloak/storage/ExportImportManager.java b/server-spi-private/src/main/java/org/keycloak/storage/ExportImportManager.java index eb53e266fa..cdbc9b3760 100644 --- a/server-spi-private/src/main/java/org/keycloak/storage/ExportImportManager.java +++ b/server-spi-private/src/main/java/org/keycloak/storage/ExportImportManager.java @@ -17,6 +17,8 @@ package org.keycloak.storage; +import org.keycloak.exportimport.ExportAdapter; +import org.keycloak.exportimport.ExportOptions; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; @@ -33,4 +35,6 @@ public interface ExportImportManager { void updateRealm(RealmRepresentation rep, RealmModel realm); UserModel createUser(RealmModel realm, UserRepresentation userRep); + + void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback); } diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 65fd3e2d98..4fbe5fd28f 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -17,7 +17,6 @@ org.keycloak.component.ComponentFactorySpi org.keycloak.provider.ExceptionConverterSpi -org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.models.ClientSpi org.keycloak.models.ClientScopeSpi org.keycloak.models.GroupSpi diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 5344c38f63..d9f46a81cb 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -22,7 +22,6 @@ import org.keycloak.provider.InvalidationHandler.InvalidableObjectType; import org.keycloak.provider.Provider; import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.sessions.AuthenticationSessionProvider; -import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.vault.VaultTranscriber; import java.util.Set; @@ -302,15 +301,6 @@ public interface KeycloakSession { @Deprecated RoleProvider roleLocalStorage(); - /** - * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage. - * No cache in front. - * - * @deprecated Access to the legacy store is no longer possible via this method. Adjust your code according to the Keycloak 19 Upgrading Guide. - */ - @Deprecated - UserFederatedStorageProvider userFederatedStorage(); - /** * Key manager * diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java index 7e7ff50e05..f45ebfc578 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java @@ -36,7 +36,6 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; -import org.keycloak.exportimport.util.ExportUtils; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; @@ -124,7 +123,7 @@ public class ResourceServerService { @Produces("application/json") public Response exportSettings() { this.auth.realm().requireManageAuthorization(); - return Response.ok(ExportUtils.exportAuthorizationSettings(session, client)).build(); + return Response.ok(ModelToRepresentation.toResourceServerRepresentation(session, client)).build(); } @Path("/import") diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index edc26f94bb..ee88f8e323 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -48,7 +48,6 @@ import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.models.LegacySessionSupportProvider; import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.storage.DatastoreProvider; -import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.vault.DefaultVaultTranscriber; import org.keycloak.vault.VaultProvider; import org.keycloak.vault.VaultTranscriber; @@ -83,7 +82,6 @@ public class DefaultKeycloakSession implements KeycloakSession { private UserSessionProvider sessionProvider; private UserLoginFailureProvider userLoginFailureProvider; private AuthenticationSessionProvider authenticationSessionProvider; - private UserFederatedStorageProvider userFederatedStorageProvider; private final KeycloakContext context; private KeyManager keyManager; private ThemeManager themeManager; @@ -164,15 +162,6 @@ public class DefaultKeycloakSession implements KeycloakSession { return factory; } - @Override - @Deprecated - public UserFederatedStorageProvider userFederatedStorage() { - if (userFederatedStorageProvider == null) { - userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class); - } - return userFederatedStorageProvider; - } - @Override @Deprecated public UserProvider userLocalStorage() { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 9250c42d44..be811dad32 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -17,7 +17,6 @@ package org.keycloak.services.resources.admin; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; -import static org.keycloak.models.utils.StripSecretsUtils.stripForExport; import static org.keycloak.util.JsonSerialization.readValue; import java.security.cert.X509Certificate; @@ -48,6 +47,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.StreamingOutput; import com.fasterxml.jackson.core.type.TypeReference; @@ -71,8 +71,8 @@ import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.exportimport.ClientDescriptionConverter; import org.keycloak.exportimport.ClientDescriptionConverterFactory; -import org.keycloak.exportimport.util.ExportOptions; -import org.keycloak.exportimport.util.ExportUtils; +import org.keycloak.exportimport.ExportAdapter; +import org.keycloak.exportimport.ExportOptions; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.Constants; @@ -110,6 +110,8 @@ import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; +import org.keycloak.storage.DatastoreProvider; +import org.keycloak.storage.ExportImportManager; import org.keycloak.storage.LegacyStoreSyncEvent; import org.keycloak.utils.ProfileHelper; import org.keycloak.utils.ReservedCharValidator; @@ -1055,8 +1057,7 @@ public class RealmAdminResource { */ @Path("partial-export") @POST - @Produces(MediaType.APPLICATION_JSON) - public RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles, + public Response partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles, @QueryParam("exportClients") Boolean exportClients) { auth.realm().requireViewRealm(); @@ -1074,8 +1075,22 @@ public class RealmAdminResource { // this means that if clients is true but groups/roles is false the service account is exported without roles // the other option is just include service accounts if clientsExported && groupsAndRolesExported ExportOptions options = new ExportOptions(false, clientsExported, groupsAndRolesExported, clientsExported); - RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options, false); - return stripForExport(session, rep); + + ExportImportManager exportProvider = session.getProvider(DatastoreProvider.class).getExportImportManager(); + + Response.ResponseBuilder response = Response.ok(); + + exportProvider.exportRealm(realm, options, new ExportAdapter() { + @Override + public void setType(String mediaType) { + response.type(mediaType); + } + @Override + public void writeToOutputStream(ConsumerOfOutputStream consumer) { + response.entity((StreamingOutput) consumer::accept); + } + }); + return response.build(); } @Path("keys") diff --git a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java index e975e4c5d7..b2202e2ed7 100644 --- a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java +++ b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java @@ -16,10 +16,8 @@ */ package org.keycloak.validation; -import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.authenticators.util.LoAUtil; import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCConfigAttributes; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java index 24f5bcf82e..d8240661a1 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java @@ -1768,6 +1768,9 @@ public class PermissionsTest extends AbstractKeycloakTest { @Test public void partialExport() { + // re-enable as part of https://github.com/keycloak/keycloak/issues/14291 + ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE); + invoke(new Invocation() { public void invoke(RealmResource realm) { realm.partialExport(false, false); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java index 38dc89eae4..9cbeb8f62a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java @@ -1,7 +1,7 @@ package org.keycloak.testsuite.admin.partialexport; -import java.util.Arrays; import org.junit.Test; +import org.keycloak.common.Profile; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentExportRepresentation; @@ -12,6 +12,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.admin.AbstractAdminTest; import java.util.HashMap; @@ -41,6 +42,8 @@ public class PartialExportTest extends AbstractAdminTest { @Test public void testExport() { + // re-enable as part of https://github.com/keycloak/keycloak/issues/14291 + ProfileAssume.assumeFeatureDisabled(Profile.Feature.MAP_STORAGE); // exportGroupsAndRoles == false, exportClients == false RealmRepresentation rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(false, false);