Modify RealmsAdminResource.importRealm to work with InputStream
Closes #13609
This commit is contained in:
parent
cff5cfb6df
commit
be2deb0517
11 changed files with 262 additions and 11 deletions
0
model/legacy-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
Executable file → Normal file
0
model/legacy-private/src/main/java/org/keycloak/migration/MigrationModelManager.java
Executable file → Normal file
|
@ -41,6 +41,7 @@ import org.keycloak.models.FederatedIdentityModel;
|
|||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OAuth2DeviceConfig;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.ParConfig;
|
||||
|
@ -86,6 +87,7 @@ import org.keycloak.representations.idm.UserFederationMapperRepresentation;
|
|||
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ExportImportManager;
|
||||
import org.keycloak.storage.ImportRealmFromRepresentation;
|
||||
import org.keycloak.storage.UserStoragePrivateUtil;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
@ -96,6 +98,8 @@ import org.keycloak.util.JsonSerialization;
|
|||
import org.keycloak.validation.ValidationUtil;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -139,6 +143,18 @@ public class LegacyExportImportManager implements ExportImportManager {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel importRealm(InputStream requestBody) {
|
||||
RealmRepresentation rep;
|
||||
try {
|
||||
rep = JsonSerialization.readValue(requestBody, RealmRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new ModelException("unable to read contents from stream", e);
|
||||
}
|
||||
logger.debugv("importRealm: {0}", rep.getRealm());
|
||||
return ImportRealmFromRepresentation.fire(session, rep);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
||||
convertDeprecatedSocialProviders(rep);
|
||||
|
|
|
@ -79,9 +79,13 @@ 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.userprofile.UserProfileProvider;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.validation.ValidationUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -104,7 +108,7 @@ import static org.keycloak.models.utils.RepresentationToModel.importRoles;
|
|||
* This wraps the functionality about export/import for legacy storage.
|
||||
*
|
||||
* <p>
|
||||
* Currently this only removes the user-storage and federation code from LegacyExportImportManager.
|
||||
* Currently, this only removes the user-storage and federation code from LegacyExportImportManager.
|
||||
* <p>
|
||||
* In the future, this needs to be rewritten completely.
|
||||
*
|
||||
|
@ -420,6 +424,26 @@ public class MapExportImportManager implements ExportImportManager {
|
|||
throw new ModelException("exporting for map storage is currently not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel importRealm(InputStream requestBody) {
|
||||
/* A future implementation that would differentiate between the old JSON representations and the new file store
|
||||
might want to add the file name or the media type as a method parameter to switch between different implementations. */
|
||||
|
||||
RealmRepresentation rep;
|
||||
try {
|
||||
rep = JsonSerialization.readValue(requestBody, RealmRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new ModelException("unable to read contents from stream", e);
|
||||
}
|
||||
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. */
|
||||
|
||||
return ImportRealmFromRepresentation.fire(session, rep);
|
||||
}
|
||||
|
||||
private static void convertDeprecatedDefaultRoles(RealmRepresentation rep, RealmModel newRealm) {
|
||||
if (rep.getDefaultRole() == null) {
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Manage importing and updating of realms for the legacy store.
|
||||
*
|
||||
|
@ -37,4 +39,6 @@ public interface ExportImportManager {
|
|||
UserModel createUser(RealmModel realm, UserRepresentation userRep);
|
||||
|
||||
void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback);
|
||||
|
||||
RealmModel importRealm(InputStream requestBody);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
||||
/**
|
||||
* Event to trigger that will complete the import for a given realm representation.
|
||||
* <p />
|
||||
* This event was created as the import of a JSON via the UI/REST API can be called using a JSON representation that contains
|
||||
* only the name of the realm and if it is enabled.
|
||||
* <p />
|
||||
* In the future, this might not be needed if this is done when the legacy store migration is complete and the functionality
|
||||
* is bundled within the map storage.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImportRealmFromRepresentation implements ProviderEvent {
|
||||
private final KeycloakSession session;
|
||||
private final RealmRepresentation realmRepresentation;
|
||||
|
||||
private RealmModel realmModel;
|
||||
|
||||
public ImportRealmFromRepresentation(KeycloakSession session, RealmRepresentation realmRepresentation) {
|
||||
this.session = session;
|
||||
this.realmRepresentation = realmRepresentation;
|
||||
}
|
||||
|
||||
public static RealmModel fire(KeycloakSession session, RealmRepresentation rep) {
|
||||
ImportRealmFromRepresentation event = new ImportRealmFromRepresentation(session, rep);
|
||||
session.getKeycloakSessionFactory().publish(event);
|
||||
return event.getRealmModel();
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public RealmRepresentation getRealmRepresentation() {
|
||||
return realmRepresentation;
|
||||
}
|
||||
|
||||
public void setRealmModel(RealmModel realmModel) {
|
||||
this.realmModel = realmModel;
|
||||
}
|
||||
|
||||
public RealmModel getRealmModel() {
|
||||
return realmModel;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.services.managers;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.storage.ImportRealmFromRepresentation;
|
||||
|
||||
/**
|
||||
* Provider to listen for {@link org.keycloak.storage.ImportRealmFromRepresentation} events.
|
||||
* If that is no longer needed after further steps around the legacy storage migration, it can be removed.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
@Deprecated
|
||||
public class RealmManagerProviderFactory implements ProviderFactory<RealmManagerProviderFactory>, Provider {
|
||||
@Override
|
||||
public RealmManagerProviderFactory create(KeycloakSession session) {
|
||||
throw new ModelException("This shouldn't be instantiated, this should only listen to events");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
factory.register(event -> {
|
||||
if (event instanceof ImportRealmFromRepresentation) {
|
||||
ImportRealmFromRepresentation importRealmFromRepresentation = (ImportRealmFromRepresentation) event;
|
||||
RealmModel realmModel = new RealmManager(importRealmFromRepresentation.getSession()).importRealm(importRealmFromRepresentation.getRealmRepresentation());
|
||||
importRealmFromRepresentation.setRealmModel(realmModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
}
|
|
@ -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.services.managers;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* Provider to listen for {@link org.keycloak.storage.ImportRealmFromRepresentation} events.
|
||||
* If that is no longer needed after further steps around the legacy storage migration, it can be removed.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
@Deprecated
|
||||
public class RealmManagerSpi implements Spi {
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "realm-manager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return RealmManagerProviderFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return RealmManagerProviderFactory.class;
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ import org.keycloak.services.ForbiddenException;
|
|||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||
import org.keycloak.storage.DatastoreProvider;
|
||||
import org.keycloak.storage.ExportImportManager;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DefaultValue;
|
||||
|
@ -49,6 +51,7 @@ import javax.ws.rs.core.Context;
|
|||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
@ -114,23 +117,21 @@ public class RealmsAdminResource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Import a realm
|
||||
*
|
||||
* Import a realm.
|
||||
* <p>
|
||||
* Imports a realm from a full representation of that realm. Realm name must be unique.
|
||||
*
|
||||
* @param rep JSON representation of the realm
|
||||
* @return
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response importRealm(final RealmRepresentation rep) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
public Response importRealm(InputStream requestBody) {
|
||||
AdminPermissions.realms(session, auth).requireCreateRealm();
|
||||
|
||||
logger.debugv("importRealm: {0}", rep.getRealm());
|
||||
ExportImportManager exportImportManager = session.getProvider(DatastoreProvider.class).getExportImportManager();
|
||||
|
||||
try {
|
||||
RealmModel realm = realmManager.importRealm(rep);
|
||||
RealmModel realm = exportImportManager.importRealm(requestBody);
|
||||
|
||||
grantPermissionsToRealmCreator(realm);
|
||||
|
||||
URI location = AdminRoot.realmsUrl(session.getContext().getUri()).path(realm.getName()).build();
|
||||
|
@ -139,6 +140,7 @@ public class RealmsAdminResource {
|
|||
return Response.created(location).build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
logger.error("Conflict detected", e);
|
||||
if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly();
|
||||
return ErrorResponse.exists("Conflict detected. See logs for details");
|
||||
} catch (PasswordPolicyNotMetException e) {
|
||||
logger.error("Password policy not met for user " + e.getUsername(), e);
|
||||
|
|
|
@ -19,6 +19,7 @@ org.keycloak.exportimport.ClientDescriptionConverterSpi
|
|||
org.keycloak.wellknown.WellKnownSpi
|
||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||
org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi
|
||||
org.keycloak.services.managers.RealmManagerSpi
|
||||
org.keycloak.authentication.actiontoken.ActionTokenHandlerSpi
|
||||
org.keycloak.services.x509.X509ClientCertificateLookupSpi
|
||||
org.keycloak.protocol.oidc.ext.OIDCExtSPI
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
org.keycloak.services.managers.RealmManagerProviderFactory
|
|
@ -102,7 +102,7 @@ public class UncaughtErrorPageTest extends AbstractKeycloakTest {
|
|||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||
String accessToken = adminClient.tokenManager().getAccessTokenString();
|
||||
|
||||
HttpPost post = new HttpPost(suiteContext.getAuthServerInfo().getUriBuilder().path("/auth/admin/realms").build());
|
||||
HttpPost post = new HttpPost(suiteContext.getAuthServerInfo().getUriBuilder().path("/auth/admin/realms/master/components").build());
|
||||
post.setEntity(new StringEntity("{ invalid : invalid }"));
|
||||
post.setHeader("Authorization", "bearer " + accessToken);
|
||||
post.setHeader("Content-Type", "application/json");
|
||||
|
@ -122,7 +122,7 @@ public class UncaughtErrorPageTest extends AbstractKeycloakTest {
|
|||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||
String accessToken = adminClient.tokenManager().getAccessTokenString();
|
||||
|
||||
HttpPost post = new HttpPost(suiteContext.getAuthServerInfo().getUriBuilder().path("/auth/admin/realms").build());
|
||||
HttpPost post = new HttpPost(suiteContext.getAuthServerInfo().getUriBuilder().path("/auth/admin/realms/master/components").build());
|
||||
post.setEntity(new StringEntity("{\"<img src=alert(1)>\":1}"));
|
||||
post.setHeader("Authorization", "bearer " + accessToken);
|
||||
post.setHeader("Content-Type", "application/json");
|
||||
|
|
Loading…
Reference in a new issue