user provider refactor complete
This commit is contained in:
commit
484a4c1454
199 files changed
+3712
-3088
No files matched your search
+107
@@ -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);
|
||||
}
|
||||
}
|
||||
+63
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -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);
|
||||
|
||||
}
|
||||
+18
@@ -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;
|
||||
|
||||
}
|
||||
+9
@@ -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> {
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
+9
@@ -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> {
|
||||
}
|
||||
+26
@@ -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
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
+47
@@ -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";
|
||||
}
|
||||
}
|
||||
Executable
+366
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+211
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+116
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
org.keycloak.exportimport.ExportSpi
|
||||
org.keycloak.exportimport.ImportSpi
|
||||
Reference in new issue
Block a user