diff --git a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java index b0c37ac22a..99237ee23f 100755 --- a/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/CredentialRepresentation.java @@ -11,9 +11,16 @@ public class CredentialRepresentation { public static final String CLIENT_CERT = "cert"; protected String type; - protected String value; protected String device; + // Plain-text value of credential (used for example during import from manually created JSON file) + protected String value; + + // Value stored in DB (used for example during export/import) + protected String hashedSaltedValue; + protected String salt; + protected Integer hashIterations; + public String getType() { return type; } @@ -37,4 +44,28 @@ public class CredentialRepresentation { public void setDevice(String device) { this.device = device; } + + public String getHashedSaltedValue() { + return hashedSaltedValue; + } + + public void setHashedSaltedValue(String hashedSaltedValue) { + this.hashedSaltedValue = hashedSaltedValue; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public Integer getHashIterations() { + return hashIterations; + } + + public void setHashIterations(Integer hashIterations) { + this.hashIterations = hashIterations; + } } diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java index 8751a38ca2..7511de6f20 100755 --- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java @@ -9,7 +9,6 @@ import java.util.List; public class OAuthClientRepresentation { protected String id; protected String name; - protected String baseUrl; protected List redirectUris; protected List webOrigins; protected Boolean enabled; @@ -44,14 +43,6 @@ public class OAuthClientRepresentation { this.enabled = enabled; } - public String getBaseUrl() { - return baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - public List getRedirectUris() { return redirectUris; } 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 4b5380d8fc..6d85877a83 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -423,4 +423,28 @@ public class RealmRepresentation { public void setFailureFactor(Integer failureFactor) { this.failureFactor = failureFactor; } + + public boolean isAuditEnabled() { + return auditEnabled; + } + + public void setAuditEnabled(boolean auditEnabled) { + this.auditEnabled = auditEnabled; + } + + public long getAuditExpiration() { + return auditExpiration; + } + + public void setAuditExpiration(long auditExpiration) { + this.auditExpiration = auditExpiration; + } + + public List getAuditListeners() { + return auditListeners; + } + + public void setAuditListeners(List auditListeners) { + this.auditListeners = auditListeners; + } } diff --git a/export-import/export-import-api/pom.xml b/export-import/export-import-api/pom.xml index 19db6e5e9a..1b4dc0a4a2 100755 --- a/export-import/export-import-api/pom.xml +++ b/export-import/export-import-api/pom.xml @@ -26,6 +26,32 @@ ${project.version} provided + + org.keycloak + keycloak-invalidation-cache-model + ${project.version} + provided + + + org.jboss.logging + jboss-logging + provided + + + org.codehaus.jackson + jackson-core-asl + provided + + + org.codehaus.jackson + jackson-mapper-asl + provided + + + net.iharder + base64 + provided + diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportConfig.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportConfig.java new file mode 100644 index 0000000000..8014c21f4e --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportConfig.java @@ -0,0 +1,107 @@ +package org.keycloak.exportimport; + +/** + * @author Stian Thorgersen + */ +public class ExportImportConfig { + + public static final String ACTION = "keycloak.migration.action"; + public static final String ACTION_EXPORT = "export"; + public static final String ACTION_IMPORT = "import"; + + public static final String PROVIDER = "keycloak.migration.provider"; + public static final String PROVIDER_DEFAULT = "zip"; + + // Name of the realm to export. If null, then full export will be triggered + public static final String REALM_NAME = "keycloak.migration.realmName"; + + // used for "dir" provider + public static final String DIR = "keycloak.migration.dir"; + // used for "zip" provider + public static final String ZIP_FILE = "keycloak.migration.zipFile"; + public static final String ZIP_PASSWORD = "keycloak.migration.zipPassword"; + // used for "singleFile" provider + public static final String FILE = "keycloak.migration.file"; + + // Number of users per file used in "dir" and "zip" providers. -1 means adding users to same file with realm. 0 means adding to separate file with unlimited page number + public static final String USERS_PER_FILE = "keycloak.migration.usersPerFile"; + public static final Integer DEFAULT_USERS_PER_FILE = 5000; + + // Strategy used during import data + public static final String STRATEGY = "keycloak.migration.strategy"; + public static final Strategy DEFAULT_STRATEGY = Strategy.OVERWRITE_EXISTING; + + public static String getAction() { + return System.getProperty(ACTION); + } + + public static void setAction(String exportImportAction) { + System.setProperty(ACTION, exportImportAction); + } + + public static String getProvider() { + return System.getProperty(PROVIDER, PROVIDER_DEFAULT); + } + + public static void setProvider(String exportImportProvider) { + System.setProperty(PROVIDER, exportImportProvider); + } + + public static String getRealmName() { + return System.getProperty(REALM_NAME); + } + + public static void setRealmName(String realmName) { + if (realmName != null) { + System.setProperty(REALM_NAME, realmName); + } else { + System.getProperties().remove(REALM_NAME); + } + } + + public static String getDir() { + return System.getProperty(DIR); + } + + public static String setDir(String dir) { + return System.setProperty(DIR, dir); + } + + public static String getZipFile() { + return System.getProperty(ZIP_FILE); + } + + public static void setZipFile(String exportImportZipFile) { + System.setProperty(ZIP_FILE, exportImportZipFile); + } + + public static String getZipPassword() { + return System.getProperty(ZIP_PASSWORD); + } + + public static void setZipPassword(String exportImportZipPassword) { + System.setProperty(ZIP_PASSWORD, exportImportZipPassword); + } + + public static String getFile() { + return System.getProperty(FILE); + } + + public static void setFile(String file) { + System.setProperty(FILE, file); + } + + public static Integer getUsersPerFile() { + String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE)); + return Integer.parseInt(usersPerFile.trim()); + } + + public static void setUsersPerFile(Integer usersPerFile) { + System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile)); + } + + public static Strategy getStrategy() { + String strategy = System.getProperty(STRATEGY, DEFAULT_STRATEGY.toString()); + return Enum.valueOf(Strategy.class, strategy); + } +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java new file mode 100644 index 0000000000..a15a1ce38c --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportManager.java @@ -0,0 +1,63 @@ +package org.keycloak.exportimport; + + +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Marek Posolda + */ +public class ExportImportManager { + + private static final Logger logger = Logger.getLogger(ExportImportManager.class); + + public void checkExportImport(KeycloakSessionFactory sessionFactory) { + String exportImportAction = ExportImportConfig.getAction(); + String realmName = ExportImportConfig.getRealmName(); + + boolean export = false; + boolean importt = false; + if (ExportImportConfig.ACTION_EXPORT.equals(exportImportAction)) { + export = true; + } else if (ExportImportConfig.ACTION_IMPORT.equals(exportImportAction)) { + importt = true; + } + + if (export || importt) { + String exportImportProviderId = ExportImportConfig.getProvider(); + logger.debug("Will use provider: " + exportImportProviderId); + KeycloakSession session = sessionFactory.create(); + + try { + if (export) { + ExportProvider exportProvider = session.getProvider(ExportProvider.class, exportImportProviderId); + + if (realmName == null) { + logger.info("Full model export requested"); + exportProvider.exportModel(session); + } else { + logger.infof("Export of realm '%s' requested", realmName); + exportProvider.exportRealm(session, realmName); + } + logger.info("Export finished successfully"); + } else { + ImportProvider importProvider = session.getProvider(ImportProvider.class, exportImportProviderId); + Strategy strategy = ExportImportConfig.getStrategy(); + if (realmName == null) { + logger.infof("Full model import requested. Strategy: %s", strategy.toString()); + importProvider.importModel(session, strategy); + } else { + logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString()); + importProvider.importRealm(session, realmName, strategy); + } + logger.info("Import finished successfully"); + } + } catch (Throwable ioe) { + logger.error("Error during export/import", ioe); + } finally { + session.close(); + } + } + } +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java deleted file mode 100644 index d53f1fa8bc..0000000000 --- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.exportimport; - -import org.keycloak.models.KeycloakSessionFactory; - -/** - * @author Marek Posolda - */ -public interface ExportImportProvider { - - void checkExportImport(KeycloakSessionFactory sessionFactory); - -} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProvider.java new file mode 100644 index 0000000000..4171b89664 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProvider.java @@ -0,0 +1,18 @@ +package org.keycloak.exportimport; + +import java.io.IOException; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.Provider; + +/** + * @author Marek Posolda + */ +public interface ExportProvider extends Provider { + + void exportModel(KeycloakSession session) throws IOException; + + void exportRealm(KeycloakSession session, String realmName) throws IOException; + +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java new file mode 100644 index 0000000000..735e1849e1 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportProviderFactory.java @@ -0,0 +1,9 @@ +package org.keycloak.exportimport; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Marek Posolda + */ +public interface ExportProviderFactory extends ProviderFactory { +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportSpi.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportSpi.java new file mode 100644 index 0000000000..6ec29606d9 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportSpi.java @@ -0,0 +1,26 @@ +package org.keycloak.exportimport; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Marek Posolda + */ +public class ExportSpi implements Spi { + + @Override + public String getName() { + return "export"; + } + + @Override + public Class getProviderClass() { + return ExportProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return ExportProviderFactory.class; + } +} 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 new file mode 100644 index 0000000000..5283977db1 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProvider.java @@ -0,0 +1,16 @@ +package org.keycloak.exportimport; + +import java.io.IOException; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.Provider; + +/** + * @author Marek Posolda + */ +public interface ImportProvider extends Provider { + + void importModel(KeycloakSession session, Strategy strategy) throws IOException; + + void importRealm(KeycloakSession session, String realmName, Strategy strategy) throws IOException; +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java new file mode 100644 index 0000000000..ecb1bd37fb --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportProviderFactory.java @@ -0,0 +1,9 @@ +package org.keycloak.exportimport; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Marek Posolda + */ +public interface ImportProviderFactory extends ProviderFactory { +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportSpi.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportSpi.java new file mode 100644 index 0000000000..b562f3fcbe --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ImportSpi.java @@ -0,0 +1,26 @@ +package org.keycloak.exportimport; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Marek Posolda + */ +public class ImportSpi implements Spi { + + @Override + public String getName() { + return "import"; + } + + @Override + public Class getProviderClass() { + return ImportProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return ImportProviderFactory.class; + } +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/Strategy.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/Strategy.java new file mode 100644 index 0000000000..6bbd70d923 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/Strategy.java @@ -0,0 +1,10 @@ +package org.keycloak.exportimport; + +/** + * @author Marek Posolda + */ +public enum Strategy { + + IGNORE_EXISTING, // Ignore existing user entries + OVERWRITE_EXISTING // Overwrite existing user entries +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportJob.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportJob.java new file mode 100644 index 0000000000..12922b4a41 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportJob.java @@ -0,0 +1,13 @@ +package org.keycloak.exportimport.util; + +import java.io.IOException; + +/** + * Task to be executed inside transaction + * + * @author Marek Posolda + */ +public interface ExportImportJob { + + public void run() throws IOException; +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportUtils.java new file mode 100644 index 0000000000..2c0ab44599 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportUtils.java @@ -0,0 +1,45 @@ +package org.keycloak.exportimport.util; + +import java.io.IOException; + +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakTransaction; +import org.keycloak.models.RealmModel; + +/** + * @author Marek Posolda + */ +public class ExportImportUtils { + + /** + * Wrap given runnable job into KeycloakTransaction. Assumption is that session already exists and it doesn't need to be closed when finished + * + * @param session + * @param job + */ + public static void runJobInTransaction(KeycloakSession session, ExportImportJob job) throws IOException { + KeycloakTransaction tx = session.getTransaction(); + try { + tx.begin(); + + job.run(); + + if (tx.isActive()) { + if (tx.getRollbackOnly()) { + tx.rollback(); + } else { + tx.commit(); + } + } + } finally { + if (tx.isActive()) { + tx.rollback(); + } + } + } + + public static String getMasterRealmAdminApplicationName(RealmModel realm) { + return realm.getName() + "-realm"; + } +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java new file mode 100644 index 0000000000..4c96812073 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -0,0 +1,365 @@ +package org.keycloak.exportimport.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.iharder.Base64; +import org.codehaus.jackson.JsonEncoding; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.keycloak.exportimport.Strategy; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.AuthenticationLinkModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.SocialLinkModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.ApplicationRepresentation; +import org.keycloak.representations.idm.AuthenticationLinkRepresentation; +import org.keycloak.representations.idm.ClaimRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.OAuthClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.RolesRepresentation; +import org.keycloak.representations.idm.ScopeMappingRepresentation; +import org.keycloak.representations.idm.SocialLinkRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +/** + * @author Marek Posolda + */ +public class ExportUtils { + + public static RealmRepresentation exportRealm(RealmModel realm, boolean includeUsers) { + RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm); + + // Audit + rep.setAuditEnabled(realm.isAuditEnabled()); + if (realm.getAuditExpiration() != 0) { + rep.setAuditExpiration(realm.getAuditExpiration()); + } + + if (realm.getAuditListeners() != null) { + rep.setAuditListeners(new LinkedList(realm.getAuditListeners())); + } + + // Applications + List applications = realm.getApplications(); + List appReps = new ArrayList(); + for (ApplicationModel app : applications) { + ApplicationRepresentation appRep = exportApplication(app); + appReps.add(appRep); + } + rep.setApplications(appReps); + + // OAuth clients + List oauthClients = realm.getOAuthClients(); + List oauthClientReps = new ArrayList(); + for (OAuthClientModel oauthClient : oauthClients) { + OAuthClientRepresentation clientRep = ModelToRepresentation.toRepresentation(oauthClient); + oauthClientReps.add(clientRep); + } + rep.setOauthClients(oauthClientReps); + + // Roles + List realmRoleReps = null; + Map> appRolesReps = new HashMap>(); + + Set realmRoles = realm.getRoles(); + if (realmRoles != null && realmRoles.size() > 0) { + realmRoleReps = exportRoles(realmRoles); + } + for (ApplicationModel app : applications) { + Set currentAppRoles = app.getRoles(); + List currentAppRoleReps = exportRoles(currentAppRoles); + appRolesReps.put(app.getName(), currentAppRoleReps); + } + + RolesRepresentation rolesRep = new RolesRepresentation(); + if (realmRoleReps != null) { + rolesRep.setRealm(realmRoleReps); + } + if (appRolesReps.size() > 0) { + rolesRep.setApplication(appRolesReps); + } + rep.setRoles(rolesRep); + + // Scopes + List allClients = new ArrayList(applications); + allClients.addAll(realm.getOAuthClients()); + Map> appScopeReps = new HashMap>(); + + for (ClientModel client : allClients) { + Set clientScopes = client.getScopeMappings(); + ScopeMappingRepresentation scopeMappingRep = null; + for (RoleModel scope : clientScopes) { + if (scope.getContainer() instanceof RealmModel) { + if (scopeMappingRep == null) { + scopeMappingRep = rep.scopeMapping(client.getClientId()); + } + scopeMappingRep.role(scope.getName()); + } else { + ApplicationModel app = (ApplicationModel)scope.getContainer(); + String appName = app.getName(); + List currentAppScopes = appScopeReps.get(appName); + if (currentAppScopes == null) { + currentAppScopes = new ArrayList(); + appScopeReps.put(appName, currentAppScopes); + } + + ScopeMappingRepresentation currentClientScope = null; + for (ScopeMappingRepresentation scopeMapping : currentAppScopes) { + if (scopeMapping.getClient().equals(client.getClientId())) { + currentClientScope = scopeMapping; + break; + } + } + if (currentClientScope == null) { + currentClientScope = new ScopeMappingRepresentation(); + currentClientScope.setClient(client.getClientId()); + currentAppScopes.add(currentClientScope); + } + currentClientScope.role(scope.getName()); + } + } + } + + if (appScopeReps.size() > 0) { + rep.setApplicationScopeMappings(appScopeReps); + } + + // Finally users if needed + if (includeUsers) { + List allUsers = realm.getUsers(); + List users = new ArrayList(); + for (UserModel user : allUsers) { + UserRepresentation userRep = exportUser(realm, user); + users.add(userRep); + } + + if (users.size() > 0) { + rep.setUsers(users); + } + } + + return rep; + } + + /** + * Full export of application including claims and secret + * @param app + * @return full ApplicationRepresentation + */ + public static ApplicationRepresentation exportApplication(ApplicationModel app) { + ApplicationRepresentation appRep = ModelToRepresentation.toRepresentation(app); + + appRep.setSecret(app.getSecret()); + ClaimRepresentation claimRep = ModelToRepresentation.toRepresentation((ClientModel)app); + appRep.setClaims(claimRep); + return appRep; + } + + public static List exportRoles(Collection roles) { + List roleReps = new ArrayList(); + + for (RoleModel role : roles) { + RoleRepresentation roleRep = exportRole(role); + roleReps.add(roleRep); + } + return roleReps; + } + + 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 + * @return RoleRepresentation with all stuff filled (including composite roles) + */ + public static RoleRepresentation exportRole(RoleModel role) { + RoleRepresentation roleRep = ModelToRepresentation.toRepresentation(role); + + Set composites = role.getComposites(); + if (composites != null && composites.size() > 0) { + Set compositeRealmRoles = null; + Map> compositeAppRoles = null; + + for (RoleModel composite : composites) { + RoleContainerModel crContainer = composite.getContainer(); + if (crContainer instanceof RealmModel) { + + if (compositeRealmRoles == null) { + compositeRealmRoles = new HashSet(); + } + compositeRealmRoles.add(composite.getName()); + } else { + if (compositeAppRoles == null) { + compositeAppRoles = new HashMap>(); + } + + ApplicationModel app = (ApplicationModel)crContainer; + String appName = app.getName(); + List currentAppComposites = compositeAppRoles.get(appName); + if (currentAppComposites == null) { + currentAppComposites = new ArrayList(); + compositeAppRoles.put(appName, currentAppComposites); + } + currentAppComposites.add(composite.getName()); + } + } + + RoleRepresentation.Composites compRep = new RoleRepresentation.Composites(); + if (compositeRealmRoles != null) { + compRep.setRealm(compositeRealmRoles); + } + if (compositeAppRoles != null) { + compRep.setApplication(compositeAppRoles); + } + + roleRep.setComposites(compRep); + } + + return roleRep; + } + + /** + * Full export of user (including role mappings and credentials) + * + * @param user + * @return fully exported user representation + */ + public static UserRepresentation exportUser(RealmModel realm, UserModel user) { + UserRepresentation userRep = ModelToRepresentation.toRepresentation(user); + + // AuthenticationLink + AuthenticationLinkModel authLink = user.getAuthenticationLink(); + if (authLink != null) { + AuthenticationLinkRepresentation authLinkRepresentation = exportAuthLink(authLink); + userRep.setAuthenticationLink(authLinkRepresentation); + } + + // Social links + Set socialLinks = realm.getSocialLinks(user); + List socialLinkReps = new ArrayList(); + for (SocialLinkModel socialLink : socialLinks) { + SocialLinkRepresentation socialLinkRep = exportSocialLink(socialLink); + socialLinkReps.add(socialLinkRep); + } + if (socialLinkReps.size() > 0) { + userRep.setSocialLinks(socialLinkReps); + } + + // Role mappings + Set roles = user.getRoleMappings(); + List realmRoleNames = new ArrayList(); + Map> appRoleNames = new HashMap>(); + for (RoleModel role : roles) { + if (role.getContainer() instanceof RealmModel) { + realmRoleNames.add(role.getName()); + } else { + ApplicationModel app = (ApplicationModel)role.getContainer(); + String appName = app.getName(); + List currentAppRoles = appRoleNames.get(appName); + if (currentAppRoles == null) { + currentAppRoles = new ArrayList(); + appRoleNames.put(appName, currentAppRoles); + } + + currentAppRoles.add(role.getName()); + } + } + + if (realmRoleNames.size() > 0) { + userRep.setRealmRoles(realmRoleNames); + } + if (appRoleNames.size() > 0) { + userRep.setApplicationRoles(appRoleNames); + } + + // Credentials + List creds = user.getCredentialsDirectly(); + List credReps = new ArrayList(); + for (UserCredentialValueModel cred : creds) { + CredentialRepresentation credRep = exportCredential(cred); + credReps.add(credRep); + } + userRep.setCredentials(credReps); + + return userRep; + } + + public static AuthenticationLinkRepresentation exportAuthLink(AuthenticationLinkModel authLinkModel) { + AuthenticationLinkRepresentation authLinkRep = new AuthenticationLinkRepresentation(); + authLinkRep.setAuthProvider(authLinkModel.getAuthProvider()); + authLinkRep.setAuthUserId(authLinkModel.getAuthUserId()); + return authLinkRep; + } + + public static SocialLinkRepresentation exportSocialLink(SocialLinkModel socialLink) { + SocialLinkRepresentation socialLinkRep = new SocialLinkRepresentation(); + socialLinkRep.setSocialProvider(socialLink.getSocialProvider()); + socialLinkRep.setSocialUserId(socialLink.getSocialUserId()); + socialLinkRep.setSocialUsername(socialLink.getSocialUsername()); + return socialLinkRep; + } + + public static CredentialRepresentation exportCredential(UserCredentialValueModel userCred) { + CredentialRepresentation credRep = new CredentialRepresentation(); + credRep.setType(userCred.getType()); + credRep.setDevice(userCred.getDevice()); + credRep.setHashedSaltedValue(userCred.getValue()); + credRep.setSalt(Base64.encodeBytes(userCred.getSalt())); + credRep.setHashIterations(userCred.getHashIterations()); + return credRep; + } + + // Streaming API + + public static void exportUsersToStream(RealmModel realm, List usersToExport, ObjectMapper mapper, OutputStream os) throws IOException { + JsonFactory factory = mapper.getJsonFactory(); + JsonGenerator generator = factory.createJsonGenerator(os, JsonEncoding.UTF8); + try { + if (mapper.isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) { + generator.useDefaultPrettyPrinter(); + } + generator.writeStartObject(); + generator.writeStringField("realm", realm.getName()); + // generator.writeStringField("strategy", strategy.toString()); + generator.writeFieldName("users"); + generator.writeStartArray(); + + for (UserModel user : usersToExport) { + UserRepresentation userRep = ExportUtils.exportUser(realm, user); + generator.writeObject(userRep); + } + + generator.writeEndArray(); + generator.writeEndObject(); + } finally { + generator.close(); + } + } +} 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 new file mode 100644 index 0000000000..274798751b --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java @@ -0,0 +1,170 @@ +package org.keycloak.exportimport.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.codehaus.jackson.JsonEncoding; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.io.SerializedString; +import org.codehaus.jackson.map.ObjectMapper; +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.exportimport.Strategy; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelProvider; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +/** + * @author Marek Posolda + */ +public class ImportUtils { + + private static final Logger logger = Logger.getLogger(ImportUtils.class); + + /** + * Fully import realm from representation, save it to model and return model of newly created realm + * + * @param session + * @param rep + * @param strategy specifies whether to overwrite or ignore existing realm or user entries + * @return newly imported realm (or existing realm if ignoreExisting is true and realm of this name already exists) + */ + public static RealmModel importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy) { + String realmName = rep.getRealm(); + ModelProvider model = session.getModel(); + RealmModel realm = model.getRealmByName(realmName); + + if (realm != null) { + if (strategy == Strategy.IGNORE_EXISTING) { + logger.infof("Realm '%s' already exists. Import skipped", realmName); + return realm; + } else { + logger.infof("Realm '%s' already exists. Removing it before import", realmName); + model.removeRealm(realm.getId()); + } + } + + realm = rep.getId() != null ? model.createRealm(rep.getId(), realmName) : model.createRealm(realmName); + + RepresentationToModel.importRealm(rep, realm); + + refreshMasterAdminApps(model, realm); + + logger.infof("Realm '%s' imported", realmName); + return realm; + } + + private static void refreshMasterAdminApps(ModelProvider model, RealmModel realm) { + String adminRealmId = Config.getAdminRealm(); + if (adminRealmId.equals(realm.getId())) { + // We just imported master realm. All 'masterAdminApps' need to be refreshed + RealmModel adminRealm = realm; + for (RealmModel currentRealm : model.getRealms()) { + ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(currentRealm)); + currentRealm.setMasterAdminApp(masterApp); + } + } else { + // Need to refresh masterApp for current realm + RealmModel adminRealm = model.getRealm(adminRealmId); + ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(realm)); + realm.setMasterAdminApp(masterApp); + } + } + + + /** + * Fully import realm (or more realms from particular stream) + * + * @param session + * @param mapper + * @param is + * @param strategy + * @throws IOException + */ + public static void importFromStream(KeycloakSession session, ObjectMapper mapper, InputStream is, Strategy strategy) throws IOException { + JsonFactory factory = mapper.getJsonFactory(); + JsonParser parser = factory.createJsonParser(is); + try { + parser.nextToken(); + + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + // Case with more realms in stream + parser.nextToken(); + while (parser.getCurrentToken() == JsonToken.START_OBJECT) { + RealmRepresentation realmRep = parser.readValueAs(RealmRepresentation.class); + parser.nextToken(); + importRealm(session, realmRep, strategy); + } + } else if (parser.getCurrentToken() == JsonToken.START_OBJECT) { + // Case with single realm in stream + RealmRepresentation realmRep = parser.readValueAs(RealmRepresentation.class); + importRealm(session, realmRep, strategy); + } + } finally { + parser.close(); + } + } + + // Assuming that it's invoked inside transaction + public static void importUsersFromStream(KeycloakSession session, String realmName, ObjectMapper mapper, InputStream is) throws IOException { + ModelProvider model = session.getModel(); + JsonFactory factory = mapper.getJsonFactory(); + JsonParser parser = factory.createJsonParser(is); + try { + parser.nextToken(); + + while (parser.nextToken() == JsonToken.FIELD_NAME) { + if ("realm".equals(parser.getText())) { + parser.nextToken(); + String currRealmName = parser.getText(); + if (!currRealmName.equals(realmName)) { + throw new IllegalStateException("Trying to import users into invalid realm. Realm name: " + realmName + ", Expected realm name: " + realmName); + } + } else if ("users".equals(parser.getText())) { + parser.nextToken(); + + if (parser.getCurrentToken() == JsonToken.START_ARRAY) { + parser.nextToken(); + } + + // TODO: support for more transactions per single users file (if needed) + List userReps = new ArrayList(); + while (parser.getCurrentToken() == JsonToken.START_OBJECT) { + UserRepresentation user = parser.readValueAs(UserRepresentation.class); + userReps.add(user); + parser.nextToken(); + } + + importUsers(model, realmName, userReps); + + if (parser.getCurrentToken() == JsonToken.END_ARRAY) { + parser.nextToken(); + } + } + } + } finally { + parser.close(); + } + } + + private static void importUsers(ModelProvider model, String realmName, List userReps) { + RealmModel realm = model.getRealmByName(realmName); + Map apps = realm.getApplicationNameMap(); + for (UserRepresentation user : userReps) { + RepresentationToModel.createUser(realm, user, apps); + } + } + +} diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java new file mode 100644 index 0000000000..145ba52ae1 --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java @@ -0,0 +1,115 @@ +package org.keycloak.exportimport.util; + +import java.io.IOException; +import java.util.List; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ExportProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; + +/** + * @author Marek Posolda + */ +public abstract class MultipleStepsExportProvider implements ExportProvider { + + protected final Logger logger = Logger.getLogger(getClass()); + + @Override + public void exportModel(final KeycloakSession session) throws IOException { + final RealmsHolder holder = new RealmsHolder(); + + // Import users into same file with realm + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() { + List realms = session.getModel().getRealms(); + holder.realms = realms; + } + + }); + + for (RealmModel realm : holder.realms) { + exportRealm(session, realm.getName()); + } + } + + @Override + public void exportRealm(final KeycloakSession session, final String realmName) throws IOException { + final int usersPerFile = ExportImportConfig.getUsersPerFile(); + final UsersHolder usersHolder = new UsersHolder(); + final boolean exportUsersIntoSameFile = usersPerFile < 0; + + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + RealmModel realm = session.getModel().getRealmByName(realmName); + RealmRepresentation rep = ExportUtils.exportRealm(realm, exportUsersIntoSameFile); + writeRealm(realmName + "-realm.json", rep); + logger.info("Realm '" + realmName + "' - data exported"); + + // Count total number of users + if (!exportUsersIntoSameFile) { + // TODO: getUsersCount method on model + usersHolder.totalCount = realm.getUsers().size(); + } + } + + }); + + if (!exportUsersIntoSameFile) { + + usersHolder.currentPageStart = 0; + + // usersPerFile==0 means exporting all users into single file (but separate to realm) + final int countPerPage = usersPerFile == 0 ? usersHolder.totalCount : usersPerFile; + + while (usersHolder.currentPageStart < usersHolder.totalCount) { + if (usersHolder.currentPageStart + countPerPage < usersHolder.totalCount) { + usersHolder.currentPageEnd = usersHolder.currentPageStart + countPerPage; + } else { + usersHolder.currentPageEnd = usersHolder.totalCount; + } + + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + RealmModel realm = session.getModel().getRealmByName(realmName); + // TODO: pagination + List users = realm.getUsers(); + usersHolder.users = users.subList(usersHolder.currentPageStart, usersHolder.currentPageEnd); + + writeUsers(realmName + "-users-" + (usersHolder.currentPageStart / countPerPage) + ".json", realm, usersHolder.users); + + logger.info("Users " + usersHolder.currentPageStart + "-" + usersHolder.currentPageEnd + " exported"); + } + + }); + + usersHolder.currentPageStart = usersHolder.currentPageEnd; + } + } + } + + protected abstract void writeRealm(String fileName, RealmRepresentation rep) throws IOException; + + protected abstract void writeUsers(String fileName, RealmModel realm, List users) throws IOException; + + public static class RealmsHolder { + List realms; + + } + + public static class UsersHolder { + List users; + int totalCount; + int currentPageStart; + int currentPageEnd; + } +} diff --git a/export-import/export-import-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/export-import/export-import-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi new file mode 100644 index 0000000000..93029438b2 --- /dev/null +++ b/export-import/export-import-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -0,0 +1,2 @@ +org.keycloak.exportimport.ExportSpi +org.keycloak.exportimport.ImportSpi \ No newline at end of file diff --git a/export-import/export-import-dir/pom.xml b/export-import/export-import-dir/pom.xml new file mode 100644 index 0000000000..4cd8dc1172 --- /dev/null +++ b/export-import/export-import-dir/pom.xml @@ -0,0 +1,66 @@ + + + + keycloak-export-import-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-export-import-dir + Keycloak Export Import To Directory + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + org.keycloak + keycloak-export-import-api + ${project.version} + provided + + + org.codehaus.jackson + jackson-core-asl + provided + + + org.codehaus.jackson + jackson-mapper-asl + provided + + + org.jboss.logging + jboss-logging + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProvider.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProvider.java new file mode 100644 index 0000000000..48dcf561dd --- /dev/null +++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProvider.java @@ -0,0 +1,73 @@ +package org.keycloak.exportimport.dir; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; + +import org.keycloak.exportimport.util.ExportUtils; +import org.keycloak.exportimport.util.MultipleStepsExportProvider; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class DirExportProvider extends MultipleStepsExportProvider { + + private final File rootDirectory; + + public DirExportProvider() { + // Determine system tmp directory + String tempDir = System.getProperty("java.io.tmpdir"); + + this.rootDirectory = new File(tempDir + "/keycloak-export"); + this.rootDirectory.mkdirs(); + + logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath()); + } + + public DirExportProvider(File rootDirectory) { + this.rootDirectory = rootDirectory; + this.rootDirectory.mkdirs(); + + logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath()); + } + + public static boolean recursiveDeleteDir(File dirPath) { + if (dirPath.exists()) { + File[] files = dirPath.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + recursiveDeleteDir(files[i]); + } else { + files[i].delete(); + } + } + } + if (dirPath.exists()) + return dirPath.delete(); + else + return true; + } + + @Override + public void writeRealm(String fileName, RealmRepresentation rep) throws IOException { + File file = new File(this.rootDirectory, fileName); + FileOutputStream stream = new FileOutputStream(file); + JsonSerialization.prettyMapper.writeValue(stream, rep); + } + + @Override + protected void writeUsers(String fileName, RealmModel realm, List users) throws IOException { + File file = new File(this.rootDirectory, fileName); + FileOutputStream os = new FileOutputStream(file); + ExportUtils.exportUsersToStream(realm, users, JsonSerialization.prettyMapper, os); + } + + @Override + public void close() { + } +} diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProviderFactory.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProviderFactory.java new file mode 100644 index 0000000000..74e7eb43c7 --- /dev/null +++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirExportProviderFactory.java @@ -0,0 +1,36 @@ +package org.keycloak.exportimport.dir; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ExportProvider; +import org.keycloak.exportimport.ExportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class DirExportProviderFactory implements ExportProviderFactory { + + public static final String PROVIDER_ID = "dir"; + + @Override + public ExportProvider create(KeycloakSession session) { + String dir = ExportImportConfig.getDir(); + return dir!=null ? new DirExportProvider(new File(dir)) : new DirExportProvider(); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} 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 new file mode 100644 index 0000000000..3f6fc901df --- /dev/null +++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java @@ -0,0 +1,109 @@ +package org.keycloak.exportimport.dir; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; + +import org.codehaus.jackson.map.ObjectMapper; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.Strategy; +import org.keycloak.exportimport.util.ExportImportJob; +import org.keycloak.exportimport.util.ExportImportUtils; +import org.keycloak.exportimport.util.ImportUtils; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class DirImportProvider implements ImportProvider { + + private static final Logger logger = Logger.getLogger(DirImportProvider.class); + + private final File rootDirectory; + + public DirImportProvider() { + // Determine system tmp directory + String tempDir = System.getProperty("java.io.tmpdir"); + + // Delete and recreate directory inside tmp + this.rootDirectory = new File(tempDir + "/keycloak-export"); + if (!this.rootDirectory .exists()) { + throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exists"); + } + + logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath()); + } + + public DirImportProvider(File rootDirectory) { + this.rootDirectory = rootDirectory; + + logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath()); + } + + @Override + public void importModel(KeycloakSession session, Strategy strategy) throws IOException { + File[] realmFiles = this.rootDirectory.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return (name.endsWith("-realm.json")); + } + }); + + for (File file : realmFiles) { + String fileName = file.getName(); + + // Parse "foo" from "foo-realm.json" + String realmName = fileName.substring(0, fileName.length() - 11); + importRealm(session, realmName, strategy); + } + } + + @Override + public void importRealm(final KeycloakSession session, final String realmName, final Strategy strategy) throws IOException { + File realmFile = new File(this.rootDirectory + File.separator + realmName + "-realm.json"); + File[] userFiles = this.rootDirectory.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return (name.startsWith(realmName)) && (name.endsWith(".json")) && (name.substring(realmName.length()).contains("-users-") ); + } + }); + + // Import realm first + FileInputStream is = new FileInputStream(realmFile); + final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class); + + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + ImportUtils.importRealm(session, realmRep, strategy); + } + + }); + + // Import users + for (File userFile : userFiles) { + final FileInputStream fis = new FileInputStream(userFile); + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis); + } + }); + } + } + + @Override + public void close() { + + } +} diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProviderFactory.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProviderFactory.java new file mode 100644 index 0000000000..3e39a77850 --- /dev/null +++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProviderFactory.java @@ -0,0 +1,34 @@ +package org.keycloak.exportimport.dir; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.ImportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class DirImportProviderFactory implements ImportProviderFactory { + + @Override + public ImportProvider create(KeycloakSession session) { + String dir = ExportImportConfig.getDir(); + return dir!=null ? new DirImportProvider(new File(dir)) : new DirImportProvider(); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return DirExportProviderFactory.PROVIDER_ID; + } +} diff --git a/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory b/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory new file mode 100644 index 0000000000..8a8fea4adb --- /dev/null +++ b/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.dir.DirExportProviderFactory \ No newline at end of file diff --git a/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory b/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory new file mode 100644 index 0000000000..ba9874ed89 --- /dev/null +++ b/export-import/export-import-dir/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.dir.DirImportProviderFactory \ No newline at end of file diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportConfig.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportConfig.java deleted file mode 100644 index f21d4bdb41..0000000000 --- a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportConfig.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.keycloak.exportimport; - -/** - * @author Stian Thorgersen - */ -public class ExportImportConfig { - - public static final String ACTION = "keycloak.migration.action"; - public static final String PROVIDER = "keycloak.migration.provider"; - public static final String PROVIDER_DEFAULT = "zip"; - - // used for "directory" provider - public static final String DIR = "keycloak.migration.dir"; - // used for "zip" provider - public static final String FILE = "keycloak.migration.zipFile"; - public static final String PASSWORD = "keycloak.migration.zipPassword"; - - public static String getAction() { - return System.getProperty(ACTION); - } - - public static void setAction(String exportImportAction) { - System.setProperty(ACTION, exportImportAction); - } - - public static String getProvider() { - return System.getProperty(PROVIDER, PROVIDER_DEFAULT); - } - - public static void setProvider(String exportImportProvider) { - System.setProperty(PROVIDER, exportImportProvider); - } - - public static String getDir() { - return System.getProperty(DIR); - } - - public static String setDir(String dir) { - return System.setProperty(DIR, dir); - } - - public static String getZipFile() { - return System.getProperty(FILE); - } - - public static void setZipFile(String exportImportZipFile) { - System.setProperty(FILE, exportImportZipFile); - } - - public static String getZipPassword() { - return System.getProperty(PASSWORD); - } - - public static void setZipPassword(String exportImportZipPassword) { - System.setProperty(PASSWORD, exportImportZipPassword); - } - -} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java index 03c9dda2ab..c43d3c5233 100644 --- a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java @@ -12,14 +12,13 @@ import org.keycloak.util.ProviderLoader; /** * @author Marek Posolda */ -public class ExportImportProviderImpl implements ExportImportProvider { +public class ExportImportProviderImpl { private static final Logger logger = Logger.getLogger(ExportImportProviderImpl.class); public static final String ACTION_EXPORT = "export"; public static final String ACTION_IMPORT = "import"; - @Override public void checkExportImport(KeycloakSessionFactory sessionFactory) { String exportImportAction = ExportImportConfig.getAction(); diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportUtils.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportUtils.java deleted file mode 100644 index cbdf36de15..0000000000 --- a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportUtils.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.keycloak.exportimport; - -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.RealmRepresentation; - -/** - * @author Marek Posolda - */ -public class ExportImportUtils { - - public RealmRepresentation exportRealm(RealmModel realm, boolean includeUsers) { - - return null; - } - - public RealmRepresentation exportUsers(RealmModel realm, int start, int count) { - - return null; - } -} diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java deleted file mode 100644 index 1f0c55e357..0000000000 --- a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.keycloak.exportimport; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Test; -import org.keycloak.model.test.AbstractModelTest; -import org.keycloak.model.test.ImportTest; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.services.managers.ApplianceBootstrap; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.KeycloakApplication; -import org.keycloak.util.ProviderLoader; - -import java.util.Iterator; - -/** - * @author Marek Posolda - */ -public abstract class ExportImportTestBase { - - protected KeycloakSessionFactory factory; - - protected KeycloakSession session; - protected RealmManager realmManager; - - @After - public void after() { - System.getProperties().remove("keycloak.model.provider"); - } - - @Test - public void testExportImport() throws Exception { - // Init JPA model - System.setProperty("keycloak.model.provider", getExportModelProvider()); - factory = KeycloakApplication.createSessionFactory(); - - // Bootstrap admin realm - beginTransaction(); - new ApplianceBootstrap().bootstrap(session, "/auth"); - commitTransaction(); - - // Classic import of realm to JPA model - beginTransaction(); - RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json"); - realmManager = new RealmManager(session); - RealmModel realm = realmManager.createRealm("demo", rep.getRealm()); - realmManager.importRealm(rep, realm); - - commitTransaction(); - - // Full export of realm - exportModel(factory); - - beginTransaction(); - realm = session.model().getRealm("demo"); - String wburkeId = realm.getUser("wburke").getId(); - String appId = realm.getApplicationByName("Application").getId(); - - // Commit transaction and close JPA now - commitTransaction(); - factory.close(); - - // Bootstrap mongo session and factory - System.setProperty("keycloak.model.provider", getImportModelProvider()); - factory = KeycloakApplication.createSessionFactory(); - - // Full import of previous export into mongo - importModel(factory); - - // Verify it's imported in mongo (reusing ImportTest) - beginTransaction(); - RealmModel importedRealm = session.model().getRealm("demo"); - System.out.println("Exported realm: " + realm + ", Imported realm: " + importedRealm); - - Assert.assertEquals(wburkeId, importedRealm.getUser("wburke").getId()); - Assert.assertEquals(appId, importedRealm.getApplicationByName("Application").getId()); - ImportTest.assertDataImportedInRealm(importedRealm); - - // Commit and close Mongo - commitTransaction(); - factory.close(); - } - - protected abstract String getExportModelProvider(); - - protected abstract String getImportModelProvider(); - - protected abstract void exportModel(KeycloakSessionFactory factory); - - protected abstract void importModel(KeycloakSessionFactory factory); - - protected void beginTransaction() { - session = factory.create(); - session.getTransaction().begin(); - realmManager = new RealmManager(session); - } - - protected void commitTransaction() { - session.getTransaction().commit(); - session.close(); - } - - protected ExportImportProvider getExportImportProvider() { - Iterator providers = ProviderLoader.load(ExportImportProvider.class).iterator(); - - if (providers.hasNext()) { - return providers.next(); - } else { - throw new IllegalStateException("ExportImportProvider not found"); - } - } -} diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java index 9705ea6030..32be243a71 100644 --- a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java +++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java @@ -10,29 +10,5 @@ import org.keycloak.models.KeycloakSessionFactory; * @author Marek Posolda */ @Ignore -public class JPAToMongoExportImportTest extends ExportImportTestBase { - - @Override - protected String getExportModelProvider() { - return "jpa"; - } - - @Override - protected String getImportModelProvider() { - return "mongo"; - } - - @Override - protected void exportModel(KeycloakSessionFactory factory) { - ExportImportConfig.setAction(ExportImportProviderImpl.ACTION_EXPORT); - ExportImportConfig.setProvider(TmpDirExportImportIOProvider.PROVIDER_ID); - getExportImportProvider().checkExportImport(factory); - } - - @Override - protected void importModel(KeycloakSessionFactory factory) { - ExportImportConfig.setAction(ExportImportProviderImpl.ACTION_IMPORT); - ExportImportConfig.setProvider(TmpDirExportImportIOProvider.PROVIDER_ID); - getExportImportProvider().checkExportImport(factory); - } +public class JPAToMongoExportImportTest { } diff --git a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java index 978872f262..686722a29c 100644 --- a/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java +++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java @@ -13,59 +13,5 @@ import java.io.File; * @author Marek Posolda */ @Ignore -public class MongoToJPAExportImportTest extends ExportImportTestBase { - - private static final String zipFile = "keycloak-export.zip"; - - @Override - protected String getExportModelProvider() { - return "mongo"; - } - - @Override - protected String getImportModelProvider() { - return "jpa"; - } - - @Override - protected void exportModel(KeycloakSessionFactory factory) { - ExportImportConfig.setAction(ExportImportProviderImpl.ACTION_EXPORT); - ExportImportConfig.setProvider(EncryptedZIPIOProvider.PROVIDER_ID); - File zipFile = getZipFile(); - ExportImportConfig.setZipFile(zipFile.getAbsolutePath()); - ExportImportConfig.setZipPassword("password123"); - - if (zipFile.exists()) { - zipFile.delete(); - } - - new ExportImportProviderImpl().checkExportImport(factory); - } - - @Override - protected void importModel(KeycloakSessionFactory factory) { - ExportImportConfig.setAction(ExportImportProviderImpl.ACTION_IMPORT); - ExportImportConfig.setProvider(EncryptedZIPIOProvider.PROVIDER_ID); - File zipFile = getZipFile(); - ExportImportConfig.setZipFile(zipFile.getAbsolutePath()); - ExportImportConfig.setZipPassword("password-invalid"); - - // Try invalid password - try { - new ExportImportProviderImpl().checkExportImport(factory); - Assert.fail("Not expected to be here. Exception should be thrown"); - } catch (Exception e) {}; - - ExportImportConfig.setZipPassword("password123"); - new ExportImportProviderImpl().checkExportImport(factory); - - if (zipFile.exists()) { - zipFile.delete(); - } - } - - private File getZipFile() { - String tempDir = System.getProperty("java.io.tmpdir"); - return new File(tempDir + File.separator + "keycloak-export.zip"); - } +public class MongoToJPAExportImportTest { } diff --git a/export-import/export-import-single-file/pom.xml b/export-import/export-import-single-file/pom.xml new file mode 100644 index 0000000000..25d65c0e43 --- /dev/null +++ b/export-import/export-import-single-file/pom.xml @@ -0,0 +1,65 @@ + + + + keycloak-export-import-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-export-import-single-file + Keycloak Export Import To Single File + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + org.keycloak + keycloak-export-import-api + ${project.version} + provided + + + org.codehaus.jackson + jackson-core-asl + provided + + + org.codehaus.jackson + jackson-mapper-asl + provided + + + org.jboss.logging + jboss-logging + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java new file mode 100644 index 0000000000..4a63ca0699 --- /dev/null +++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java @@ -0,0 +1,84 @@ +package org.keycloak.exportimport.singlefile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.codehaus.jackson.map.ObjectMapper; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.ExportProvider; +import org.keycloak.exportimport.util.ExportImportJob; +import org.keycloak.exportimport.util.ExportImportUtils; +import org.keycloak.exportimport.util.ExportUtils; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class SingleFileExportProvider implements ExportProvider { + + private static final Logger logger = Logger.getLogger(SingleFileExportProvider.class); + + private File file; + + public SingleFileExportProvider(File file) { + this.file = file; + } + + public void setFile(File file) { + this.file = file; + } + + @Override + public void exportModel(final KeycloakSession session) throws IOException { + logger.infof("Exporting model into file %s", this.file.getAbsolutePath()); + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + List realms = session.getModel().getRealms(); + List reps = new ArrayList(); + for (RealmModel realm : realms) { + reps.add(ExportUtils.exportRealm(realm, true)); + } + + writeToFile(reps); + } + + }); + + } + + @Override + public void exportRealm(final KeycloakSession session, final String realmName) throws IOException { + logger.infof("Exporting realm '%s' into file %s", realmName, this.file.getAbsolutePath()); + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + RealmModel realm = session.getModel().getRealmByName(realmName); + RealmRepresentation realmRep = ExportUtils.exportRealm(realm, true); + writeToFile(realmRep); + } + + }); + } + + @Override + public void close() { + } + + private ObjectMapper getObjectMapper() { + return JsonSerialization.prettyMapper; + } + + private void writeToFile(Object reps) throws IOException { + FileOutputStream stream = new FileOutputStream(this.file); + getObjectMapper().writeValue(stream, reps); + } +} diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProviderFactory.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProviderFactory.java new file mode 100644 index 0000000000..614c3bacab --- /dev/null +++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProviderFactory.java @@ -0,0 +1,36 @@ +package org.keycloak.exportimport.singlefile; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ExportProvider; +import org.keycloak.exportimport.ExportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class SingleFileExportProviderFactory implements ExportProviderFactory { + + public static final String PROVIDER_ID = "singleFile"; + + @Override + public ExportProvider create(KeycloakSession session) { + String fileName = ExportImportConfig.getFile(); + return new SingleFileExportProvider(new File(fileName)); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} 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 new file mode 100644 index 0000000000..5ac38f400a --- /dev/null +++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java @@ -0,0 +1,58 @@ +package org.keycloak.exportimport.singlefile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.Strategy; +import org.keycloak.exportimport.util.ExportImportJob; +import org.keycloak.exportimport.util.ExportImportUtils; +import org.keycloak.exportimport.util.ExportUtils; +import org.keycloak.exportimport.util.ImportUtils; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class SingleFileImportProvider implements ImportProvider { + + private static final Logger logger = Logger.getLogger(SingleFileImportProvider.class); + + private File file; + + public SingleFileImportProvider(File file) { + this.file = file; + } + + @Override + public void importModel(final KeycloakSession session, final Strategy strategy) throws IOException { + logger.infof("Full importing from file %s", this.file.getAbsolutePath()); + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + FileInputStream is = new FileInputStream(file); + ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy); + } + + }); + } + + @Override + public void importRealm(KeycloakSession session, String realmName, Strategy strategy) throws IOException { + // TODO: just that single realm in case that file contains many realms? + importModel(session, strategy); + } + + @Override + public void close() { + + } +} diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProviderFactory.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProviderFactory.java new file mode 100644 index 0000000000..6e3e73261d --- /dev/null +++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProviderFactory.java @@ -0,0 +1,34 @@ +package org.keycloak.exportimport.singlefile; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.ImportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class SingleFileImportProviderFactory implements ImportProviderFactory { + + @Override + public ImportProvider create(KeycloakSession session) { + String fileName = ExportImportConfig.getFile(); + return new SingleFileImportProvider(new File(fileName)); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return SingleFileExportProviderFactory.PROVIDER_ID; + } +} diff --git a/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory b/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory new file mode 100644 index 0000000000..82a1fafc06 --- /dev/null +++ b/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory \ No newline at end of file diff --git a/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory b/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory new file mode 100644 index 0000000000..d2192b6e84 --- /dev/null +++ b/export-import/export-import-single-file/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory \ No newline at end of file diff --git a/export-import/export-import-zip/pom.xml b/export-import/export-import-zip/pom.xml new file mode 100644 index 0000000000..7738f85b7b --- /dev/null +++ b/export-import/export-import-zip/pom.xml @@ -0,0 +1,70 @@ + + + + keycloak-export-import-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-export-import-zip + Keycloak Export Import To Encrypted ZIP + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + org.keycloak + keycloak-export-import-api + ${project.version} + provided + + + org.codehaus.jackson + jackson-core-asl + provided + + + org.codehaus.jackson + jackson-mapper-asl + provided + + + org.jboss.logging + jboss-logging + provided + + + de.idyl + winzipaes + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProvider.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProvider.java new file mode 100644 index 0000000000..ac0e0dfe7c --- /dev/null +++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProvider.java @@ -0,0 +1,73 @@ +package org.keycloak.exportimport.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import de.idyl.winzipaes.AesZipFileEncrypter; +import de.idyl.winzipaes.impl.AESEncrypter; +import de.idyl.winzipaes.impl.AESEncrypterBC; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.util.ExportUtils; +import org.keycloak.exportimport.util.MultipleStepsExportProvider; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class ZipExportProvider extends MultipleStepsExportProvider { + + private static final Logger logger = Logger.getLogger(ZipExportProvider.class); + + private final AesZipFileEncrypter encrypter; + private final String password; + + public ZipExportProvider(File zipFile, String password) { + if (zipFile.exists()) { + throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " already exists"); + } + this.password = password; + + try { + AESEncrypter encrypter = new AESEncrypterBC(); + this.encrypter = new AesZipFileEncrypter(zipFile, encrypter); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + logger.infof("Exporting into zip file %s", zipFile.getAbsolutePath()); + } + + @Override + protected void writeRealm(String fileName, RealmRepresentation rep) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + JsonSerialization.mapper.writeValue(stream, rep); + + byte[] byteArray = stream.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(byteArray); + this.encrypter.add(fileName, bis, this.password); + } + + @Override + protected void writeUsers(String fileName, RealmModel realm, List users) throws IOException { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + ExportUtils.exportUsersToStream(realm, users, JsonSerialization.mapper, stream); + + byte[] byteArray = stream.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(byteArray); + this.encrypter.add(fileName, bis, this.password); + } + + @Override + public void close() { + try { + this.encrypter.close(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProviderFactory.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProviderFactory.java new file mode 100644 index 0000000000..03f014760e --- /dev/null +++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipExportProviderFactory.java @@ -0,0 +1,44 @@ +package org.keycloak.exportimport.zip; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ExportProvider; +import org.keycloak.exportimport.ExportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class ZipExportProviderFactory implements ExportProviderFactory { + + + public static final String PROVIDER_ID = "zip"; + + @Override + public ExportProvider create(KeycloakSession session) { + String fileName = ExportImportConfig.getZipFile(); + String password = ExportImportConfig.getZipPassword(); + if (fileName == null) { + throw new IllegalArgumentException("ZIP file for export not provided"); + } + if (password == null) { + throw new IllegalArgumentException("Password for encrypting ZIP not provided"); + } + return new ZipExportProvider(new File(fileName), password); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} 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 new file mode 100644 index 0000000000..6ff1f5a244 --- /dev/null +++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java @@ -0,0 +1,109 @@ +package org.keycloak.exportimport.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.zip.DataFormatException; + +import de.idyl.winzipaes.AesZipFileDecrypter; +import de.idyl.winzipaes.impl.AESDecrypter; +import de.idyl.winzipaes.impl.AESDecrypterBC; +import de.idyl.winzipaes.impl.ExtZipEntry; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.Strategy; +import org.keycloak.exportimport.util.ExportImportJob; +import org.keycloak.exportimport.util.ExportImportUtils; +import org.keycloak.exportimport.util.ImportUtils; +import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class ZipImportProvider implements ImportProvider { + + private static final Logger logger = Logger.getLogger(ZipImportProvider.class); + + private final AesZipFileDecrypter decrypter; + private final String password; + + public ZipImportProvider(File zipFile, String password) { + try { + if (!zipFile.exists()) { + throw new IllegalStateException("File " + zipFile.getAbsolutePath() + " doesn't exists"); + } + + AESDecrypter decrypter = new AESDecrypterBC(); + this.decrypter = new AesZipFileDecrypter(zipFile, decrypter); + this.password = password; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + logger.infof("Importing from ZIP file %s", zipFile.getAbsolutePath()); + } + + @Override + public void importModel(KeycloakSession session, Strategy strategy) throws IOException { + for (ExtZipEntry entry : this.decrypter.getEntryList()) { + String entryName = entry.getName(); + if (entryName.endsWith("-realm.json")) { + // Parse "foo" from "foo-realm.json" + String realmName = entryName.substring(0, entryName.length() - 11); + importRealm(session, realmName, strategy); + } + } + } + + @Override + public void importRealm(final KeycloakSession session, final String realmName, final Strategy strategy) throws IOException { + try { + // Import realm first + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + this.decrypter.extractEntry(this.decrypter.getEntry(realmName + "-realm.json"), bos, this.password); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + final RealmRepresentation realmRep = JsonSerialization.mapper.readValue(bis, RealmRepresentation.class); + + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + ImportUtils.importRealm(session, realmRep, strategy); + } + + }); + + + // Import users + for (ExtZipEntry entry : this.decrypter.getEntryList()) { + String name = entry.getName(); + if ( (name.startsWith(realmName)) && (name.endsWith(".json")) && (name.substring(realmName.length()).contains("-users-")) ) { + bos = new ByteArrayOutputStream(); + this.decrypter.extractEntry(entry, bos, this.password); + final ByteArrayInputStream bis2 = new ByteArrayInputStream(bos.toByteArray()); + + ExportImportUtils.runJobInTransaction(session, new ExportImportJob() { + + @Override + public void run() throws IOException { + ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, bis2); + } + }); + } + } + } catch (DataFormatException dfe) { + throw new RuntimeException(dfe); + } + } + + @Override + public void close() { + try { + this.decrypter.close(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProviderFactory.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProviderFactory.java new file mode 100644 index 0000000000..9127a8bcb9 --- /dev/null +++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProviderFactory.java @@ -0,0 +1,41 @@ +package org.keycloak.exportimport.zip; + +import java.io.File; + +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ImportProvider; +import org.keycloak.exportimport.ImportProviderFactory; +import org.keycloak.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public class ZipImportProviderFactory implements ImportProviderFactory { + + @Override + public ImportProvider create(KeycloakSession session) { + String fileName = ExportImportConfig.getZipFile(); + String password = ExportImportConfig.getZipPassword(); + if (fileName == null) { + throw new IllegalArgumentException("ZIP file for import not provided"); + } + if (password == null) { + throw new IllegalArgumentException("Password for decrypting ZIP not provided"); + } + return new ZipImportProvider(new File(fileName), password); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return ZipExportProviderFactory.PROVIDER_ID; + } +} diff --git a/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory b/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory new file mode 100644 index 0000000000..dce02f18db --- /dev/null +++ b/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.zip.ZipExportProviderFactory \ No newline at end of file diff --git a/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory b/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory new file mode 100644 index 0000000000..b88f372286 --- /dev/null +++ b/export-import/export-import-zip/src/main/resources/META-INF/services/org.keycloak.exportimport.ImportProviderFactory @@ -0,0 +1 @@ +org.keycloak.exportimport.zip.ZipImportProviderFactory \ No newline at end of file diff --git a/export-import/pom.xml b/export-import/pom.xml index b9a5a4d2e5..15d39f6a36 100755 --- a/export-import/pom.xml +++ b/export-import/pom.xml @@ -17,6 +17,9 @@ export-import-api export-import-impl + export-import-dir + export-import-single-file + export-import-zip diff --git a/model/api/pom.xml b/model/api/pom.xml index f72d856fea..d0af4dc25a 100755 --- a/model/api/pom.xml +++ b/model/api/pom.xml @@ -30,6 +30,11 @@ ${project.version} provided + + org.jboss.logging + jboss-logging + provided + junit junit diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 939eebaddc..68eda42528 100644 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -3,6 +3,9 @@ package org.keycloak.models.utils; import java.io.IOException; import java.io.StringWriter; import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Set; @@ -10,8 +13,11 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import org.bouncycastle.openssl.PEMWriter; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.util.PemUtils; @@ -65,6 +71,23 @@ public final class KeycloakModelUtils { return PemUtils.removeBeginEnd(s); } + public static void generateRealmKeys(RealmModel realm) { + KeyPair keyPair = null; + try { + keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + realm.setPrivateKey(keyPair.getPrivate()); + realm.setPublicKey(keyPair.getPublic()); + } + + public static UserCredentialModel generateSecret(ClientModel app) { + UserCredentialModel secret = UserCredentialModel.generateSecret(); + app.setSecret(secret.getValue()); + return secret; + } + /** * Deep search if given role is descendant of composite role * diff --git a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java similarity index 79% rename from services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java rename to model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 4a27c2d6c1..c7f3e36e71 100755 --- a/services/src/main/java/org/keycloak/services/managers/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -1,4 +1,4 @@ -package org.keycloak.services.managers; +package org.keycloak.models.utils; import org.keycloak.models.ApplicationModel; import org.keycloak.models.AuthenticationProviderModel; @@ -13,9 +13,11 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.AuthenticationProviderRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.OAuthClientRepresentation; import org.keycloak.representations.idm.RealmAuditRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -29,6 +31,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; /** * @author Bill Burke @@ -201,4 +204,53 @@ public class ModelToRepresentation { } return rep; } + + public static ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) { + ApplicationRepresentation rep = new ApplicationRepresentation(); + rep.setId(applicationModel.getId()); + rep.setName(applicationModel.getName()); + rep.setEnabled(applicationModel.isEnabled()); + rep.setAdminUrl(applicationModel.getManagementUrl()); + rep.setPublicClient(applicationModel.isPublicClient()); + rep.setBearerOnly(applicationModel.isBearerOnly()); + rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired()); + rep.setBaseUrl(applicationModel.getBaseUrl()); + rep.setNotBefore(applicationModel.getNotBefore()); + + Set redirectUris = applicationModel.getRedirectUris(); + if (redirectUris != null) { + rep.setRedirectUris(new LinkedList(redirectUris)); + } + + Set webOrigins = applicationModel.getWebOrigins(); + if (webOrigins != null) { + rep.setWebOrigins(new LinkedList(webOrigins)); + } + + if (!applicationModel.getDefaultRoles().isEmpty()) { + rep.setDefaultRoles(applicationModel.getDefaultRoles().toArray(new String[0])); + } + + return rep; + } + + public static OAuthClientRepresentation toRepresentation(OAuthClientModel model) { + OAuthClientRepresentation rep = new OAuthClientRepresentation(); + rep.setId(model.getId()); + rep.setName(model.getClientId()); + rep.setEnabled(model.isEnabled()); + rep.setPublicClient(model.isPublicClient()); + rep.setDirectGrantsOnly(model.isDirectGrantsOnly()); + Set redirectUris = model.getRedirectUris(); + if (redirectUris != null) { + rep.setRedirectUris(new LinkedList(redirectUris)); + } + + Set webOrigins = model.getWebOrigins(); + if (webOrigins != null) { + rep.setWebOrigins(new LinkedList(webOrigins)); + } + rep.setNotBefore(model.getNotBefore()); + return rep; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java new file mode 100644 index 0000000000..b031eac75e --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -0,0 +1,671 @@ +package org.keycloak.models.utils; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.iharder.Base64; +import org.jboss.logging.Logger; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.AuthenticationLinkModel; +import org.keycloak.models.AuthenticationProviderModel; +import org.keycloak.models.ClaimMask; +import org.keycloak.models.ClientModel; +import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.SocialLinkModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.ApplicationRepresentation; +import org.keycloak.representations.idm.AuthenticationLinkRepresentation; +import org.keycloak.representations.idm.AuthenticationProviderRepresentation; +import org.keycloak.representations.idm.ClaimRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.OAuthClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.ScopeMappingRepresentation; +import org.keycloak.representations.idm.SocialLinkRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +public class RepresentationToModel { + + private static Logger logger = Logger.getLogger(RepresentationToModel.class); + + public static void importRealm(RealmRepresentation rep, RealmModel newRealm) { + newRealm.setName(rep.getRealm()); + if (rep.isEnabled() != null) newRealm.setEnabled(rep.isEnabled()); + if (rep.isSocial() != null) newRealm.setSocial(rep.isSocial()); + if (rep.isBruteForceProtected() != null) newRealm.setBruteForceProtected(rep.isBruteForceProtected()); + if (rep.getMaxFailureWaitSeconds() != null) newRealm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); + if (rep.getMinimumQuickLoginWaitSeconds() != null) newRealm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); + if (rep.getWaitIncrementSeconds() != null) newRealm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); + if (rep.getQuickLoginCheckMilliSeconds() != null) newRealm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); + if (rep.getMaxDeltaTimeSeconds() != null) newRealm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); + if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor()); + + if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore()); + + if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); + else newRealm.setAccessTokenLifespan(300); + + if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); + else newRealm.setSsoSessionIdleTimeout(600); + if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); + else newRealm.setSsoSessionMaxLifespan(36000); + + if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); + else newRealm.setAccessCodeLifespan(60); + + if (rep.getAccessCodeLifespanUserAction() != null) + newRealm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); + else newRealm.setAccessCodeLifespanUserAction(300); + + if (rep.isSslNotRequired() != null) newRealm.setSslNotRequired(rep.isSslNotRequired()); + if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); + if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed()); + if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe()); + if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); + if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); + if (rep.isUpdateProfileOnInitialSocialLogin() != null) + newRealm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin()); + if (rep.getPrivateKey() == null || rep.getPublicKey() == null) { + KeycloakModelUtils.generateRealmKeys(newRealm); + } else { + newRealm.setPrivateKeyPem(rep.getPrivateKey()); + newRealm.setPublicKeyPem(rep.getPublicKey()); + } + if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme()); + if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme()); + if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme()); + if (rep.getEmailTheme() != null) newRealm.setEmailTheme(rep.getEmailTheme()); + + if (rep.getRequiredCredentials() != null) { + for (String requiredCred : rep.getRequiredCredentials()) { + addRequiredCredential(newRealm, requiredCred); + } + } else { + addRequiredCredential(newRealm, CredentialRepresentation.PASSWORD); + } + + if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy())); + + if (rep.getApplications() != null) { + Map appMap = createApplications(rep, newRealm); + } + + if (rep.getRoles() != null) { + if (rep.getRoles().getRealm() != null) { // realm roles + for (RoleRepresentation roleRep : rep.getRoles().getRealm()) { + createRole(newRealm, roleRep); + } + } + if (rep.getRoles().getApplication() != null) { + for (Map.Entry> entry : rep.getRoles().getApplication().entrySet()) { + ApplicationModel app = newRealm.getApplicationByName(entry.getKey()); + if (app == null) { + throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey()); + } + for (RoleRepresentation roleRep : entry.getValue()) { + // Application role may already exists (for example if it is defaultRole) + RoleModel role = app.getRole(roleRep.getName()); + if (role == null) { + role = app.addRole(roleRep.getName()); + } + role.setDescription(roleRep.getDescription()); + } + } + } + // now that all roles are created, re-iterate and set up composites + if (rep.getRoles().getRealm() != null) { // realm roles + for (RoleRepresentation roleRep : rep.getRoles().getRealm()) { + RoleModel role = newRealm.getRole(roleRep.getName()); + addComposites(role, roleRep, newRealm); + } + } + if (rep.getRoles().getApplication() != null) { + for (Map.Entry> entry : rep.getRoles().getApplication().entrySet()) { + ApplicationModel app = newRealm.getApplicationByName(entry.getKey()); + if (app == null) { + throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey()); + } + for (RoleRepresentation roleRep : entry.getValue()) { + RoleModel role = app.getRole(roleRep.getName()); + addComposites(role, roleRep, newRealm); + } + } + } + } + + + if (rep.getDefaultRoles() != null) { + for (String roleString : rep.getDefaultRoles()) { + newRealm.addDefaultRole(roleString.trim()); + } + } + + if (rep.getOauthClients() != null) { + createOAuthClients(rep, newRealm); + } + + + // Now that all possible roles and applications are created, create scope mappings + + Map appMap = newRealm.getApplicationNameMap(); + + if (rep.getApplicationScopeMappings() != null) { + + for (Map.Entry> entry : rep.getApplicationScopeMappings().entrySet()) { + ApplicationModel app = appMap.get(entry.getKey()); + if (app == null) { + throw new RuntimeException("Unable to find application role mappings for app: " + entry.getKey()); + } + createApplicationScopeMappings(newRealm, app, entry.getValue()); + } + } + + if (rep.getScopeMappings() != null) { + for (ScopeMappingRepresentation scope : rep.getScopeMappings()) { + ClientModel client = newRealm.findClient(scope.getClient()); + for (String roleString : scope.getRoles()) { + RoleModel role = newRealm.getRole(roleString.trim()); + if (role == null) { + role = newRealm.addRole(roleString.trim()); + } + client.addScopeMapping(role); + } + + } + } + + if (rep.getSmtpServer() != null) { + newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer())); + } + + if (rep.getSocialProviders() != null) { + newRealm.setSocialConfig(new HashMap(rep.getSocialProviders())); + } + if (rep.getLdapServer() != null) { + newRealm.setLdapServerConfig(new HashMap(rep.getLdapServer())); + } + + if (rep.getAuthenticationProviders() != null) { + List authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders()); + newRealm.setAuthenticationProviders(authProviderModels); + } else { + List authProviderModels = Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER); + newRealm.setAuthenticationProviders(authProviderModels); + } + + // create users and their role mappings and social mappings + + if (rep.getUsers() != null) { + for (UserRepresentation userRep : rep.getUsers()) { + UserModel user = createUser(newRealm, userRep, appMap); + } + } + } + + public static void updateRealm(RealmRepresentation rep, RealmModel realm) { + if (rep.getRealm() != null) { + realm.setName(rep.getRealm()); + } + if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled()); + if (rep.isSocial() != null) realm.setSocial(rep.isSocial()); + if (rep.isBruteForceProtected() != null) realm.setBruteForceProtected(rep.isBruteForceProtected()); + if (rep.getMaxFailureWaitSeconds() != null) realm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); + if (rep.getMinimumQuickLoginWaitSeconds() != null) realm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); + if (rep.getWaitIncrementSeconds() != null) realm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); + if (rep.getQuickLoginCheckMilliSeconds() != null) realm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); + if (rep.getMaxDeltaTimeSeconds() != null) realm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); + if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor()); + if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); + if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed()); + if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe()); + if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail()); + if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); + if (rep.isUpdateProfileOnInitialSocialLogin() != null) + realm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin()); + if (rep.isSslNotRequired() != null) realm.setSslNotRequired((rep.isSslNotRequired())); + if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); + if (rep.getAccessCodeLifespanUserAction() != null) + realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); + if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore()); + if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); + if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); + if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); + if (rep.getRequiredCredentials() != null) { + realm.updateRequiredCredentials(rep.getRequiredCredentials()); + } + if (rep.getLoginTheme() != null) realm.setLoginTheme(rep.getLoginTheme()); + if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme()); + if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme()); + if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme()); + + if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy())); + + if (rep.getDefaultRoles() != null) { + realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()])); + } + + if (rep.getSmtpServer() != null) { + realm.setSmtpConfig(new HashMap(rep.getSmtpServer())); + } + + if (rep.getSocialProviders() != null) { + realm.setSocialConfig(new HashMap(rep.getSocialProviders())); + } + + if (rep.getLdapServer() != null) { + realm.setLdapServerConfig(new HashMap(rep.getLdapServer())); + } + if (rep.getAuthenticationProviders() != null) { + List authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders()); + realm.setAuthenticationProviders(authProviderModels); + } + + if ("GENERATE".equals(rep.getPublicKey())) { + KeycloakModelUtils.generateRealmKeys(realm); + } + } + + // Basic realm stuff + + public static void addRequiredCredential(RealmModel newRealm, String requiredCred) { + newRealm.addRequiredCredential(requiredCred); + } + + + private static List convertAuthenticationProviders(List authenticationProviders) { + List result = new ArrayList(); + + for (AuthenticationProviderRepresentation representation : authenticationProviders) { + AuthenticationProviderModel model = new AuthenticationProviderModel(representation.getProviderName(), + representation.isPasswordUpdateSupported(), representation.getConfig()); + result.add(model); + } + return result; + } + + // Roles + + public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) { + RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName()); + if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription()); + } + + private static void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) { + if (roleRep.getComposites() == null) return; + if (roleRep.getComposites().getRealm() != null) { + for (String roleStr : roleRep.getComposites().getRealm()) { + RoleModel realmRole = realm.getRole(roleStr); + if (realmRole == null) throw new RuntimeException("Unable to find composite realm role: " + roleStr); + role.addCompositeRole(realmRole); + } + } + if (roleRep.getComposites().getApplication() != null) { + for (Map.Entry> entry : roleRep.getComposites().getApplication().entrySet()) { + ApplicationModel app = realm.getApplicationByName(entry.getKey()); + if (app == null) { + throw new RuntimeException("App doesn't exist in role definitions: " + roleRep.getName()); + } + for (String roleStr : entry.getValue()) { + RoleModel appRole = app.getRole(roleStr); + if (appRole == null) throw new RuntimeException("Unable to find composite app role: " + roleStr); + role.addCompositeRole(appRole); + } + + } + + } + + } + + // APPLICATIONS + + private static Map createApplications(RealmRepresentation rep, RealmModel realm) { + Map appMap = new HashMap(); + for (ApplicationRepresentation resourceRep : rep.getApplications()) { + ApplicationModel app = createApplication(realm, resourceRep); + appMap.put(app.getName(), app); + } + return appMap; + } + + /** + * Does not create scope or role mappings! + * + * @param realm + * @param resourceRep + * @return + */ + public static ApplicationModel createApplication(RealmModel realm, ApplicationRepresentation resourceRep) { + logger.debug("************ CREATE APPLICATION: {0}" + resourceRep.getName()); + ApplicationModel applicationModel = resourceRep.getId()!=null ? realm.addApplication(resourceRep.getId(), resourceRep.getName()) : realm.addApplication(resourceRep.getName()); + if (resourceRep.isEnabled() != null) applicationModel.setEnabled(resourceRep.isEnabled()); + applicationModel.setManagementUrl(resourceRep.getAdminUrl()); + if (resourceRep.isSurrogateAuthRequired() != null) + applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired()); + applicationModel.setBaseUrl(resourceRep.getBaseUrl()); + if (resourceRep.isBearerOnly() != null) applicationModel.setBearerOnly(resourceRep.isBearerOnly()); + if (resourceRep.isPublicClient() != null) applicationModel.setPublicClient(resourceRep.isPublicClient()); + applicationModel.updateApplication(); + + if (resourceRep.getNotBefore() != null) { + applicationModel.setNotBefore(resourceRep.getNotBefore()); + } + + applicationModel.setSecret(resourceRep.getSecret()); + if (applicationModel.getSecret() == null) { + KeycloakModelUtils.generateSecret(applicationModel); + } + + + if (resourceRep.getRedirectUris() != null) { + for (String redirectUri : resourceRep.getRedirectUris()) { + applicationModel.addRedirectUri(redirectUri); + } + } + if (resourceRep.getWebOrigins() != null) { + for (String webOrigin : resourceRep.getWebOrigins()) { + logger.debugv("Application: {0} webOrigin: {1}", resourceRep.getName(), webOrigin); + applicationModel.addWebOrigin(webOrigin); + } + } else { + // add origins from redirect uris + if (resourceRep.getRedirectUris() != null) { + Set origins = new HashSet(); + for (String redirectUri : resourceRep.getRedirectUris()) { + logger.info("add redirectUri to origin: " + redirectUri); + if (redirectUri.startsWith("http:")) { + URI uri = URI.create(redirectUri); + String origin = uri.getScheme() + "://" + uri.getHost(); + if (uri.getPort() != -1) { + origin += ":" + uri.getPort(); + } + logger.debugv("adding default application origin: {0}" , origin); + origins.add(origin); + } + } + if (origins.size() > 0) { + applicationModel.setWebOrigins(origins); + } + } + } + + if (resourceRep.getDefaultRoles() != null) { + applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles()); + } + + if (resourceRep.getClaims() != null) { + setClaims(applicationModel, resourceRep.getClaims()); + } else { + applicationModel.setAllowedClaimsMask(ClaimMask.USERNAME); + } + + return applicationModel; + } + + public static void updateApplication(ApplicationRepresentation rep, ApplicationModel resource) { + if (rep.getName() != null) resource.setName(rep.getName()); + if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled()); + if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly()); + if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient()); + if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl()); + if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl()); + if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); + resource.updateApplication(); + + if (rep.getNotBefore() != null) { + resource.setNotBefore(rep.getNotBefore()); + } + if (rep.getDefaultRoles() != null) { + resource.updateDefaultRoles(rep.getDefaultRoles()); + } + + List redirectUris = rep.getRedirectUris(); + if (redirectUris != null) { + resource.setRedirectUris(new HashSet(redirectUris)); + } + + List webOrigins = rep.getWebOrigins(); + if (webOrigins != null) { + resource.setWebOrigins(new HashSet(webOrigins)); + } + + if (rep.getClaims() != null) { + setClaims(resource, rep.getClaims()); + } + } + + public static void setClaims(ClientModel model, ClaimRepresentation rep) { + long mask = model.getAllowedClaimsMask(); + if (rep.getAddress()) { + mask |= ClaimMask.ADDRESS; + } else { + mask &= ~ClaimMask.ADDRESS; + } + if (rep.getEmail()) { + mask |= ClaimMask.EMAIL; + } else { + mask &= ~ClaimMask.EMAIL; + } + if (rep.getGender()) { + mask |= ClaimMask.GENDER; + } else { + mask &= ~ClaimMask.GENDER; + } + if (rep.getLocale()) { + mask |= ClaimMask.LOCALE; + } else { + mask &= ~ClaimMask.LOCALE; + } + if (rep.getName()) { + mask |= ClaimMask.NAME; + } else { + mask &= ~ClaimMask.NAME; + } + if (rep.getPhone()) { + mask |= ClaimMask.PHONE; + } else { + mask &= ~ClaimMask.PHONE; + } + if (rep.getPicture()) { + mask |= ClaimMask.PICTURE; + } else { + mask &= ~ClaimMask.PICTURE; + } + if (rep.getProfile()) { + mask |= ClaimMask.PROFILE; + } else { + mask &= ~ClaimMask.PROFILE; + } + if (rep.getUsername()) { + mask |= ClaimMask.USERNAME; + } else { + mask &= ~ClaimMask.USERNAME; + } + if (rep.getWebsite()) { + mask |= ClaimMask.WEBSITE; + } else { + mask &= ~ClaimMask.WEBSITE; + } + model.setAllowedClaimsMask(mask); + } + + // OAuth clients + + private static void createOAuthClients(RealmRepresentation realmRep, RealmModel realm) { + for (OAuthClientRepresentation rep : realmRep.getOauthClients()) { + createOAuthClient(rep, realm); + } + } + + public static OAuthClientModel createOAuthClient(String id, String name, RealmModel realm) { + OAuthClientModel model = id!=null ? realm.addOAuthClient(id, name) : realm.addOAuthClient(name); + KeycloakModelUtils.generateSecret(model); + return model; + } + + public static OAuthClientModel createOAuthClient(OAuthClientRepresentation rep, RealmModel realm) { + OAuthClientModel model = createOAuthClient(rep.getId(), rep.getName(), realm); + updateOAuthClient(rep, model); + return model; + } + + public static void updateOAuthClient(OAuthClientRepresentation rep, OAuthClientModel model) { + if (rep.getName() != null) model.setClientId(rep.getName()); + if (rep.isEnabled() != null) model.setEnabled(rep.isEnabled()); + if (rep.isPublicClient() != null) model.setPublicClient(rep.isPublicClient()); + if (rep.isDirectGrantsOnly() != null) model.setDirectGrantsOnly(rep.isDirectGrantsOnly()); + if (rep.getClaims() != null) { + setClaims(model, rep.getClaims()); + } + if (rep.getNotBefore() != null) { + model.setNotBefore(rep.getNotBefore()); + } + if (rep.getSecret() != null) model.setSecret(rep.getSecret()); + List redirectUris = rep.getRedirectUris(); + if (redirectUris != null) { + model.setRedirectUris(new HashSet(redirectUris)); + } + + List webOrigins = rep.getWebOrigins(); + if (webOrigins != null) { + model.setWebOrigins(new HashSet(webOrigins)); + } + + if (rep.getClaims() != null) { + setClaims(model, rep.getClaims()); + } + + if (rep.getNotBefore() != null) { + model.setNotBefore(rep.getNotBefore()); + } + + } + + // Scope mappings + + public static void createApplicationScopeMappings(RealmModel realm, ApplicationModel applicationModel, List mappings) { + for (ScopeMappingRepresentation mapping : mappings) { + ClientModel client = realm.findClient(mapping.getClient()); + for (String roleString : mapping.getRoles()) { + RoleModel role = applicationModel.getRole(roleString.trim()); + if (role == null) { + role = applicationModel.addRole(roleString.trim()); + } + client.addScopeMapping(role); + } + } + } + + // Users + + public static UserModel createUser(RealmModel newRealm, UserRepresentation userRep, Map appMap) { + UserModel user = newRealm.addUser(userRep.getId(), userRep.getUsername(), false); + user.setEnabled(userRep.isEnabled()); + user.setEmail(userRep.getEmail()); + user.setFirstName(userRep.getFirstName()); + user.setLastName(userRep.getLastName()); + if (userRep.getAttributes() != null) { + for (Map.Entry entry : userRep.getAttributes().entrySet()) { + user.setAttribute(entry.getKey(), entry.getValue()); + } + } + if (userRep.getRequiredActions() != null) { + for (String requiredAction : userRep.getRequiredActions()) { + user.addRequiredAction(UserModel.RequiredAction.valueOf(requiredAction)); + } + } + if (userRep.getCredentials() != null) { + for (CredentialRepresentation cred : userRep.getCredentials()) { + updateCredential(user, cred); + } + } + if (userRep.getAuthenticationLink() != null) { + AuthenticationLinkRepresentation link = userRep.getAuthenticationLink(); + AuthenticationLinkModel authLink = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId()); + user.setAuthenticationLink(authLink); + } + if (userRep.getSocialLinks() != null) { + for (SocialLinkRepresentation socialLink : userRep.getSocialLinks()) { + SocialLinkModel mappingModel = new SocialLinkModel(socialLink.getSocialProvider(), socialLink.getSocialUserId(), socialLink.getSocialUsername()); + newRealm.addSocialLink(user, mappingModel); + } + } + if (userRep.getRealmRoles() != null) { + for (String roleString : userRep.getRealmRoles()) { + RoleModel role = newRealm.getRole(roleString.trim()); + if (role == null) { + role = newRealm.addRole(roleString.trim()); + } + user.grantRole(role); + } + } + if (userRep.getApplicationRoles() != null) { + for (Map.Entry> entry : userRep.getApplicationRoles().entrySet()) { + ApplicationModel app = appMap.get(entry.getKey()); + if (app == null) { + throw new RuntimeException("Unable to find application role mappings for app: " + entry.getKey()); + } + createApplicationRoleMappings(app, user, entry.getValue()); + } + } + return user; + } + + // Detect if it is "plain-text" or "hashed" representation and update model according to it + private static void updateCredential(UserModel user, CredentialRepresentation cred) { + if (cred.getValue() != null) { + UserCredentialModel plainTextCred = convertCredential(cred); + user.updateCredential(plainTextCred); + } else { + UserCredentialValueModel hashedCred = new UserCredentialValueModel(); + hashedCred.setType(cred.getType()); + hashedCred.setDevice(cred.getDevice()); + hashedCred.setHashIterations(cred.getHashIterations()); + try { + hashedCred.setSalt(Base64.decode(cred.getSalt())); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + hashedCred.setValue(cred.getHashedSaltedValue()); + user.updateCredentialDirectly(hashedCred); + } + } + + public static UserCredentialModel convertCredential(CredentialRepresentation cred) { + UserCredentialModel credential = new UserCredentialModel(); + credential.setType(cred.getType()); + credential.setValue(cred.getValue()); + return credential; + } + + // Role mappings + + public static void createApplicationRoleMappings(ApplicationModel applicationModel, UserModel user, List roleNames) { + if (user == null) { + throw new RuntimeException("User not found"); + } + + for (String roleName : roleNames) { + RoleModel role = applicationModel.getRole(roleName.trim()); + if (role == null) { + role = applicationModel.addRole(roleName.trim()); + } + user.grantRole(role); + + } + } + +} diff --git a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java index 7593ae5320..412ef28d73 100755 --- a/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java @@ -5,7 +5,6 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.models.ApplicationModel; -import org.keycloak.models.Constants; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.PasswordPolicy; @@ -16,10 +15,8 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionProvider; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.managers.OAuthClientManager; import org.keycloak.services.managers.RealmManager; import java.util.ArrayList; @@ -151,8 +148,9 @@ public class AdapterTest extends AbstractModelTest { public void testOAuthClient() throws Exception { test1CreateRealm(); - OAuthClientModel oauth = new OAuthClientManager(realmModel).create("oauth-client"); - oauth = realmModel.getOAuthClient("oauth-client"); + RepresentationToModel.createOAuthClient(null, "oauth-client", realmModel); + OAuthClientModel oauth = realmModel.getOAuthClient("oauth-client"); + Assert.assertNotNull(oauth); } @Test diff --git a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java index f8869bcde1..58c0444b12 100755 --- a/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java @@ -7,6 +7,8 @@ import org.keycloak.models.ApplicationModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.services.managers.ApplicationManager; @@ -57,10 +59,10 @@ public class ApplicationModelTest extends AbstractModelTest { @Test public void json() { - ApplicationRepresentation representation = appManager.toRepresentation(application); + ApplicationRepresentation representation = ModelToRepresentation.toRepresentation(application); RealmModel realm = realmManager.createRealm("copy"); - ApplicationModel copy = appManager.createApplication(realm, representation); + ApplicationModel copy = RepresentationToModel.createApplication(realm, representation); assertEquals(application, copy); } diff --git a/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java index 05fee92813..9d53df165c 100755 --- a/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java @@ -4,13 +4,10 @@ import org.junit.Assert; import org.junit.Test; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import java.util.HashMap; -import java.util.Iterator; -import java.util.List; public class ModelTest extends AbstractModelTest { diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java index 6f13dac86b..cf4d70c81b 100644 --- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java +++ b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java @@ -37,7 +37,7 @@ public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredenti @Override public void setup(LDAPIdentityStore store) { - // TODO: Don't setup it here once PLIDM-508 is fixed + // TODO: Don't setup it here once PLINK-508 is fixed if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) { String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate"); this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512"; diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java index 56cee64ac2..ebaf3d797c 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java @@ -12,6 +12,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.CredentialRepresentation; import java.util.Arrays; @@ -58,7 +59,7 @@ public class ApplianceBootstrap { realm.setAccessCodeLifespanUserAction(300); realm.setSslNotRequired(true); realm.setRegistrationAllowed(false); - manager.generateRealmKeys(realm); + KeycloakModelUtils.generateRealmKeys(realm); realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER)); realm.setAuditListeners(Collections.singleton("jboss-logging")); diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java index 1f186b1612..3f9daca73e 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java @@ -4,26 +4,15 @@ import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonPropertyOrder; import org.jboss.logging.Logger; import org.keycloak.models.ApplicationModel; -import org.keycloak.models.ClaimMask; -import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionProvider; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.adapters.config.BaseRealmConfig; -import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.ScopeMappingRepresentation; -import org.keycloak.representations.idm.UserRoleMappingRepresentation; import java.net.URI; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Bill Burke @@ -41,112 +30,9 @@ public class ApplicationManager { public ApplicationManager() { } - - /** - * Does not create scope or role mappings! - * - * @param realm - * @param resourceRep - * @return - */ - public ApplicationModel createApplication(RealmModel realm, ApplicationRepresentation resourceRep) { - logger.debug("************ CREATE APPLICATION: {0}" + resourceRep.getName()); - ApplicationModel applicationModel = realm.addApplication(resourceRep.getName()); - if (resourceRep.isEnabled() != null) applicationModel.setEnabled(resourceRep.isEnabled()); - applicationModel.setManagementUrl(resourceRep.getAdminUrl()); - if (resourceRep.isSurrogateAuthRequired() != null) - applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired()); - applicationModel.setBaseUrl(resourceRep.getBaseUrl()); - if (resourceRep.isBearerOnly() != null) applicationModel.setBearerOnly(resourceRep.isBearerOnly()); - if (resourceRep.isPublicClient() != null) applicationModel.setPublicClient(resourceRep.isPublicClient()); - applicationModel.updateApplication(); - - if (resourceRep.getNotBefore() != null) { - applicationModel.setNotBefore(resourceRep.getNotBefore()); - } - - applicationModel.setSecret(resourceRep.getSecret()); - if (applicationModel.getSecret() == null) { - generateSecret(applicationModel); - } - - - if (resourceRep.getRedirectUris() != null) { - for (String redirectUri : resourceRep.getRedirectUris()) { - applicationModel.addRedirectUri(redirectUri); - } - } - if (resourceRep.getWebOrigins() != null) { - for (String webOrigin : resourceRep.getWebOrigins()) { - logger.debugv("Application: {0} webOrigin: {1}", resourceRep.getName(), webOrigin); - applicationModel.addWebOrigin(webOrigin); - } - } else { - // add origins from redirect uris - if (resourceRep.getRedirectUris() != null) { - Set origins = new HashSet(); - for (String redirectUri : resourceRep.getRedirectUris()) { - logger.info("add redirectUri to origin: " + redirectUri); - if (redirectUri.startsWith("http:")) { - URI uri = URI.create(redirectUri); - String origin = uri.getScheme() + "://" + uri.getHost(); - if (uri.getPort() != -1) { - origin += ":" + uri.getPort(); - } - logger.debugv("adding default application origin: {0}" , origin); - origins.add(origin); - } - } - if (origins.size() > 0) { - applicationModel.setWebOrigins(origins); - } - } - } - - if (resourceRep.getDefaultRoles() != null) { - applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles()); - } - - if (resourceRep.getClaims() != null) { - ClaimManager.setClaims(applicationModel, resourceRep.getClaims()); - } else { - applicationModel.setAllowedClaimsMask(ClaimMask.USERNAME); - } - - return applicationModel; - } - - public void createRoleMappings(ApplicationModel applicationModel, UserModel user, List roleNames) { - for (String roleName : roleNames) { - if (user == null) { - throw new RuntimeException("User not found"); - } - - RoleModel role = applicationModel.getRole(roleName.trim()); - if (role == null) { - role = applicationModel.addRole(roleName.trim()); - } - user.grantRole(role); - - } - } - - public void createScopeMappings(RealmModel realm, ApplicationModel applicationModel, List mappings) { - for (ScopeMappingRepresentation mapping : mappings) { - for (String roleString : mapping.getRoles()) { - RoleModel role = applicationModel.getRole(roleString.trim()); - if (role == null) { - role = applicationModel.addRole(roleString.trim()); - } - ClientModel client = realm.findClient(mapping.getClient()); - client.addScopeMapping(role); - } - } - } - public ApplicationModel createApplication(RealmModel realm, String name) { ApplicationModel app = realm.addApplication(name); - generateSecret(app); + KeycloakModelUtils.generateSecret(app); return app; } @@ -163,74 +49,6 @@ public class ApplicationManager { } } - public UserCredentialModel generateSecret(ApplicationModel app) { - UserCredentialModel secret = UserCredentialModel.generateSecret(); - app.setSecret(secret.getValue()); - return secret; - } - - public void updateApplication(ApplicationRepresentation rep, ApplicationModel resource) { - if (rep.getName() != null) resource.setName(rep.getName()); - if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled()); - if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly()); - if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient()); - if (rep.getAdminUrl() != null) resource.setManagementUrl(rep.getAdminUrl()); - if (rep.getBaseUrl() != null) resource.setBaseUrl(rep.getBaseUrl()); - if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); - resource.updateApplication(); - - if (rep.getNotBefore() != null) { - resource.setNotBefore(rep.getNotBefore()); - } - if (rep.getDefaultRoles() != null) { - resource.updateDefaultRoles(rep.getDefaultRoles()); - } - - List redirectUris = rep.getRedirectUris(); - if (redirectUris != null) { - resource.setRedirectUris(new HashSet(redirectUris)); - } - - List webOrigins = rep.getWebOrigins(); - if (webOrigins != null) { - resource.setWebOrigins(new HashSet(webOrigins)); - } - - if (rep.getClaims() != null) { - ClaimManager.setClaims(resource, rep.getClaims()); - } - } - - public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) { - ApplicationRepresentation rep = new ApplicationRepresentation(); - rep.setId(applicationModel.getId()); - rep.setName(applicationModel.getName()); - rep.setEnabled(applicationModel.isEnabled()); - rep.setAdminUrl(applicationModel.getManagementUrl()); - rep.setPublicClient(applicationModel.isPublicClient()); - rep.setBearerOnly(applicationModel.isBearerOnly()); - rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired()); - rep.setBaseUrl(applicationModel.getBaseUrl()); - rep.setNotBefore(applicationModel.getNotBefore()); - - Set redirectUris = applicationModel.getRedirectUris(); - if (redirectUris != null) { - rep.setRedirectUris(new LinkedList(redirectUris)); - } - - Set webOrigins = applicationModel.getWebOrigins(); - if (webOrigins != null) { - rep.setWebOrigins(new LinkedList(webOrigins)); - } - - if (!applicationModel.getDefaultRoles().isEmpty()) { - rep.setDefaultRoles(applicationModel.getDefaultRoles().toArray(new String[0])); - } - - return rep; - - } - @JsonPropertyOrder({"realm", "realm-public-key", "bearer-only", "auth-server-url", "ssl-not-required", "resource", "public-client", "credentials", "use-resource-role-mappings"}) diff --git a/services/src/main/java/org/keycloak/services/managers/ClaimManager.java b/services/src/main/java/org/keycloak/services/managers/ClaimManager.java deleted file mode 100755 index af1ab62d41..0000000000 --- a/services/src/main/java/org/keycloak/services/managers/ClaimManager.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.keycloak.services.managers; - -import org.keycloak.models.ClaimMask; -import org.keycloak.models.ClientModel; -import org.keycloak.representations.idm.ClaimRepresentation; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class ClaimManager { - public static void setClaims(ClientModel model, ClaimRepresentation rep) { - long mask = model.getAllowedClaimsMask(); - if (rep.getAddress()) { - mask |= ClaimMask.ADDRESS; - } else { - mask &= ~ClaimMask.ADDRESS; - } - if (rep.getEmail()) { - mask |= ClaimMask.EMAIL; - } else { - mask &= ~ClaimMask.EMAIL; - } - if (rep.getGender()) { - mask |= ClaimMask.GENDER; - } else { - mask &= ~ClaimMask.GENDER; - } - if (rep.getLocale()) { - mask |= ClaimMask.LOCALE; - } else { - mask &= ~ClaimMask.LOCALE; - } - if (rep.getName()) { - mask |= ClaimMask.NAME; - } else { - mask &= ~ClaimMask.NAME; - } - if (rep.getPhone()) { - mask |= ClaimMask.PHONE; - } else { - mask &= ~ClaimMask.PHONE; - } - if (rep.getPicture()) { - mask |= ClaimMask.PICTURE; - } else { - mask &= ~ClaimMask.PICTURE; - } - if (rep.getProfile()) { - mask |= ClaimMask.PROFILE; - } else { - mask &= ~ClaimMask.PROFILE; - } - if (rep.getUsername()) { - mask |= ClaimMask.USERNAME; - } else { - mask &= ~ClaimMask.USERNAME; - } - if (rep.getWebsite()) { - mask |= ClaimMask.WEBSITE; - } else { - mask &= ~ClaimMask.WEBSITE; - } - model.setAllowedClaimsMask(mask); - } -} diff --git a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java index df13eb4679..b8c1c886f1 100755 --- a/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java +++ b/services/src/main/java/org/keycloak/services/managers/OAuthClientManager.java @@ -2,22 +2,15 @@ package org.keycloak.services.managers; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonPropertyOrder; -import org.keycloak.models.ApplicationModel; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserSessionProvider; import org.keycloak.representations.adapters.config.BaseRealmConfig; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.OAuthClientRepresentation; import java.net.URI; import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Bill Burke @@ -26,35 +19,11 @@ import java.util.Set; public class OAuthClientManager { private RealmManager realmManager; - protected RealmModel realm; - - public OAuthClientManager(RealmModel realm) { - this.realm = realm; - } public OAuthClientManager(RealmManager realmManager) { this.realmManager = realmManager; } - public UserCredentialModel generateSecret(OAuthClientModel app) { - UserCredentialModel secret = UserCredentialModel.generateSecret(); - app.setSecret(secret.getValue()); - return secret; - } - - - public OAuthClientModel create(String name) { - OAuthClientModel model = realm.addOAuthClient(name); - generateSecret(model); - return model; - } - - public OAuthClientModel create(OAuthClientRepresentation rep) { - OAuthClientModel model = create(rep.getName()); - update(rep, model); - return model; - } - public boolean removeClient(RealmModel realm, OAuthClientModel client) { if (realm.removeOAuthClient(client.getId())) { UserSessionProvider sessions = realmManager.getSession().sessions(); @@ -67,58 +36,6 @@ public class OAuthClientManager { } } - public void update(OAuthClientRepresentation rep, OAuthClientModel model) { - if (rep.getName() != null) model.setClientId(rep.getName()); - if (rep.isEnabled() != null) model.setEnabled(rep.isEnabled()); - if (rep.isPublicClient() != null) model.setPublicClient(rep.isPublicClient()); - if (rep.isDirectGrantsOnly() != null) model.setDirectGrantsOnly(rep.isDirectGrantsOnly()); - if (rep.getClaims() != null) { - ClaimManager.setClaims(model, rep.getClaims()); - } - if (rep.getNotBefore() != null) { - model.setNotBefore(rep.getNotBefore()); - } - if (rep.getSecret() != null) model.setSecret(rep.getSecret()); - List redirectUris = rep.getRedirectUris(); - if (redirectUris != null) { - model.setRedirectUris(new HashSet(redirectUris)); - } - - List webOrigins = rep.getWebOrigins(); - if (webOrigins != null) { - model.setWebOrigins(new HashSet(webOrigins)); - } - - if (rep.getClaims() != null) { - ClaimManager.setClaims(model, rep.getClaims()); - } - - if (rep.getNotBefore() != null) { - model.setNotBefore(rep.getNotBefore()); - } - - } - - public static OAuthClientRepresentation toRepresentation(OAuthClientModel model) { - OAuthClientRepresentation rep = new OAuthClientRepresentation(); - rep.setId(model.getId()); - rep.setName(model.getClientId()); - rep.setEnabled(model.isEnabled()); - rep.setPublicClient(model.isPublicClient()); - rep.setDirectGrantsOnly(model.isDirectGrantsOnly()); - Set redirectUris = model.getRedirectUris(); - if (redirectUris != null) { - rep.setRedirectUris(new LinkedList(redirectUris)); - } - - Set webOrigins = model.getWebOrigins(); - if (webOrigins != null) { - rep.setWebOrigins(new LinkedList(webOrigins)); - } - rep.setNotBefore(model.getNotBefore()); - return rep; - } - @JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-not-required", "resource", "public-client", "credentials"}) public static class InstallationAdapterConfig extends BaseRealmConfig { 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 29cc8b2487..dcdc27695e 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -1,48 +1,25 @@ package org.keycloak.services.managers; import org.jboss.logging.Logger; +import org.keycloak.exportimport.util.ExportImportUtils; import org.keycloak.models.AccountRoles; import org.keycloak.models.AdminRoles; import org.keycloak.models.ApplicationModel; -import org.keycloak.models.AuthenticationLinkModel; -import org.keycloak.models.AuthenticationProviderModel; -import org.keycloak.models.ClientModel; import org.keycloak.Config; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelProvider; -import org.keycloak.models.OAuthClientModel; -import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.SocialLinkModel; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; -import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserSessionProvider; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.representations.idm.ApplicationRepresentation; -import org.keycloak.representations.idm.AuthenticationLinkRepresentation; -import org.keycloak.representations.idm.AuthenticationProviderRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.OAuthClientRepresentation; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.RealmAuditRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.ScopeMappingRepresentation; -import org.keycloak.representations.idm.SocialLinkRepresentation; -import org.keycloak.representations.idm.UserRepresentation; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; /** * Per request object @@ -128,10 +105,6 @@ public class RealmManager { adminConsole.addScopeMapping(adminRole); } - public String getMasterRealmAdminApplicationName(RealmModel realm) { - return realm.getName() + "-realm"; - } - public String getRealmAdminApplicationName(RealmModel realm) { return "realm-management"; } @@ -162,80 +135,6 @@ public class RealmManager { return removed; } - public void generateRealmKeys(RealmModel realm) { - KeyPair keyPair = null; - try { - keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - realm.setPrivateKey(keyPair.getPrivate()); - realm.setPublicKey(keyPair.getPublic()); - } - - public void updateRealm(RealmRepresentation rep, RealmModel realm) { - if (rep.getRealm() != null) { - realm.setName(rep.getRealm()); - } - if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled()); - if (rep.isSocial() != null) realm.setSocial(rep.isSocial()); - if (rep.isBruteForceProtected() != null) realm.setBruteForceProtected(rep.isBruteForceProtected()); - if (rep.getMaxFailureWaitSeconds() != null) realm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); - if (rep.getMinimumQuickLoginWaitSeconds() != null) realm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); - if (rep.getWaitIncrementSeconds() != null) realm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); - if (rep.getQuickLoginCheckMilliSeconds() != null) realm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); - if (rep.getMaxDeltaTimeSeconds() != null) realm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); - if (rep.getFailureFactor() != null) realm.setFailureFactor(rep.getFailureFactor()); - if (rep.isPasswordCredentialGrantAllowed() != null) realm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); - if (rep.isRegistrationAllowed() != null) realm.setRegistrationAllowed(rep.isRegistrationAllowed()); - if (rep.isRememberMe() != null) realm.setRememberMe(rep.isRememberMe()); - if (rep.isVerifyEmail() != null) realm.setVerifyEmail(rep.isVerifyEmail()); - if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); - if (rep.isUpdateProfileOnInitialSocialLogin() != null) - realm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin()); - if (rep.isSslNotRequired() != null) realm.setSslNotRequired((rep.isSslNotRequired())); - if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); - if (rep.getAccessCodeLifespanUserAction() != null) - realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); - if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore()); - if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); - if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); - if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); - if (rep.getRequiredCredentials() != null) { - realm.updateRequiredCredentials(rep.getRequiredCredentials()); - } - if (rep.getLoginTheme() != null) realm.setLoginTheme(rep.getLoginTheme()); - if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme()); - if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme()); - if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme()); - - if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy())); - - if (rep.getDefaultRoles() != null) { - realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()])); - } - - if (rep.getSmtpServer() != null) { - realm.setSmtpConfig(new HashMap(rep.getSmtpServer())); - } - - if (rep.getSocialProviders() != null) { - realm.setSocialConfig(new HashMap(rep.getSocialProviders())); - } - - if (rep.getLdapServer() != null) { - realm.setLdapServerConfig(new HashMap(rep.getLdapServer())); - } - if (rep.getAuthenticationProviders() != null) { - List authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders()); - realm.setAuthenticationProviders(authProviderModels); - } - - if ("GENERATE".equals(rep.getPublicKey())) { - generateRealmKeys(realm); - } - } - public void updateRealmAudit(RealmAuditRepresentation rep, RealmModel realm) { realm.setAuditEnabled(rep.isAuditEnabled()); realm.setAuditExpiration(rep.getAuditExpiration() != null ? rep.getAuditExpiration() : 0); @@ -262,7 +161,7 @@ public class RealmManager { ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session)); - ApplicationModel realmAdminApp = applicationManager.createApplication(adminRealm, getMasterRealmAdminApplicationName(realm)); + ApplicationModel realmAdminApp = applicationManager.createApplication(adminRealm, ExportImportUtils.getMasterRealmAdminApplicationName(realm)); realmAdminApp.setBearerOnly(true); realm.setMasterAdminApp(realmAdminApp); @@ -319,273 +218,7 @@ public class RealmManager { } public void importRealm(RealmRepresentation rep, RealmModel newRealm) { - newRealm.setName(rep.getRealm()); - if (rep.isEnabled() != null) newRealm.setEnabled(rep.isEnabled()); - if (rep.isSocial() != null) newRealm.setSocial(rep.isSocial()); - if (rep.isBruteForceProtected() != null) newRealm.setBruteForceProtected(rep.isBruteForceProtected()); - if (rep.getMaxFailureWaitSeconds() != null) newRealm.setMaxFailureWaitSeconds(rep.getMaxFailureWaitSeconds()); - if (rep.getMinimumQuickLoginWaitSeconds() != null) newRealm.setMinimumQuickLoginWaitSeconds(rep.getMinimumQuickLoginWaitSeconds()); - if (rep.getWaitIncrementSeconds() != null) newRealm.setWaitIncrementSeconds(rep.getWaitIncrementSeconds()); - if (rep.getQuickLoginCheckMilliSeconds() != null) newRealm.setQuickLoginCheckMilliSeconds(rep.getQuickLoginCheckMilliSeconds()); - if (rep.getMaxDeltaTimeSeconds() != null) newRealm.setMaxDeltaTimeSeconds(rep.getMaxDeltaTimeSeconds()); - if (rep.getFailureFactor() != null) newRealm.setFailureFactor(rep.getFailureFactor()); - - if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore()); - - if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); - else newRealm.setAccessTokenLifespan(300); - - if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); - else newRealm.setSsoSessionIdleTimeout(600); - if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); - else newRealm.setSsoSessionMaxLifespan(36000); - - if (rep.getAccessCodeLifespan() != null) newRealm.setAccessCodeLifespan(rep.getAccessCodeLifespan()); - else newRealm.setAccessCodeLifespan(60); - - if (rep.getAccessCodeLifespanUserAction() != null) - newRealm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); - else newRealm.setAccessCodeLifespanUserAction(300); - - if (rep.isSslNotRequired() != null) newRealm.setSslNotRequired(rep.isSslNotRequired()); - if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed()); - if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed()); - if (rep.isRememberMe() != null) newRealm.setRememberMe(rep.isRememberMe()); - if (rep.isVerifyEmail() != null) newRealm.setVerifyEmail(rep.isVerifyEmail()); - if (rep.isResetPasswordAllowed() != null) newRealm.setResetPasswordAllowed(rep.isResetPasswordAllowed()); - if (rep.isUpdateProfileOnInitialSocialLogin() != null) - newRealm.setUpdateProfileOnInitialSocialLogin(rep.isUpdateProfileOnInitialSocialLogin()); - if (rep.getPrivateKey() == null || rep.getPublicKey() == null) { - generateRealmKeys(newRealm); - } else { - newRealm.setPrivateKeyPem(rep.getPrivateKey()); - newRealm.setPublicKeyPem(rep.getPublicKey()); - } - if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme()); - if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme()); - if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme()); - if (rep.getEmailTheme() != null) newRealm.setEmailTheme(rep.getEmailTheme()); - - if (rep.getRequiredCredentials() != null) { - for (String requiredCred : rep.getRequiredCredentials()) { - addRequiredCredential(newRealm, requiredCred); - } - } else { - addRequiredCredential(newRealm, CredentialRepresentation.PASSWORD); - } - - if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy())); - - if (rep.getApplications() != null) { - Map appMap = createApplications(rep, newRealm); - } - - if (rep.getRoles() != null) { - if (rep.getRoles().getRealm() != null) { // realm roles - for (RoleRepresentation roleRep : rep.getRoles().getRealm()) { - createRole(newRealm, roleRep); - } - } - if (rep.getRoles().getApplication() != null) { - for (Map.Entry> entry : rep.getRoles().getApplication().entrySet()) { - ApplicationModel app = newRealm.getApplicationByName(entry.getKey()); - if (app == null) { - throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey()); - } - for (RoleRepresentation roleRep : entry.getValue()) { - RoleModel role = app.addRole(roleRep.getName()); - role.setDescription(roleRep.getDescription()); - } - } - } - // now that all roles are created, re-iterate and set up composites - if (rep.getRoles().getRealm() != null) { // realm roles - for (RoleRepresentation roleRep : rep.getRoles().getRealm()) { - RoleModel role = newRealm.getRole(roleRep.getName()); - addComposites(role, roleRep, newRealm); - } - } - if (rep.getRoles().getApplication() != null) { - for (Map.Entry> entry : rep.getRoles().getApplication().entrySet()) { - ApplicationModel app = newRealm.getApplicationByName(entry.getKey()); - if (app == null) { - throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey()); - } - for (RoleRepresentation roleRep : entry.getValue()) { - RoleModel role = app.getRole(roleRep.getName()); - addComposites(role, roleRep, newRealm); - } - } - } - } - - - if (rep.getDefaultRoles() != null) { - for (String roleString : rep.getDefaultRoles()) { - newRealm.addDefaultRole(roleString.trim()); - } - } - - if (rep.getOauthClients() != null) { - createOAuthClients(rep, newRealm); - } - - - // Now that all possible roles and applications are created, create scope mappings - - Map appMap = newRealm.getApplicationNameMap(); - - if (rep.getApplicationScopeMappings() != null) { - ApplicationManager manager = new ApplicationManager(this); - for (Map.Entry> entry : rep.getApplicationScopeMappings().entrySet()) { - ApplicationModel app = appMap.get(entry.getKey()); - if (app == null) { - throw new RuntimeException("Unable to find application role mappings for app: " + entry.getKey()); - } - manager.createScopeMappings(newRealm, app, entry.getValue()); - } - } - - if (rep.getScopeMappings() != null) { - for (ScopeMappingRepresentation scope : rep.getScopeMappings()) { - for (String roleString : scope.getRoles()) { - RoleModel role = newRealm.getRole(roleString.trim()); - if (role == null) { - role = newRealm.addRole(roleString.trim()); - } - ClientModel client = newRealm.findClient(scope.getClient()); - client.addScopeMapping(role); - } - - } - } - - if (rep.getSmtpServer() != null) { - newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer())); - } - - if (rep.getSocialProviders() != null) { - newRealm.setSocialConfig(new HashMap(rep.getSocialProviders())); - } - if (rep.getLdapServer() != null) { - newRealm.setLdapServerConfig(new HashMap(rep.getLdapServer())); - } - - if (rep.getAuthenticationProviders() != null) { - List authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders()); - newRealm.setAuthenticationProviders(authProviderModels); - } else { - List authProviderModels = Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER); - newRealm.setAuthenticationProviders(authProviderModels); - } - - // create users and their role mappings and social mappings - - if (rep.getUsers() != null) { - for (UserRepresentation userRep : rep.getUsers()) { - UserModel user = createUser(newRealm, userRep, appMap); - } - } - } - - public void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) { - if (roleRep.getComposites() == null) return; - if (roleRep.getComposites().getRealm() != null) { - for (String roleStr : roleRep.getComposites().getRealm()) { - RoleModel realmRole = realm.getRole(roleStr); - if (realmRole == null) throw new RuntimeException("Unable to find composite realm role: " + roleStr); - role.addCompositeRole(realmRole); - } - } - if (roleRep.getComposites().getApplication() != null) { - for (Map.Entry> entry : roleRep.getComposites().getApplication().entrySet()) { - ApplicationModel app = realm.getApplicationByName(entry.getKey()); - if (app == null) { - throw new RuntimeException("App doesn't exist in role definitions: " + roleRep.getName()); - } - for (String roleStr : entry.getValue()) { - RoleModel appRole = app.getRole(roleStr); - if (appRole == null) throw new RuntimeException("Unable to find composite app role: " + roleStr); - role.addCompositeRole(appRole); - } - - } - - } - - } - - public void createRole(RealmModel newRealm, RoleRepresentation roleRep) { - RoleModel role = newRealm.addRole(roleRep.getName()); - if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription()); - } - - public void createRole(RealmModel newRealm, ApplicationModel app, RoleRepresentation roleRep) { - RoleModel role = app.addRole(roleRep.getName()); - if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription()); - } - - - public UserModel createUser(RealmModel newRealm, UserRepresentation userRep, Map appMap) { - UserModel user = newRealm.addUser(userRep.getId(), userRep.getUsername(), false); - user.setEnabled(userRep.isEnabled()); - user.setEmail(userRep.getEmail()); - user.setFirstName(userRep.getFirstName()); - user.setLastName(userRep.getLastName()); - if (userRep.getAttributes() != null) { - for (Map.Entry entry : userRep.getAttributes().entrySet()) { - user.setAttribute(entry.getKey(), entry.getValue()); - } - } - if (userRep.getRequiredActions() != null) { - for (String requiredAction : userRep.getRequiredActions()) { - user.addRequiredAction(RequiredAction.valueOf(requiredAction)); - } - } - if (userRep.getCredentials() != null) { - for (CredentialRepresentation cred : userRep.getCredentials()) { - UserCredentialModel credential = fromRepresentation(cred); - user.updateCredential(credential); - } - } - if (userRep.getAuthenticationLink() != null) { - AuthenticationLinkRepresentation link = userRep.getAuthenticationLink(); - AuthenticationLinkModel authLink = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId()); - user.setAuthenticationLink(authLink); - } - if (userRep.getSocialLinks() != null) { - for (SocialLinkRepresentation socialLink : userRep.getSocialLinks()) { - SocialLinkModel mappingModel = new SocialLinkModel(socialLink.getSocialProvider(), socialLink.getSocialUserId(), socialLink.getSocialUsername()); - newRealm.addSocialLink(user, mappingModel); - } - } - if (userRep.getRealmRoles() != null) { - for (String roleString : userRep.getRealmRoles()) { - RoleModel role = newRealm.getRole(roleString.trim()); - if (role == null) { - role = newRealm.addRole(roleString.trim()); - } - user.grantRole(role); - } - } - if (userRep.getApplicationRoles() != null) { - ApplicationManager manager = new ApplicationManager(this); - for (Map.Entry> entry : userRep.getApplicationRoles().entrySet()) { - ApplicationModel app = appMap.get(entry.getKey()); - if (app == null) { - throw new RuntimeException("Unable to find application role mappings for app: " + entry.getKey()); - } - manager.createRoleMappings(app, user, entry.getValue()); - } - } - return user; - } - - public static UserCredentialModel fromRepresentation(CredentialRepresentation cred) { - UserCredentialModel credential = new UserCredentialModel(); - credential.setType(cred.getType()); - credential.setValue(cred.getValue()); - return credential; + RepresentationToModel.importRealm(rep, newRealm); } /** @@ -606,37 +239,4 @@ public class RealmManager { return realmModel.searchForUser(searchString.trim()); } - public void addRequiredCredential(RealmModel newRealm, String requiredCred) { - newRealm.addRequiredCredential(requiredCred); - } - - protected Map createApplications(RealmRepresentation rep, RealmModel realm) { - Map appMap = new HashMap(); - ApplicationManager manager = new ApplicationManager(this); - for (ApplicationRepresentation resourceRep : rep.getApplications()) { - ApplicationModel app = manager.createApplication(realm, resourceRep); - appMap.put(app.getName(), app); - } - return appMap; - } - - protected void createOAuthClients(RealmRepresentation realmRep, RealmModel realm) { - OAuthClientManager manager = new OAuthClientManager(realm); - for (OAuthClientRepresentation rep : realmRep.getOauthClients()) { - OAuthClientModel app = manager.create(rep); - } - } - - protected List convertAuthenticationProviders(List authenticationProviders) { - List result = new ArrayList(); - - for (AuthenticationProviderRepresentation representation : authenticationProviders) { - AuthenticationProviderModel model = new AuthenticationProviderModel(representation.getProviderName(), - representation.isPasswordUpdateSupported(), representation.getConfig()); - result.add(model); - } - return result; - } - - } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 68fe12fa68..19c7c58e47 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -53,7 +53,7 @@ import org.keycloak.services.ForbiddenException; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.OAuthRedirect; 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 c868e3f336..f4094af6c3 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -7,7 +7,7 @@ import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config; import org.keycloak.SkeletonKeyContextResolver; -import org.keycloak.exportimport.ExportImportProvider; +import org.keycloak.exportimport.ExportImportManager; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; @@ -24,7 +24,6 @@ import org.keycloak.services.scheduled.ScheduledTaskRunner; import org.keycloak.services.util.JsonConfigProvider; import org.keycloak.timer.TimerProvider; import org.keycloak.util.JsonSerialization; -import org.keycloak.util.ProviderLoader; import javax.servlet.ServletContext; import javax.ws.rs.core.Application; @@ -38,7 +37,6 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; @@ -81,7 +79,7 @@ public class KeycloakApplication extends Application { classes.add(JsResource.class); classes.add(WelcomeResource.class); - checkExportImportProvider(); + new ExportImportManager().checkExportImport(this.sessionFactory); setupDefaultRealm(context.getContextPath()); @@ -235,16 +233,4 @@ public class KeycloakApplication extends Application { } } - protected void checkExportImportProvider() { - Iterator providers = ProviderLoader.load(ExportImportProvider.class).iterator(); - - if (providers.hasNext()) { - ExportImportProvider exportImport = providers.next(); - exportImport.checkExportImport(sessionFactory); - } else { - log.warn("No ExportImportProvider found!"); - } - } - - } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java index 919fcce413..530811211a 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java @@ -10,13 +10,15 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.adapters.action.SessionStats; import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.services.managers.ApplicationManager; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.KeycloakApplication; @@ -97,7 +99,7 @@ public class ApplicationResource { ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session)); try { - applicationManager.updateApplication(rep, application); + RepresentationToModel.updateApplication(rep, application); return Response.noContent().build(); } catch (ModelDuplicateException e) { return Flows.errors().exists("Application " + rep.getName() + " already exists"); @@ -116,8 +118,7 @@ public class ApplicationResource { public ApplicationRepresentation getApplication() { auth.requireView(); - ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session)); - return applicationManager.toRepresentation(application); + return ModelToRepresentation.toRepresentation(application); } @@ -184,7 +185,7 @@ public class ApplicationResource { auth.requireManage(); logger.debug("regenerateSecret"); - UserCredentialModel cred = new ApplicationManager().generateSecret(application); + UserCredentialModel cred = KeycloakModelUtils.generateSecret(application); CredentialRepresentation rep = ModelToRepresentation.toRepresentation(cred); return rep; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java index f83bfc79f3..c3038473e9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java @@ -8,6 +8,8 @@ import org.keycloak.models.ApplicationModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.services.managers.ApplicationManager; import org.keycloak.services.managers.RealmManager; @@ -60,12 +62,11 @@ public class ApplicationsResource { List rep = new ArrayList(); List applicationModels = realm.getApplications(); - ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session)); boolean view = auth.hasView(); for (ApplicationModel applicationModel : applicationModels) { if (view) { - rep.add(resourceManager.toRepresentation(applicationModel)); + rep.add(ModelToRepresentation.toRepresentation(applicationModel)); } else { ApplicationRepresentation app = new ApplicationRepresentation(); app.setName(applicationModel.getName()); @@ -87,9 +88,8 @@ public class ApplicationsResource { public Response createApplication(final @Context UriInfo uriInfo, final ApplicationRepresentation rep) { auth.requireManage(); - ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session)); try { - ApplicationModel applicationModel = resourceManager.createApplication(realm, rep); + ApplicationModel applicationModel = RepresentationToModel.createApplication(realm, rep); return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getName()).build()).build(); } catch (ModelDuplicateException e) { return Flows.errors().exists("Application " + rep.getName() + " already exists"); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClaimResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClaimResource.java index 9a9ebaa389..0353bad3b7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClaimResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClaimResource.java @@ -1,9 +1,9 @@ package org.keycloak.services.resources.admin; import org.keycloak.models.ClientModel; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClaimRepresentation; -import org.keycloak.services.managers.ClaimManager; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -47,6 +47,6 @@ public class ClaimResource { @Consumes(MediaType.APPLICATION_JSON) public void updateClaims(ClaimRepresentation rep) { auth.requireManage(); - ClaimManager.setClaims(model, rep); + RepresentationToModel.setClaims(model, rep); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java index d8b6d652e3..e7722d24d0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java @@ -8,9 +8,10 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.OAuthClientRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.OAuthClientManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; @@ -82,9 +83,8 @@ public class OAuthClientResource { public Response update(final OAuthClientRepresentation rep) { auth.requireManage(); - OAuthClientManager manager = new OAuthClientManager(realm); try { - manager.update(rep, oauthClient); + RepresentationToModel.updateOAuthClient(rep, oauthClient); return Response.noContent().build(); } catch (ModelDuplicateException e) { return Flows.errors().exists("Client " + rep.getName() + " already exists"); @@ -102,7 +102,7 @@ public class OAuthClientResource { public OAuthClientRepresentation getOAuthClient() { auth.requireView(); - return OAuthClientManager.toRepresentation(oauthClient); + return ModelToRepresentation.toRepresentation(oauthClient); } /** @@ -118,7 +118,7 @@ public class OAuthClientResource { public String getInstallation() throws IOException { auth.requireView(); - OAuthClientManager manager = new OAuthClientManager(realm); + OAuthClientManager manager = new OAuthClientManager(); Object rep = manager.toInstallationRepresentation(realm, oauthClient, getApplication().getBaseUri(uriInfo)); // TODO Temporary solution to pretty-print diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java index f80a8c95a4..dd081f03bf 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java @@ -8,6 +8,8 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.OAuthClientRepresentation; import org.keycloak.services.managers.OAuthClientManager; import org.keycloak.services.resources.flows.Flows; @@ -65,7 +67,7 @@ public class OAuthClientsResource { boolean view = auth.hasView(); for (OAuthClientModel oauth : oauthModels) { if (view) { - rep.add(OAuthClientManager.toRepresentation(oauth)); + rep.add(ModelToRepresentation.toRepresentation(oauth)); } else { OAuthClientRepresentation client = new OAuthClientRepresentation(); client.setName(oauth.getClientId()); @@ -87,9 +89,8 @@ public class OAuthClientsResource { public Response createOAuthClient(final @Context UriInfo uriInfo, final OAuthClientRepresentation rep) { auth.requireManage(); - OAuthClientManager resourceManager = new OAuthClientManager(realm); try { - OAuthClientModel oauth = resourceManager.create(rep); + OAuthClientModel oauth = RepresentationToModel.createOAuthClient(rep, realm); return Response.created(uriInfo.getAbsolutePathBuilder().path(oauth.getId()).build()).build(); } catch (ModelDuplicateException e) { return Flows.errors().exists("Client " + rep.getName() + " already exists"); 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 b9243491e8..79a0bcd1b4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -13,11 +13,12 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.adapters.action.SessionStats; import org.keycloak.representations.idm.RealmAuditRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.LDAPConnectionTestManager; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.TokenManager; @@ -138,7 +139,7 @@ public class RealmAdminResource { logger.debug("updating realm: " + realm.getName()); try { - new RealmManager(session).updateRealm(rep, realm); + RepresentationToModel.updateRealm(rep, realm); return Response.noContent().build(); } catch (ModelDuplicateException e) { return Flows.errors().exists("Realm " + rep.getRealm() + " already exists"); 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 35f817604d..de7f157802 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 @@ -14,7 +14,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.ForbiddenException; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.resources.KeycloakApplication; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index d1bb14980c..cf433067b1 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -2,13 +2,12 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.models.Constants; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.resources.flows.Flows; import javax.ws.rs.Consumes; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java index 8ef17d8e27..18599de0e0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java @@ -5,7 +5,7 @@ import org.keycloak.models.ApplicationModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import java.util.Collections; import java.util.HashSet; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java index 92b129c780..96a225b0d7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java @@ -7,12 +7,10 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ApplicationMappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; -import org.keycloak.services.managers.RealmManager; +import org.keycloak.models.utils.ModelToRepresentation; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index e86781d6e7..32ccf7f854 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -17,6 +17,7 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.idm.ApplicationMappingsRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -26,7 +27,7 @@ import org.keycloak.representations.idm.SocialLinkRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.services.managers.AccessCodeEntry; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.TokenManager; @@ -760,7 +761,7 @@ public class UsersResource { throw new BadRequestException("No password provided"); } - UserCredentialModel cred = RealmManager.fromRepresentation(pass); + UserCredentialModel cred = RepresentationToModel.convertCredential(pass); user.updateCredential(cred); user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); } diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index acb145b71e..6507a5e130 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -289,7 +289,17 @@ org.keycloak - keycloak-export-import-impl + keycloak-export-import-dir + ${project.version} + + + org.keycloak + keycloak-export-import-single-file + ${project.version} + + + org.keycloak + keycloak-export-import-zip ${project.version} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java index 5c7bc52279..f33a43e063 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java @@ -33,6 +33,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.AccessToken; import org.keycloak.services.managers.ApplicationManager; import org.keycloak.services.managers.RealmManager; @@ -59,7 +60,7 @@ public class CompositeRoleTest { @Override protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) { RealmModel realm = manager.createRealm("Test"); - manager.generateRealmKeys(realm); + KeycloakModelUtils.generateRealmKeys(realm); realmPublicKey = realm.getPublicKey(); realm.setSsoSessionIdleTimeout(3000); realm.setAccessTokenLifespan(10000); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java new file mode 100644 index 0000000000..688b54ce33 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -0,0 +1,269 @@ +package org.keycloak.testsuite.exportimport; + +import java.io.File; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.Config; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.dir.DirExportProvider; +import org.keycloak.exportimport.dir.DirExportProviderFactory; +import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; +import org.keycloak.exportimport.zip.ZipExportProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelProvider; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testutils.KeycloakServer; + +/** + * @author Marek Posolda + */ +public class ExportImportTest { + + @ClassRule + public static KeycloakRule keycloakRule = new KeycloakRule( new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + addUser(appRealm, "user1", "password"); + addUser(appRealm, "user2", "password"); + addUser(appRealm, "user3", "password"); + addUser(adminstrationRealm, "admin2", "admin2"); + } + + }); + + @Test + public void testDirFullExportImport() throws Throwable { + ExportImportConfig.setProvider(DirExportProviderFactory.PROVIDER_ID); + String targetDirPath = getExportImportTestDirectory() + File.separator + "dirExport"; + DirExportProvider.recursiveDeleteDir(new File(targetDirPath)); + ExportImportConfig.setDir(targetDirPath); + ExportImportConfig.setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE); + + testFullExportImport(); + + // There should be 4 files in target directory (2 realm, 2 user) + Assert.assertEquals(4, new File(targetDirPath).listFiles().length); + } + + @Test + public void testDirRealmExportImport() throws Throwable { + ExportImportConfig.setProvider(DirExportProviderFactory.PROVIDER_ID); + String targetDirPath = getExportImportTestDirectory() + File.separator + "dirRealmExport"; + DirExportProvider.recursiveDeleteDir(new File(targetDirPath)); + ExportImportConfig.setDir(targetDirPath); + ExportImportConfig.setUsersPerFile(3); + + testRealmExportImport(); + + // There should be 3 files in target directory (1 realm, 2 user) + Assert.assertEquals(3, new File(targetDirPath).listFiles().length); + } + + @Test + public void testSingleFileFullExportImport() throws Throwable { + ExportImportConfig.setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + String targetFilePath = getExportImportTestDirectory() + File.separator + "singleFile-full.json"; + ExportImportConfig.setFile(targetFilePath); + + testFullExportImport(); + } + + @Test + public void testSingleFileRealmExportImport() throws Throwable { + ExportImportConfig.setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + String targetFilePath = getExportImportTestDirectory() + File.separator + "singleFile-realm.json"; + ExportImportConfig.setFile(targetFilePath); + + testRealmExportImport(); + } + + @Test + public void testZipFullExportImport() throws Throwable { + ExportImportConfig.setProvider(ZipExportProviderFactory.PROVIDER_ID); + String zipFilePath = getExportImportTestDirectory() + File.separator + "export-full.zip"; + new File(zipFilePath).delete(); + ExportImportConfig.setZipFile(zipFilePath); + ExportImportConfig.setZipPassword("encPassword"); + ExportImportConfig.setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE); + + testFullExportImport(); + } + + @Test + public void testZipRealmExportImport() throws Throwable { + ExportImportConfig.setProvider(ZipExportProviderFactory.PROVIDER_ID); + String zipFilePath = getExportImportTestDirectory() + File.separator + "export-realm.zip"; + new File(zipFilePath).delete(); + ExportImportConfig.setZipFile(zipFilePath); + ExportImportConfig.setZipPassword("encPassword"); + ExportImportConfig.setUsersPerFile(3); + + testRealmExportImport(); + } + + private void testFullExportImport() { + ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT); + ExportImportConfig.setRealmName(null); + + // Restart server, which triggers export + keycloakRule.restartServer(); + + // Delete some realm (and some data in admin realm) + KeycloakSession session = keycloakRule.startSession(); + try { + ModelProvider model = session.getModel(); + model.removeRealm(model.getRealmByName("test").getId()); + Assert.assertEquals(1, model.getRealms().size()); + + RealmModel master = model.getRealmByName(Config.getAdminRealm()); + master.removeUser("admin2"); + + assertNotAuthenticated(model, Config.getAdminRealm(), "admin2", "admin2"); + assertNotAuthenticated(model, "test", "test-user@localhost", "password"); + assertNotAuthenticated(model, "test", "user1", "password"); + assertNotAuthenticated(model, "test", "user2", "password"); + assertNotAuthenticated(model, "test", "user3", "password"); + } finally { + keycloakRule.stopSession(session, true); + } + + // Configure import + ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT); + + // Restart server, which triggers import + keycloakRule.restartServer(); + + // Ensure data are imported back + session = keycloakRule.startSession(); + try { + ModelProvider model = session.getModel(); + Assert.assertEquals(2, model.getRealms().size()); + + assertAuthenticated(model, Config.getAdminRealm(), "admin2", "admin2"); + assertAuthenticated(model, "test", "test-user@localhost", "password"); + assertAuthenticated(model, "test", "user1", "password"); + assertAuthenticated(model, "test", "user2", "password"); + assertAuthenticated(model, "test", "user3", "password"); + + } finally { + keycloakRule.stopSession(session, true); + } + } + + private void testRealmExportImport() { + ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT); + ExportImportConfig.setRealmName("test"); + + // Restart server, which triggers export + keycloakRule.restartServer(); + + // Delete some realm (and some data in admin realm) + KeycloakSession session = keycloakRule.startSession(); + try { + ModelProvider model = session.getModel(); + model.removeRealm(model.getRealmByName("test").getId()); + Assert.assertEquals(1, model.getRealms().size()); + + RealmModel master = model.getRealmByName(Config.getAdminRealm()); + master.removeUser("admin2"); + + assertNotAuthenticated(model, Config.getAdminRealm(), "admin2", "admin2"); + assertNotAuthenticated(model, "test", "test-user@localhost", "password"); + assertNotAuthenticated(model, "test", "user1", "password"); + assertNotAuthenticated(model, "test", "user2", "password"); + assertNotAuthenticated(model, "test", "user3", "password"); + } finally { + keycloakRule.stopSession(session, true); + } + + // Configure import + ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT); + + // Restart server, which triggers import + keycloakRule.restartServer(); + + // Ensure data are imported back, but just for "test" realm + session = keycloakRule.startSession(); + try { + ModelProvider model = session.getModel(); + Assert.assertEquals(2, model.getRealms().size()); + + assertNotAuthenticated(model, Config.getAdminRealm(), "admin2", "admin2"); + assertAuthenticated(model, "test", "test-user@localhost", "password"); + assertAuthenticated(model, "test", "user1", "password"); + assertAuthenticated(model, "test", "user2", "password"); + assertAuthenticated(model, "test", "user3", "password"); + + addUser(model.getRealmByName(Config.getAdminRealm()), "admin2", "admin2"); + } finally { + keycloakRule.stopSession(session, true); + } + } + + private void assertAuthenticated(ModelProvider model, String realmName, String username, String password) { + RealmModel realm = model.getRealmByName(realmName); + if (realm == null) { + Assert.fail("realm " + realmName + " not found"); + } + + UserModel user = realm.getUser(username); + if (user == null) { + Assert.fail("user " + username + " not found"); + } + + Assert.assertTrue(realm.validatePassword(user, password)); + } + + private void assertNotAuthenticated(ModelProvider model, String realmName, String username, String password) { + RealmModel realm = model.getRealmByName(realmName); + if (realm == null) { + return; + } + + UserModel user = realm.getUser(username); + if (user == null) { + return; + } + + Assert.assertFalse(realm.validatePassword(user, password)); + } + + private static void addUser(RealmModel appRealm, String username, String password) { + UserModel user = appRealm.addUser(username); + user.setEmail(username + "@test.com"); + user.setEnabled(true); + + UserCredentialModel creds = new UserCredentialModel(); + creds.setType(CredentialRepresentation.PASSWORD); + creds.setValue(password); + user.updateCredential(creds); + } + + private String getExportImportTestDirectory() { + String dirPath = null; + String relativeDirExportImportPath = "testsuite" + File.separator + "integration" + File.separator + "target" + File.separator + "export-import"; + + if (System.getProperties().containsKey("maven.home")) { + dirPath = System.getProperty("user.dir").replaceFirst("testsuite.integration.*", relativeDirExportImportPath); + } else { + for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) { + if (c.contains(File.separator + "testsuite" + File.separator + "integration")) { + dirPath = c.replaceFirst("testsuite.integration.*", relativeDirExportImportPath); + } + } + } + + String absolutePath = new File(dirPath).getAbsolutePath(); + return absolutePath; + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index 0ba9ed3048..f79358f56f 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -12,7 +12,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.services.managers.ModelToRepresentation; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testutils.KeycloakServer; import org.keycloak.util.JsonSerialization; @@ -146,4 +146,13 @@ public abstract class AbstractKeycloakRule extends ExternalResource { } session.close(); } + + public void restartServer() { + try { + server.stop(); + server.start(); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } } diff --git a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java index 4b89245ecb..4077f6b58e 100644 --- a/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java +++ b/testsuite/tools/src/main/java/org/keycloak/test/tools/PerfTools.java @@ -1,7 +1,7 @@ package org.keycloak.test.tools; import org.keycloak.exportimport.ExportImportConfig; -import org.keycloak.exportimport.ExportImportProvider; +import org.keycloak.exportimport.ExportProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; @@ -231,10 +231,10 @@ public class PerfTools { ExportImportConfig.setProvider("dir"); ExportImportConfig.setDir(dir); - Iterator providers = ProviderLoader.load(ExportImportProvider.class).iterator(); + Iterator providers = ProviderLoader.load(ExportProvider.class).iterator(); if (providers.hasNext()) { - ExportImportProvider exportImport = providers.next(); + ExportProvider exportImport = providers.next(); exportImport.checkExportImport(sessionFactory); } else { throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);