From fe1ede26bea3e24597aa142148f31a7a4e4de09d Mon Sep 17 00:00:00 2001 From: mposolda Date: Sat, 11 Jul 2015 17:27:59 +0200 Subject: [PATCH 1/2] KEYCLOAK-1558 Can't import the file exported in 1.2.0.Beta1 through admin console --- .../services/managers/RealmManager.java | 81 +++++++++++-------- .../resources/admin/RealmsAdminResource.java | 8 +- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 1aff2edd77..a0e12359b1 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -21,7 +21,9 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultRequiredActions; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.OAuthClientRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.timer.TimerProvider; @@ -267,13 +269,27 @@ public class RealmManager { setupMasterAdminManagement(realm); if (!hasRealmAdminManagementClient(rep)) setupRealmAdminManagement(realm); if (!hasAccountManagementClient(rep)) setupAccountManagement(realm); - if (!hasImpersonationServiceClient(rep)) setupImpersonationService(realm); + + boolean postponeImpersonationSetup = false; + if (!hasImpersonationServiceClient(rep)) { + if (hasRealmAdminManagementClient(rep)) { + postponeImpersonationSetup = true; + } else { + setupImpersonationService(realm); + } + } if (!hasBrokerClient(rep)) setupBrokerService(realm); if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm); RepresentationToModel.importRealm(session, rep, realm); + // Could happen when migrating from older version and I have exported JSON file, which contains "realm-management" client but not "impersonation" client + // I need to postpone impersonation because it needs "realm-management" client and it's roles set + if (postponeImpersonationSetup) { + setupImpersonationService(realm); + } + setupAuthenticationFlows(realm); setupRequiredActions(realm); @@ -287,50 +303,49 @@ public class RealmManager { } private boolean hasRealmAdminManagementClient(RealmRepresentation rep) { - if (rep.getClients() == null) return false; - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getClientId().equals(getRealmAdminClientId(rep))) { - return true; - } - } - return false; + String realmAdminClientId = getRealmAdminClientId(rep); + return hasClient(rep, realmAdminClientId); } private boolean hasAccountManagementClient(RealmRepresentation rep) { - if (rep.getClients() == null) return false; - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) { - return true; - } - } - return false; + return hasClient(rep, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); } private boolean hasImpersonationServiceClient(RealmRepresentation rep) { - if (rep.getClients() == null) return false; - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getClientId().equals(Constants.IMPERSONATION_SERVICE_CLIENT_ID)) { - return true; - } - } - return false; + return hasClient(rep, Constants.IMPERSONATION_SERVICE_CLIENT_ID); } private boolean hasBrokerClient(RealmRepresentation rep) { - if (rep.getClients() == null) return false; - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getClientId().equals(Constants.BROKER_SERVICE_CLIENT_ID)) { - return true; - } - } - return false; + return hasClient(rep, Constants.BROKER_SERVICE_CLIENT_ID); } private boolean hasAdminConsoleClient(RealmRepresentation rep) { - if (rep.getClients() == null) return false; - for (ClientRepresentation clientRep : rep.getClients()) { - if (clientRep.getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) { - return true; + return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID); + } + + private boolean hasClient(RealmRepresentation rep, String clientId) { + if (rep.getClients() != null) { + for (ClientRepresentation clientRep : rep.getClients()) { + if (clientRep.getClientId().equals(clientId)) { + return true; + } } } + + // TODO: Just for compatibility with old versions. Should be removed later... + if (rep.getApplications() != null) { + for (ApplicationRepresentation clientRep : rep.getApplications()) { + if (clientRep.getName().equals(clientId)) { + return true; + } + } + } + if (rep.getOauthClients() != null) { + for (OAuthClientRepresentation clientRep : rep.getOauthClients()) { + if (clientRep.getName().equals(clientId)) { + return true; + } + } + } + return false; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index 2d14b4bc30..5b5a611a4e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -168,12 +168,8 @@ public class RealmsAdminResource { for (InputPart inputPart : inputParts) { // inputPart.getBody doesn't work as content-type is wrong, and inputPart.setMediaType is not supported on AS7 (RestEasy 2.3.2.Final) rep = JsonSerialization.readValue(inputPart.getBodyAsString(), RealmRepresentation.class); - RealmModel realm; - try { - realm = realmManager.importRealm(rep); - } catch (ModelDuplicateException e) { - return ErrorResponse.exists("Realm " + rep.getRealm() + " already exists"); - } + + RealmModel realm = realmManager.importRealm(rep); grantPermissionsToRealmCreator(realm); From dc366c53a7d749285c72da91335658788f0d326f Mon Sep 17 00:00:00 2001 From: mposolda Date: Sat, 11 Jul 2015 18:56:42 +0200 Subject: [PATCH 2/2] KEYCLOAK-1175 Import at startup can fail if master realm not present --- .../keycloak/exportimport/ImportProvider.java | 6 +++++ .../exportimport/util/ImportUtils.java | 17 ++++++++++-- .../exportimport/dir/DirImportProvider.java | 19 +++++++++++--- .../singlefile/SingleFileImportProvider.java | 26 +++++++++++++++++-- .../exportimport/zip/ZipImportProvider.java | 19 +++++++++++--- .../exportimport/ExportImportManager.java | 16 +++++++++++- .../resources/KeycloakApplication.java | 2 +- 7 files changed, 91 insertions(+), 14 deletions(-) rename {export-import/export-import-api => services}/src/main/java/org/keycloak/exportimport/ExportImportManager.java (77%) diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java index 56bc812464..1aa0b5e0b8 100755 --- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java @@ -13,4 +13,10 @@ public interface ImportProvider extends Provider { void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException; void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException; + + /** + * @return true if master realm was previously exported and is available in the data to be imported + * @throws IOException + */ + boolean isMasterRealmExported() throws IOException; } diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java index fbd909db83..494082077a 100755 --- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java @@ -21,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.keycloak.exportimport.ExportImportConfig; @@ -143,6 +144,15 @@ public class ImportUtils { * @throws IOException */ public static void importFromStream(KeycloakSession session, ObjectMapper mapper, InputStream is, Strategy strategy) throws IOException { + Map realmReps = getRealmsFromStream(mapper, is); + for (RealmRepresentation realmRep : realmReps.values()) { + importRealm(session, realmRep, strategy); + } + } + + public static Map getRealmsFromStream(ObjectMapper mapper, InputStream is) throws IOException { + Map result = new HashMap(); + JsonFactory factory = mapper.getJsonFactory(); JsonParser parser = factory.createJsonParser(is); try { @@ -166,18 +176,21 @@ public class ImportUtils { } for (RealmRepresentation realmRep : realmReps) { - importRealm(session, realmRep, strategy); + result.put(realmRep.getId(), realmRep); } } else if (parser.getCurrentToken() == JsonToken.START_OBJECT) { // Case with single realm in stream RealmRepresentation realmRep = parser.readValueAs(RealmRepresentation.class); - importRealm(session, realmRep, strategy); + result.put(realmRep.getId(), realmRep); } } finally { parser.close(); } + + return result; } + // Assuming that it's invoked inside transaction public static void importUsersFromStream(KeycloakSession session, String realmName, ObjectMapper mapper, InputStream is) throws IOException { RealmProvider model = session.realms(); diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java index 15d6dc8957..9a77264215 100755 --- a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java +++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java @@ -49,6 +49,20 @@ public class DirImportProvider implements ImportProvider { @Override public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException { + List realmNames = getRealmsToImport(); + + for (String realmName : realmNames) { + importRealm(factory, realmName, strategy); + } + } + + @Override + public boolean isMasterRealmExported() throws IOException { + List realmNames = getRealmsToImport(); + return realmNames.contains(Config.getAdminRealm()); + } + + private List getRealmsToImport() throws IOException { File[] realmFiles = this.rootDirectory.listFiles(new FilenameFilter() { @Override @@ -70,10 +84,7 @@ public class DirImportProvider implements ImportProvider { realmNames.add(realmName); } } - - for (String realmName : realmNames) { - importRealm(factory, realmName, strategy); - } + return realmNames; } @Override diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java index 8e993d0b5f..62ae93b7a3 100755 --- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java +++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java @@ -1,6 +1,7 @@ package org.keycloak.exportimport.singlefile; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.exportimport.ImportProvider; import org.keycloak.exportimport.Strategy; import org.keycloak.exportimport.util.ExportImportSessionTask; @@ -8,11 +9,13 @@ import org.keycloak.exportimport.util.ImportUtils; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.util.JsonSerialization; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Map; /** * @author Marek Posolda @@ -23,6 +26,9 @@ public class SingleFileImportProvider implements ImportProvider { private File file; + // Allows to cache representation per provider to avoid parsing them twice + protected Map realmReps; + public SingleFileImportProvider(File file) { this.file = file; } @@ -30,17 +36,33 @@ public class SingleFileImportProvider implements ImportProvider { @Override public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException { logger.infof("Full importing from file %s", this.file.getAbsolutePath()); + checkRealmReps(); + KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() { @Override protected void runExportImportTask(KeycloakSession session) throws IOException { - FileInputStream is = new FileInputStream(file); - ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy); + for (RealmRepresentation realmRep : realmReps.values()) { + ImportUtils.importRealm(session, realmRep, strategy); + } } }); } + @Override + public boolean isMasterRealmExported() throws IOException { + checkRealmReps(); + return (realmReps.containsKey(Config.getAdminRealm())); + } + + protected void checkRealmReps() throws IOException { + if (realmReps == null) { + FileInputStream is = new FileInputStream(file); + realmReps = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, is); + } + } + @Override public void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException { // TODO: import just that single realm in case that file contains many realms? diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java index fb4013956f..c9e617853a 100755 --- a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java +++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java @@ -51,6 +51,20 @@ public class ZipImportProvider implements ImportProvider { @Override public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException { + List realmNames = getRealmsToImport(); + + for (String realmName : realmNames) { + importRealm(factory, realmName, strategy); + } + } + + @Override + public boolean isMasterRealmExported() throws IOException { + List realmNames = getRealmsToImport(); + return realmNames.contains(Config.getAdminRealm()); + } + + private List getRealmsToImport() throws IOException { List realmNames = new ArrayList(); for (ExtZipEntry entry : this.decrypter.getEntryList()) { String entryName = entry.getName(); @@ -66,10 +80,7 @@ public class ZipImportProvider implements ImportProvider { } } } - - for (String realmName : realmNames) { - importRealm(factory, realmName, strategy); - } + return realmNames; } @Override diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java similarity index 77% rename from export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java rename to services/src/main/java/org/keycloak/exportimport/ExportImportManager.java index caedb36d39..c42077a5dd 100644 --- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java +++ b/services/src/main/java/org/keycloak/exportimport/ExportImportManager.java @@ -2,8 +2,10 @@ package org.keycloak.exportimport; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.managers.ApplianceBootstrap; /** * @author Marek Posolda @@ -12,7 +14,7 @@ public class ExportImportManager { private static final Logger logger = Logger.getLogger(ExportImportManager.class); - public void checkExportImport(KeycloakSessionFactory sessionFactory) { + public void checkExportImport(KeycloakSessionFactory sessionFactory, String contextPath) { String exportImportAction = ExportImportConfig.getAction(); String realmName = ExportImportConfig.getRealmName(); @@ -46,9 +48,21 @@ public class ExportImportManager { Strategy strategy = ExportImportConfig.getStrategy(); if (realmName == null) { logger.infof("Full model import requested. Strategy: %s", strategy.toString()); + + // Check if master realm was exported. If it's not, then it needs to be created before other realms are imported + if (!importProvider.isMasterRealmExported()) { + new ApplianceBootstrap().bootstrap(sessionFactory, contextPath); + } + importProvider.importModel(sessionFactory, strategy); } else { logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString()); + + if (!realmName.equals(Config.getAdminRealm())) { + // Check if master realm exists. If it's not, then it needs to be created before other realm is imported + new ApplianceBootstrap().bootstrap(sessionFactory, contextPath); + } + importProvider.importRealm(sessionFactory, realmName, strategy); } logger.info("Import finished successfully"); diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 0601882213..875511e502 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -83,7 +83,7 @@ public class KeycloakApplication extends Application { classes.add(JsResource.class); classes.add(WelcomeResource.class); - new ExportImportManager().checkExportImport(this.sessionFactory); + new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath()); setupDefaultRealm(context.getContextPath());