user provider refactor complete

This commit is contained in:
Bill Burke committed 2014-07-15 15:25:06 -04:00
commit 484a4c1454
199 files changed
+3712 -3088

No files matched your search

@@ -0,0 +1,107 @@
package org.keycloak.exportimport;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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);
}
}
@@ -0,0 +1,63 @@
package org.keycloak.exportimport;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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(sessionFactory);
} else {
logger.infof("Export of realm '%s' requested", realmName);
exportProvider.exportRealm(sessionFactory, 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(sessionFactory, strategy);
} else {
logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString());
importProvider.importRealm(sessionFactory, realmName, strategy);
}
logger.info("Import finished successfully");
}
} catch (Throwable ioe) {
logger.error("Error during export/import", ioe);
} finally {
session.close();
}
}
}
}
@@ -1,12 +0,0 @@
package org.keycloak.exportimport;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportImportProvider {
void checkExportImport(KeycloakSessionFactory sessionFactory);
}
@@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportProvider extends Provider {
void exportModel(KeycloakSessionFactory factory) throws IOException;
void exportRealm(KeycloakSessionFactory factory, String realmName) throws IOException;
}
@@ -0,0 +1,9 @@
package org.keycloak.exportimport;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportProviderFactory extends ProviderFactory<ExportProvider> {
}
@@ -0,0 +1,26 @@
package org.keycloak.exportimport;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExportSpi implements Spi {
@Override
public String getName() {
return "export";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ExportProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ExportProviderFactory.class;
}
}
@@ -0,0 +1,17 @@
package org.keycloak.exportimport;
import java.io.IOException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ImportProvider extends Provider {
void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException;
void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException;
}
@@ -0,0 +1,9 @@
package org.keycloak.exportimport;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ImportProviderFactory extends ProviderFactory<ImportProvider> {
}
@@ -0,0 +1,26 @@
package org.keycloak.exportimport;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ImportSpi implements Spi {
@Override
public String getName() {
return "import";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ImportProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ImportProviderFactory.class;
}
}
@@ -0,0 +1,10 @@
package org.keycloak.exportimport;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public enum Strategy {
IGNORE_EXISTING, // Ignore existing user entries
OVERWRITE_EXISTING // Overwrite existing user entries
}
@@ -0,0 +1,15 @@
package org.keycloak.exportimport.util;
import java.io.IOException;
import org.keycloak.models.KeycloakSession;
/**
* Task to be executed inside transaction
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportImportJob {
public void run(KeycloakSession session) throws IOException;
}
@@ -0,0 +1,47 @@
package org.keycloak.exportimport.util;
import java.io.IOException;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExportImportUtils {
/**
* Wrap given runnable job into KeycloakTransaction.
*
* @param factory
* @param job
*/
public static void runJobInTransaction(KeycloakSessionFactory factory, ExportImportJob job) throws IOException {
KeycloakSession session = factory.create();
KeycloakTransaction tx = session.getTransaction();
try {
tx.begin();
job.run(session);
if (tx.isActive()) {
if (tx.getRollbackOnly()) {
tx.rollback();
} else {
tx.commit();
}
}
} finally {
if (tx.isActive()) {
tx.rollback();
}
session.close();
}
}
public static String getMasterRealmAdminApplicationName(RealmModel realm) {
return realm.getName() + "-realm";
}
}
@@ -0,0 +1,366 @@
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.KeycloakSession;
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExportUtils {
public static RealmRepresentation exportRealm(KeycloakSession session, 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<String>(realm.getAuditListeners()));
}
// Applications
List<ApplicationModel> applications = realm.getApplications();
List<ApplicationRepresentation> appReps = new ArrayList<ApplicationRepresentation>();
for (ApplicationModel app : applications) {
ApplicationRepresentation appRep = exportApplication(app);
appReps.add(appRep);
}
rep.setApplications(appReps);
// OAuth clients
List<OAuthClientModel> oauthClients = realm.getOAuthClients();
List<OAuthClientRepresentation> oauthClientReps = new ArrayList<OAuthClientRepresentation>();
for (OAuthClientModel oauthClient : oauthClients) {
OAuthClientRepresentation clientRep = ModelToRepresentation.toRepresentation(oauthClient);
oauthClientReps.add(clientRep);
}
rep.setOauthClients(oauthClientReps);
// Roles
List<RoleRepresentation> realmRoleReps = null;
Map<String, List<RoleRepresentation>> appRolesReps = new HashMap<String, List<RoleRepresentation>>();
Set<RoleModel> realmRoles = realm.getRoles();
if (realmRoles != null && realmRoles.size() > 0) {
realmRoleReps = exportRoles(realmRoles);
}
for (ApplicationModel app : applications) {
Set<RoleModel> currentAppRoles = app.getRoles();
List<RoleRepresentation> 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<ClientModel> allClients = new ArrayList<ClientModel>(applications);
allClients.addAll(realm.getOAuthClients());
Map<String, List<ScopeMappingRepresentation>> appScopeReps = new HashMap<String, List<ScopeMappingRepresentation>>();
for (ClientModel client : allClients) {
Set<RoleModel> 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<ScopeMappingRepresentation> currentAppScopes = appScopeReps.get(appName);
if (currentAppScopes == null) {
currentAppScopes = new ArrayList<ScopeMappingRepresentation>();
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<UserModel> allUsers = session.users().getUsers(realm);
List<UserRepresentation> users = new ArrayList<UserRepresentation>();
for (UserModel user : allUsers) {
UserRepresentation userRep = exportUser(session, 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<RoleRepresentation> exportRoles(Collection<RoleModel> roles) {
List<RoleRepresentation> roleReps = new ArrayList<RoleRepresentation>();
for (RoleModel role : roles) {
RoleRepresentation roleRep = exportRole(role);
roleReps.add(roleRep);
}
return roleReps;
}
public static List<String> getRoleNames(Collection<RoleModel> roles) {
List<String> roleNames = new ArrayList<String>();
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<RoleModel> composites = role.getComposites();
if (composites != null && composites.size() > 0) {
Set<String> compositeRealmRoles = null;
Map<String, List<String>> compositeAppRoles = null;
for (RoleModel composite : composites) {
RoleContainerModel crContainer = composite.getContainer();
if (crContainer instanceof RealmModel) {
if (compositeRealmRoles == null) {
compositeRealmRoles = new HashSet<String>();
}
compositeRealmRoles.add(composite.getName());
} else {
if (compositeAppRoles == null) {
compositeAppRoles = new HashMap<String, List<String>>();
}
ApplicationModel app = (ApplicationModel)crContainer;
String appName = app.getName();
List<String> currentAppComposites = compositeAppRoles.get(appName);
if (currentAppComposites == null) {
currentAppComposites = new ArrayList<String>();
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(KeycloakSession session, 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<SocialLinkModel> socialLinks = session.users().getSocialLinks(user, realm);
List<SocialLinkRepresentation> socialLinkReps = new ArrayList<SocialLinkRepresentation>();
for (SocialLinkModel socialLink : socialLinks) {
SocialLinkRepresentation socialLinkRep = exportSocialLink(socialLink);
socialLinkReps.add(socialLinkRep);
}
if (socialLinkReps.size() > 0) {
userRep.setSocialLinks(socialLinkReps);
}
// Role mappings
Set<RoleModel> roles = user.getRoleMappings();
List<String> realmRoleNames = new ArrayList<String>();
Map<String, List<String>> appRoleNames = new HashMap<String, List<String>>();
for (RoleModel role : roles) {
if (role.getContainer() instanceof RealmModel) {
realmRoleNames.add(role.getName());
} else {
ApplicationModel app = (ApplicationModel)role.getContainer();
String appName = app.getName();
List<String> currentAppRoles = appRoleNames.get(appName);
if (currentAppRoles == null) {
currentAppRoles = new ArrayList<String>();
appRoleNames.put(appName, currentAppRoles);
}
currentAppRoles.add(role.getName());
}
}
if (realmRoleNames.size() > 0) {
userRep.setRealmRoles(realmRoleNames);
}
if (appRoleNames.size() > 0) {
userRep.setApplicationRoles(appRoleNames);
}
// Credentials
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
List<CredentialRepresentation> credReps = new ArrayList<CredentialRepresentation>();
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(KeycloakSession session, RealmModel realm, List<UserModel> 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(session, realm, user);
generator.writeObject(userRep);
}
generator.writeEndArray();
generator.writeEndObject();
} finally {
generator.close();
}
}
}
@@ -0,0 +1,211 @@
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.AdminRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelProvider;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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.model();
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);
if (Config.getAdminRealm().equals(realm.getId())) {
realm.setMasterAdminApp(null);
}
model.removeRealm(realm.getId());
}
}
realm = rep.getId() != null ? model.createRealm(rep.getId(), realmName) : model.createRealm(realmName);
RepresentationToModel.importRealm(session, 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));
if (masterApp != null) {
currentRealm.setMasterAdminApp(masterApp);
} else {
setupMasterAdminManagement(model, currentRealm);
}
}
} else {
// Need to refresh masterApp for current realm
RealmModel adminRealm = model.getRealm(adminRealmId);
ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(realm));
if (masterApp != null) {
realm.setMasterAdminApp(masterApp);
} else {
setupMasterAdminManagement(model, realm);
}
}
}
// TODO: We need method here, so we are able to refresh masterAdmin applications after import. Should be RealmManager moved to model/api instead?
public static void setupMasterAdminManagement(ModelProvider model, RealmModel realm) {
RealmModel adminRealm;
RoleModel adminRole;
if (realm.getName().equals(Config.getAdminRealm())) {
adminRealm = realm;
adminRole = realm.addRole(AdminRoles.ADMIN);
RoleModel createRealmRole = realm.addRole(AdminRoles.CREATE_REALM);
adminRole.addCompositeRole(createRealmRole);
} else {
adminRealm = model.getRealmByName(Config.getAdminRealm());
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
}
ApplicationModel realmAdminApp = KeycloakModelUtils.createApplication(adminRealm, ExportImportUtils.getMasterRealmAdminApplicationName(realm));
realmAdminApp.setBearerOnly(true);
realm.setMasterAdminApp(realmAdminApp);
for (String r : AdminRoles.ALL_REALM_ROLES) {
RoleModel role = realmAdminApp.addRole(r);
adminRole.addCompositeRole(role);
}
}
/**
* 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.model();
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<UserRepresentation> userReps = new ArrayList<UserRepresentation>();
while (parser.getCurrentToken() == JsonToken.START_OBJECT) {
UserRepresentation user = parser.readValueAs(UserRepresentation.class);
userReps.add(user);
parser.nextToken();
}
importUsers(session, model, realmName, userReps);
if (parser.getCurrentToken() == JsonToken.END_ARRAY) {
parser.nextToken();
}
}
}
} finally {
parser.close();
}
}
private static void importUsers(KeycloakSession session, ModelProvider model, String realmName, List<UserRepresentation> userReps) {
RealmModel realm = model.getRealmByName(realmName);
Map<String, ApplicationModel> apps = realm.getApplicationNameMap();
for (UserRepresentation user : userReps) {
RepresentationToModel.createUser(session, realm, user, apps);
}
}
}
@@ -0,0 +1,116 @@
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.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class MultipleStepsExportProvider implements ExportProvider {
protected final Logger logger = Logger.getLogger(getClass());
@Override
public void exportModel(KeycloakSessionFactory factory) throws IOException {
final RealmsHolder holder = new RealmsHolder();
// Import users into same file with realm
ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
@Override
public void run(KeycloakSession session) {
List<RealmModel> realms = session.model().getRealms();
holder.realms = realms;
}
});
for (RealmModel realm : holder.realms) {
exportRealm(factory, realm.getName());
}
}
@Override
public void exportRealm(KeycloakSessionFactory factory, final String realmName) throws IOException {
final int usersPerFile = ExportImportConfig.getUsersPerFile();
final UsersHolder usersHolder = new UsersHolder();
final boolean exportUsersIntoSameFile = usersPerFile < 0;
ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
@Override
public void run(KeycloakSession session) throws IOException {
RealmModel realm = session.model().getRealmByName(realmName);
RealmRepresentation rep = ExportUtils.exportRealm(session, 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 = session.users().getUsers(realm).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(factory, new ExportImportJob() {
@Override
public void run(KeycloakSession session) throws IOException {
RealmModel realm = session.model().getRealmByName(realmName);
// TODO: pagination
List<UserModel> users = session.users().getUsers(realm);
usersHolder.users = users.subList(usersHolder.currentPageStart, usersHolder.currentPageEnd);
writeUsers(realmName + "-users-" + (usersHolder.currentPageStart / countPerPage) + ".json", session, 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, KeycloakSession session, RealmModel realm, List<UserModel> users) throws IOException;
public static class RealmsHolder {
List<RealmModel> realms;
}
public static class UsersHolder {
List<UserModel> users;
int totalCount;
int currentPageStart;
int currentPageEnd;
}
}
@@ -0,0 +1,2 @@
org.keycloak.exportimport.ExportSpi
org.keycloak.exportimport.ImportSpi