diff --git a/export-import/export-import-api/pom.xml b/export-import/export-import-api/pom.xml new file mode 100644 index 0000000000..7f94100865 --- /dev/null +++ b/export-import/export-import-api/pom.xml @@ -0,0 +1,38 @@ + + + + keycloak-export-import + org.keycloak + 1.0-beta-1-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-export-import-api + Keycloak Export Import API + + + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + 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 new file mode 100644 index 0000000000..9ada46bfca --- /dev/null +++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/ExportImportProvider.java @@ -0,0 +1,11 @@ +package org.keycloak.exportimport; + +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Marek Posolda + */ +public interface ExportImportProvider { + + void checkExportImport(KeycloakSessionFactory identitySessionFactory); +} diff --git a/export-import/export-import-impl/pom.xml b/export-import/export-import-impl/pom.xml new file mode 100644 index 0000000000..a4898e50a9 --- /dev/null +++ b/export-import/export-import-impl/pom.xml @@ -0,0 +1,228 @@ + + + + keycloak-export-import + org.keycloak + 1.0-beta-1-SNAPSHOT + ../pom.xml + + 4.0.0 + + keycloak-export-import-impl + Keycloak Export Import Impl + + + + + 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.keycloak + keycloak-audit-api + ${project.version} + provided + + + + org.jboss.resteasy + jaxrs-api + provided + + + org.jboss.resteasy + resteasy-jaxrs + provided + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + + + org.jboss.logging + jboss-logging + provided + + + org.codehaus.jackson + jackson-core-asl + provided + + + org.codehaus.jackson + jackson-mapper-asl + provided + + + net.iharder + base64 + provided + + + org.bouncycastle + bcprov-jdk16 + provided + + + + de.idyl + winzipaes + provided + + + + org.keycloak + keycloak-model-tests + ${project.version} + tests + test + + + org.keycloak + keycloak-model-jpa + ${project.version} + test + + + org.keycloak + keycloak-model-jpa + ${project.version} + tests + test + + + org.keycloak + keycloak-model-mongo + ${project.version} + test + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + test + + + org.hibernate + hibernate-entitymanager + test + + + com.h2database + h2 + test + + + junit + junit + test + + + org.mongodb + mongo-java-driver + test + + + + + localhost + 27018 + keycloak + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + integration-test + + test + + + + ${keycloak.mongo.host} + ${keycloak.mongo.port} + ${keycloak.mongo.db} + ${keycloak.mongo.clearOnStartup} + + + + + default-test + + true + + + + + + + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + + + start-mongodb + pre-integration-test + + start + + + ${keycloak.mongo.port} + file + ${project.build.directory}/mongodb.log + + + + stop-mongodb + post-integration-test + + stop + + + + + + + + + diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java new file mode 100644 index 0000000000..87dfa5577b --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportPropertiesManager.java @@ -0,0 +1,152 @@ +package org.keycloak.exportimport; + +import java.lang.reflect.Method; +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 org.jboss.logging.Logger; +import org.keycloak.models.utils.reflection.Property; +import org.keycloak.models.utils.reflection.PropertyCriteria; +import org.keycloak.models.utils.reflection.PropertyQueries; + +/** + * Not thread safe (objectProperties). Assumption is that export/import is executed once per JVM + * + * @author Marek Posolda + */ +public class ExportImportPropertiesManager { + + private static final Logger logger = Logger.getLogger(ExportImportPropertiesManager.class); + + private Map, Map>> objectProperties = new HashMap, Map>>(); + + // Add properties of class to objectProperties + protected void initClassProperties(Class clazz) { + Map> classProps = PropertyQueries.createQuery(clazz).addCriteria(new NonEmptyGetterCriteria()).getWritableResultList(); + this.objectProperties.put(clazz, classProps); + } + + public void setBasicPropertiesFromModel(Object model, Object entity) { + Class modelClass = getModelClass(model); + + // Lazy init of properties + checkPropertiesAvailable(modelClass, entity.getClass()); + + Map> modelProps = this.objectProperties.get(modelClass); + Map> entityProps = this.objectProperties.get(entity.getClass()); + + Map> entityPropsCopy = new HashMap>(entityProps); + + logger.debugf("Properties of entity %s: %s", entity, entityProps.keySet()); + for (Property modelProperty : modelProps.values()) { + Property entityProperty = entityPropsCopy.get(modelProperty.getName()); + + entityPropsCopy.remove(modelProperty.getName()); + + if (entityProperty != null) { + Object propertyValue = modelProperty.getValue(model); + + // Workaround needed because model classes have many getters/setters with "Set", but for entity classes, there are usually "List" + if (propertyValue instanceof Set) { + Set propValueAsSet = (Set)propertyValue; + entityProperty.setValue(entity, new ArrayList(propValueAsSet)); + } else { + entityProperty.setValue(entity, propertyValue); + } + if (logger.isTraceEnabled()) { + logger.tracef("Property %s successfully set in JSON to entity %s", modelProperty.getName(), entity); + } + } else { + logger.debugf("Property %s not known in JSON for entity %s", modelProperty.getName(), entity); + } + } + + logger.debugf("Entity properties for manual setup: %s", entityPropsCopy.keySet()); + } + + private void checkPropertiesAvailable(Class modelClass, Class entityClass) { + if (!objectProperties.containsKey(modelClass)) { + initClassProperties(modelClass); + } + if (!objectProperties.containsKey(entityClass)) { + initClassProperties(entityClass); + } + } + + public void setBasicPropertiesToModel(Object model, Object entity) { + Class modelClass = getModelClass(model); + + // Lazy init of properties + checkPropertiesAvailable(modelClass, entity.getClass()); + + Map> modelProps = this.objectProperties.get(modelClass); + Map> entityProps = this.objectProperties.get(entity.getClass()); + + Map> entityPropsCopy = new HashMap>(entityProps); + + logger.debugf("Properties of exported entity %s: %s", entity, entityProps.keySet()); + + for (Property modelProperty : modelProps.values()) { + Property entityProperty = entityPropsCopy.get(modelProperty.getName()); + + entityPropsCopy.remove(modelProperty.getName()); + + if (entityProperty != null) { + Object propertyValue = entityProperty.getValue(entity); + + // Workaround needed because model classes have many getters/setters with "Set", but for entity classes, there are usually "List" + if (propertyValue instanceof List && Set.class.isAssignableFrom(modelProperty.getJavaClass())) { + List propValueAsList = (List)propertyValue; + modelProperty.setValue(model, new HashSet(propValueAsList)); + } else { + modelProperty.setValue(model, propertyValue); + } + + if (logger.isTraceEnabled()) { + logger.tracef("Property %s successfully set in model from entity %s", modelProperty.getName(), entity); + } + } else { + logger.debugf("Property %s not known for entity %s", modelProperty.getName(), entity); + } + } + + logger.debugf("Entity properties for manual setup: %s", entityPropsCopy.keySet()); + } + + protected Class getModelClass(Object model) { + Class modelClass = model.getClass(); + Class[] interfaces = modelClass.getInterfaces(); + + // Bit unsafe, but looks that it works for all "model adapters" so far + if (interfaces.length == 0) { + return modelClass; + } else { + return interfaces[0]; + } + } + + public static class NonEmptyGetterCriteria implements PropertyCriteria { + + private static final List IGNORED_METHODS = Arrays.asList("getPasswordPolicy", "getAuthenticationProviders"); + + @Override + public boolean methodMatches(Method m) { + // Ignore non-empty getters + if (m.getParameterTypes().length > 0) { + return false; + } + + // Ignore some "known" getters (for example incompatible types between model and entity) + if (IGNORED_METHODS.contains(m.getName())) { + return false; + } + + return true; + } + } +} 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 new file mode 100644 index 0000000000..32e0b84f74 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ExportImportProviderImpl.java @@ -0,0 +1,84 @@ +package org.keycloak.exportimport; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ExportImportIOProvider; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.models.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.KeycloakTransaction; +import org.keycloak.util.ProviderLoader; + +/** + * @author Marek Posolda + */ +public class ExportImportProviderImpl implements ExportImportProvider { + + 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 identitySessionFactory) { + String exportImportAction = Config.getExportImportAction(); + + boolean export = false; + boolean importt = false; + if (ACTION_EXPORT.equals(exportImportAction)) { + logger.infof("Full model export requested"); + export = true; + } else if (ACTION_IMPORT.equals(exportImportAction)) { + logger.infof("Full model import requested"); + importt = true; + } + + if (export || importt) { + KeycloakSession session = identitySessionFactory.createSession(); + KeycloakTransaction transaction = session.getTransaction(); + try { + transaction.begin(); + + if (export) { + ExportWriter exportWriter = getProvider().getExportWriter(); + new ModelExporter().exportModel(session, exportWriter); + logger.infof("Export finished successfully"); + } else { + ImportReader importReader = getProvider().getImportReader(); + new ModelImporter().importModel(session, importReader); + logger.infof("Import finished successfully"); + } + + if (transaction.isActive()) { + if (transaction.getRollbackOnly()) { + transaction.rollback(); + } else { + transaction.commit(); + } + } + } catch (Exception e) { + if (transaction.isActive()) { + session.getTransaction().rollback(); + } + throw new RuntimeException(e); + } finally { + session.close(); + } + } + } + + private ExportImportIOProvider getProvider() { + String providerId = Config.getExportImportProvider(); + logger.infof("Requested migration provider: " + providerId); + + Iterable providers = ProviderLoader.load(ExportImportIOProvider.class); + for (ExportImportIOProvider provider : providers) { + if (providerId.equals(provider.getId())) { + return provider; + } + } + + throw new IllegalStateException("Provider " + providerId + " not found"); + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java new file mode 100644 index 0000000000..b1893f390c --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelExporter.java @@ -0,0 +1,334 @@ +package org.keycloak.exportimport; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.AuthenticationLinkModel; +import org.keycloak.models.AuthenticationProviderModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredCredentialModel; +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.UsernameLoginFailureModel; +import org.keycloak.models.entities.ApplicationEntity; +import org.keycloak.models.entities.AuthenticationLinkEntity; +import org.keycloak.models.entities.AuthenticationProviderEntity; +import org.keycloak.models.entities.CredentialEntity; +import org.keycloak.models.entities.OAuthClientEntity; +import org.keycloak.models.entities.RealmEntity; +import org.keycloak.models.entities.RequiredCredentialEntity; +import org.keycloak.models.entities.RoleEntity; +import org.keycloak.models.entities.SocialLinkEntity; +import org.keycloak.models.entities.UserEntity; +import org.keycloak.models.entities.UsernameLoginFailureEntity; + +/** + * @author Marek Posolda + */ +public class ModelExporter { + + private static final Logger logger = Logger.getLogger(ModelExporter.class); + + private ExportWriter exportWriter; + private ExportImportPropertiesManager propertiesManager; + + public void exportModel(KeycloakSession keycloakSession, ExportWriter exportWriter) { + // Initialize needed objects + this.exportWriter = exportWriter; + this.propertiesManager = new ExportImportPropertiesManager(); + + // Create separate files for "realms", "applications", "oauthClients", "roles" and finally "users". Users may be done in more files (pagination) + exportRealms(keycloakSession, "realms.json"); + exportApplications(keycloakSession, "applications.json"); + exportOAuthClients(keycloakSession, "oauthClients.json"); + exportRoles(keycloakSession, "roles.json"); + exportUsers(keycloakSession, "users.json"); + exportUserFailures(keycloakSession, "userFailures.json"); + + this.exportWriter.closeExportWriter(); + } + + protected void exportRealms(KeycloakSession keycloakSession, String fileName) { + List realms = keycloakSession.getRealms(); + + // Convert models to entities, which will be written into JSON file + List result = new LinkedList(); + for (RealmModel realmModel : realms) { + RealmEntity entity = new RealmEntity(); + entity.setId(realmModel.getId()); + result.add(entity); + + // Export all basic properties from realm + this.propertiesManager.setBasicPropertiesFromModel(realmModel, entity); + + // Export 'advanced' properties + ApplicationModel realmAdminApp = realmModel.getAdminApp(); + if (realmAdminApp != null) { + entity.setAdminAppId(realmAdminApp.getId()); + } + entity.setDefaultRoles(realmModel.getDefaultRoles()); + + List reqCredEntities = new ArrayList(); + List requiredCredModels = realmModel.getRequiredCredentials(); + for (RequiredCredentialModel requiredCredModel : requiredCredModels) { + RequiredCredentialEntity reqCredEntity = new RequiredCredentialEntity(); + this.propertiesManager.setBasicPropertiesFromModel(requiredCredModel, reqCredEntity); + reqCredEntities.add(reqCredEntity); + } + entity.setRequiredCredentials(reqCredEntities); + + // password policy + entity.setPasswordPolicy(realmModel.getPasswordPolicy().toString()); + + // authentication providers + List authProviderEntities = new ArrayList(); + for (AuthenticationProviderModel authProvider : realmModel.getAuthenticationProviders()) { + AuthenticationProviderEntity authProviderEntity = new AuthenticationProviderEntity(); + this.propertiesManager.setBasicPropertiesFromModel(authProvider, authProviderEntity); + authProviderEntities.add(authProviderEntity); + + } + entity.setAuthenticationProviders(authProviderEntities); + } + + this.exportWriter.writeEntities(fileName, result); + logger.infof("Realms exported: " + result); + } + + protected void exportApplications(KeycloakSession keycloakSession, String fileName) { + List allApplications = getAllApplications(keycloakSession); + + List result = new LinkedList(); + for (ApplicationModel appModel : allApplications) { + ApplicationEntity appEntity = new ApplicationEntity(); + appEntity.setId(appModel.getId()); + result.add(appEntity); + + this.propertiesManager.setBasicPropertiesFromModel(appModel, appEntity); + + // Export 'advanced' properties of application + appEntity.setRealmId(appModel.getRealm().getId()); + appEntity.setDefaultRoles(appModel.getDefaultRoles()); + + List scopeIds = getScopeIds(appModel); + appEntity.setScopeIds(scopeIds); + } + + this.exportWriter.writeEntities(fileName, result); + logger.infof("Applications exported: " + result); + } + + protected void exportOAuthClients(KeycloakSession keycloakSession, String fileName) { + List realms = keycloakSession.getRealms(); + List allClients = new ArrayList(); + for (RealmModel realmModel : realms) { + allClients.addAll(realmModel.getOAuthClients()); + } + + List result = new LinkedList(); + for (OAuthClientModel clientModel : allClients) { + OAuthClientEntity clientEntity = new OAuthClientEntity(); + clientEntity.setId(clientModel.getId()); + result.add(clientEntity); + + this.propertiesManager.setBasicPropertiesFromModel(clientModel, clientEntity); + + // Export 'advanced' properties of client + clientEntity.setName(clientModel.getClientId()); + clientEntity.setRealmId(clientModel.getRealm().getId()); + + List scopeIds = getScopeIds(clientModel); + clientEntity.setScopeIds(scopeIds); + } + + this.exportWriter.writeEntities(fileName, result); + logger.infof("OAuth clients exported: " + result); + } + + protected void exportRoles(KeycloakSession keycloakSession, String fileName) { + List allRoles = getAllRoles(keycloakSession); + + List result = new LinkedList(); + for (RoleModel roleModel : allRoles) { + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setId(roleModel.getId()); + result.add(roleEntity); + + roleEntity.setName(roleModel.getName()); + roleEntity.setDescription(roleModel.getDescription()); + + RoleContainerModel roleContainer = roleModel.getContainer(); + if (roleContainer instanceof RealmModel) { + RealmModel realm = (RealmModel)roleContainer; + roleEntity.setRealmId(realm.getId()); + } else { + ApplicationModel appModel = (ApplicationModel)roleContainer; + roleEntity.setApplicationId(appModel.getId()); + } + + List compositeRolesIds = null; + for (RoleModel composite : roleModel.getComposites()) { + + // Lazy init + if (compositeRolesIds == null) { + compositeRolesIds = new ArrayList(); + } + + compositeRolesIds.add(composite.getId()); + } + roleEntity.setCompositeRoleIds(compositeRolesIds); + } + + this.exportWriter.writeEntities(fileName, result); + + logger.infof("%d roles exported: ", result.size()); + if (logger.isDebugEnabled()) { + logger.debug("Exported roles: " + result); + } + } + + protected void exportUsers(KeycloakSession keycloakSession, String fileName) { + List realms = keycloakSession.getRealms(); + List result = new LinkedList(); + + for (RealmModel realm : realms) { + List userModels = realm.getUsers(); + for (UserModel userModel : userModels) { + UserEntity userEntity = new UserEntity(); + userEntity.setId(userModel.getId()); + result.add(userEntity); + + this.propertiesManager.setBasicPropertiesFromModel(userModel, userEntity); + + userEntity.setLoginName(userModel.getLoginName()); + userEntity.setRealmId(realm.getId()); + + // authentication links + AuthenticationLinkModel authLink = realm.getAuthenticationLink(userModel); + if (authLink != null) { + AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity(); + this.propertiesManager.setBasicPropertiesFromModel(authLink, authLinkEntity); + + userEntity.setAuthenticationLink(authLinkEntity); + } + + // social links + Set socialLinks = realm.getSocialLinks(userModel); + if (socialLinks != null && !socialLinks.isEmpty()) { + List socialLinkEntities = new ArrayList(); + for (SocialLinkModel socialLink : socialLinks) { + SocialLinkEntity socialLinkEntity = new SocialLinkEntity(); + this.propertiesManager.setBasicPropertiesFromModel(socialLink, socialLinkEntity); + + socialLinkEntities.add(socialLinkEntity); + } + + userEntity.setSocialLinks(socialLinkEntities); + } + + // required actions + Set requiredActions = userModel.getRequiredActions(); + if (requiredActions != null && !requiredActions.isEmpty()) { + userEntity.setRequiredActions(new ArrayList(requiredActions)); + } + + // attributes + userEntity.setAttributes(userModel.getAttributes()); + + // roleIds + Set roles = realm.getRoleMappings(userModel); + List roleIds = new ArrayList(); + for (RoleModel role : roles) { + roleIds.add(role.getId()); + } + userEntity.setRoleIds(roleIds); + + // credentials + List credentials = realm.getCredentialsDirectly(userModel); + List credEntities = new ArrayList(); + for (UserCredentialValueModel credModel : credentials) { + CredentialEntity credEntity = new CredentialEntity(); + this.propertiesManager.setBasicPropertiesFromModel(credModel, credEntity); + credEntities.add(credEntity); + } + + userEntity.setCredentials(credEntities); + } + } + + this.exportWriter.writeEntities(fileName, result); + + logger.infof("%d users exported: ", result.size()); + if (logger.isDebugEnabled()) { + logger.debug("Exported users: " + result); + } + } + + + // Does it makes sense to export user failures ? + protected void exportUserFailures(KeycloakSession keycloakSession, String fileName) { + List realms = keycloakSession.getRealms(); + List allFailures = new ArrayList(); + for (RealmModel realmModel : realms) { + allFailures.addAll(realmModel.getAllUserLoginFailures()); + } + + List result = new LinkedList(); + for (UsernameLoginFailureModel failureModel : allFailures) { + UsernameLoginFailureEntity failureEntity = new UsernameLoginFailureEntity(); + this.propertiesManager.setBasicPropertiesFromModel(failureModel, failureEntity); + result.add(failureEntity); + + failureEntity.setUsername(failureModel.getUsername()); + failureEntity.setNumFailures(failureModel.getNumFailures()); + } + + this.exportWriter.writeEntities(fileName, result); + } + + private List getScopeIds(ClientModel clientModel) { + Set allScopes = clientModel.getRealm().getScopeMappings(clientModel); + List scopeIds = new ArrayList(); + for (RoleModel role : allScopes) { + scopeIds.add(role.getId()); + } + return scopeIds; + } + + private List getAllApplications(KeycloakSession keycloakSession) { + List realms = keycloakSession.getRealms(); + List allApplications = new ArrayList(); + for (RealmModel realmModel : realms) { + allApplications.addAll(realmModel.getApplications()); + } + return allApplications; + } + + private List getAllRoles(KeycloakSession keycloakSession) { + List allRoles = new ArrayList(); + + List realms = keycloakSession.getRealms(); + for (RealmModel realmModel : realms) { + allRoles.addAll(realmModel.getRoles()); + } + + List allApplications = getAllApplications(keycloakSession); + for (ApplicationModel appModel : allApplications) { + allRoles.addAll(appModel.getRoles()); + } + + return allRoles; + } + +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java new file mode 100644 index 0000000000..b751d73891 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/ModelImporter.java @@ -0,0 +1,329 @@ +package org.keycloak.exportimport; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.AuthenticationLinkModel; +import org.keycloak.models.AuthenticationProviderModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.PasswordPolicy; +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.UsernameLoginFailureModel; +import org.keycloak.models.entities.ApplicationEntity; +import org.keycloak.models.entities.AuthenticationLinkEntity; +import org.keycloak.models.entities.AuthenticationProviderEntity; +import org.keycloak.models.entities.ClientEntity; +import org.keycloak.models.entities.CredentialEntity; +import org.keycloak.models.entities.OAuthClientEntity; +import org.keycloak.models.entities.RealmEntity; +import org.keycloak.models.entities.RequiredCredentialEntity; +import org.keycloak.models.entities.RoleEntity; +import org.keycloak.models.entities.SocialLinkEntity; +import org.keycloak.models.entities.UserEntity; +import org.keycloak.models.entities.UsernameLoginFailureEntity; + +/** + * @author Marek Posolda + */ +public class ModelImporter { + + private static final Logger logger = Logger.getLogger(ModelImporter.class); + + private ImportReader importReader; + private ExportImportPropertiesManager propertiesManager; + + public void importModel(KeycloakSession keycloakSession, ImportReader importReader) { + // Initialize needed objects + this.importReader = importReader; + this.propertiesManager = new ExportImportPropertiesManager(); + + // Delete all the data from current model + keycloakSession.removeAllData(); + + importRealms(keycloakSession, "realms.json"); + importApplications(keycloakSession, "applications.json"); + importRoles(keycloakSession, "roles.json"); + + // Now we have all realms,applications and roles filled. So fill other objects (default roles, scopes etc) + importRealmsStep2(keycloakSession, "realms.json"); + importApplicationsStep2(keycloakSession, "applications.json"); + + importOAuthClients(keycloakSession, "oauthClients.json"); + importUsers(keycloakSession, "users.json"); + importUserFailures(keycloakSession, "userFailures.json"); + + this.importReader.closeImportReader(); + } + + protected void importRealms(KeycloakSession keycloakSession, String fileName) { + List realms = this.importReader.readEntities(fileName, RealmEntity.class); + + for (RealmEntity realmEntity : realms) { + RealmModel realm = keycloakSession.createRealm(realmEntity.getId(), realmEntity.getName()); + + this.propertiesManager.setBasicPropertiesToModel(realm, realmEntity); + + Set reqCredModels = new HashSet(); + for (RequiredCredentialEntity requiredCredEntity : realmEntity.getRequiredCredentials()) { + reqCredModels.add(requiredCredEntity.getType()); + } + realm.updateRequiredCredentials(reqCredModels); + + // AdminApp and defaultRoles are set in step2 + + // password policy + realm.setPasswordPolicy(new PasswordPolicy(realmEntity.getPasswordPolicy())); + + // authentication providers + List authProviderModels = new ArrayList(); + for (AuthenticationProviderEntity authProviderEntity : realmEntity.getAuthenticationProviders()) { + AuthenticationProviderModel authProvider = new AuthenticationProviderModel(); + this.propertiesManager.setBasicPropertiesToModel(authProvider, authProviderEntity); + authProviderModels.add(authProvider); + + } + realm.setAuthenticationProviders(authProviderModels); + } + + logger.infof("Realms imported: " + realms); + } + + protected void importApplications(KeycloakSession keycloakSession, String fileName) { + List apps = this.importReader.readEntities(fileName, ApplicationEntity.class); + for (ApplicationEntity appEntity : apps) { + RealmModel realm = keycloakSession.getRealm(appEntity.getRealmId()); + ApplicationModel app = realm.addApplication(appEntity.getId(), appEntity.getName()); + + this.propertiesManager.setBasicPropertiesToModel(app , appEntity); + + // scopeIds and default roles will be done in step2 + } + + logger.infof("Applications imported: " + apps); + } + + protected void importRoles(KeycloakSession keycloakSession, String fileName) { + // helper map for composite roles + Map rolesMap = new HashMap(); + + List roles = this.importReader.readEntities(fileName, RoleEntity.class); + for (RoleEntity roleEntity : roles) { + RoleModel role = null; + if (roleEntity.getRealmId() != null) { + RealmModel realm = keycloakSession.getRealm(roleEntity.getRealmId()); + role = realm.addRole(roleEntity.getId(), roleEntity.getName()); + } else if (roleEntity.getApplicationId() != null) { + ApplicationModel app = findApplicationById(keycloakSession, roleEntity.getApplicationId()); + role = app.addRole(roleEntity.getId(), roleEntity.getName()); + } else { + throw new IllegalStateException("Role " + roleEntity.getId() + " doesn't have realmId nor applicationId"); + } + + role.setDescription(roleEntity.getDescription()); + + rolesMap.put(roleEntity.getId(), roleEntity); + } + + // All roles were added. Fill composite roles now + for (RealmModel realm : keycloakSession.getRealms()) { + + // realm roles + fillCompositeRoles(rolesMap, realm, realm); + + // app roles + for (ApplicationModel app : realm.getApplications()) { + fillCompositeRoles(rolesMap, app, realm); + } + } + + logger.infof("%d roles imported: ", roles); + if (logger.isDebugEnabled()) { + logger.debug("Imported roles: " + roles); + } + } + + private void fillCompositeRoles(Map rolesMap, RoleContainerModel roleContainer, RealmModel realm) { + for (RoleModel role : roleContainer.getRoles()) { + RoleEntity roleEntity = rolesMap.get(role.getId()); + + if (roleEntity.getCompositeRoleIds() == null) { + continue; + } + + for (String compositeRoleId : roleEntity.getCompositeRoleIds()) { + RoleModel compositeRole = realm.getRoleById(compositeRoleId); + role.addCompositeRole(compositeRole); + } + } + } + + protected void importRealmsStep2(KeycloakSession keycloakSession, String fileName) { + List realms = this.importReader.readEntities(fileName, RealmEntity.class); + RealmModel adminRealm = keycloakSession.getRealm(Config.getAdminRealm()); + + for (RealmEntity realmEntity : realms) { + RealmModel realm = keycloakSession.getRealm(realmEntity.getId()); + + // admin app + String adminAppId = realmEntity.getAdminAppId(); + if (adminAppId != null) { + realm.setAdminApp(adminRealm.getApplicationById(adminAppId)); + } + + // Default roles + realm.updateDefaultRoles(realmEntity.getDefaultRoles().toArray(new String[] {})); + } + } + + protected void importApplicationsStep2(KeycloakSession keycloakSession, String fileName) { + List apps = this.importReader.readEntities(fileName, ApplicationEntity.class); + for (ApplicationEntity appEntity : apps) { + RealmModel realm = keycloakSession.getRealm(appEntity.getRealmId()); + ApplicationModel application = realm.getApplicationById(appEntity.getId()); + + // Default roles + application.updateDefaultRoles(appEntity.getDefaultRoles().toArray(new String[] {})); + + // Scopes + addScopes(realm, application, appEntity); + } + } + + private void addScopes(RealmModel realm, ClientModel client, ClientEntity clientEntity) { + for (String scopeId : clientEntity.getScopeIds()) { + RoleModel scope = realm.getRoleById(scopeId); + realm.addScopeMapping(client, scope); + } + } + + protected void importOAuthClients(KeycloakSession keycloakSession, String fileName) { + List clients = this.importReader.readEntities(fileName, OAuthClientEntity.class); + for (OAuthClientEntity clientEntity : clients) { + RealmModel realm = keycloakSession.getRealm(clientEntity.getRealmId()); + OAuthClientModel client = realm.addOAuthClient(clientEntity.getId(), clientEntity.getName()); + + this.propertiesManager.setBasicPropertiesToModel(client, clientEntity); + + client.setClientId(clientEntity.getName()); + + // Scopes. All roles are already added at this point + addScopes(realm, client, clientEntity); + } + + logger.infof("OAuth clients imported: " + clients); + } + + protected ApplicationModel findApplicationById(KeycloakSession keycloakSession, String applicationId) { + for (RealmModel realm : keycloakSession.getRealms()) { + ApplicationModel appModel = realm.getApplicationById(applicationId); + if (appModel != null) { + return appModel; + } + } + + return null; + } + + public void importUsers(KeycloakSession keycloakSession, String fileName) { + List users = this.importReader.readEntities(fileName, UserEntity.class); + for (UserEntity userEntity : users) { + RealmModel realm = keycloakSession.getRealm(userEntity.getRealmId()); + UserModel user = realm.addUser(userEntity.getId(), userEntity.getLoginName()); + + // We need to remove defaultRoles here as realm.addUser is automatically adding them. We may add them later during roles mapping processing + for (RoleModel role : realm.getRoleMappings(user)) { + realm.deleteRoleMapping(user, role); + } + + this.propertiesManager.setBasicPropertiesToModel(user, userEntity); + + // authentication links + AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink(); + if (authLinkEntity != null) { + AuthenticationLinkModel authLinkModel = new AuthenticationLinkModel(); + this.propertiesManager.setBasicPropertiesToModel(authLinkModel, authLinkEntity); + + realm.setAuthenticationLink(user, authLinkModel); + } + + // social links + List socialLinks = userEntity.getSocialLinks(); + if (socialLinks != null && !socialLinks.isEmpty()) { + for (SocialLinkEntity socialLinkEntity : socialLinks) { + SocialLinkModel socialLink = new SocialLinkModel(); + this.propertiesManager.setBasicPropertiesToModel(socialLink, socialLinkEntity); + + realm.addSocialLink(user, socialLink); + } + } + + // required actions + List requiredActions = userEntity.getRequiredActions(); + if (requiredActions != null && !requiredActions.isEmpty()) { + for (UserModel.RequiredAction reqAction : requiredActions) { + user.addRequiredAction(reqAction); + } + } + + // attributes + if (userEntity.getAttributes() != null) { + for (Map.Entry attr : userEntity.getAttributes().entrySet()) { + user.setAttribute(attr.getKey(), attr.getValue()); + } + } + + // roles + if (userEntity.getRoleIds() != null) { + for (String roleId : userEntity.getRoleIds()) { + RoleModel role = realm.getRoleById(roleId); + realm.grantRole(user, role); + } + } + + // credentials + List credentials = userEntity.getCredentials(); + if (credentials != null) { + for (CredentialEntity credEntity : credentials) { + UserCredentialValueModel credModel = new UserCredentialValueModel(); + this.propertiesManager.setBasicPropertiesToModel(credModel, credEntity); + + realm.updateCredentialDirectly(user, credModel); + } + } + } + + logger.infof("%d users imported: ", users.size()); + if (logger.isDebugEnabled()) { + logger.debug("Imported users: " + users); + } + } + + public void importUserFailures(KeycloakSession keycloakSession, String fileName) { + List userFailures = this.importReader.readEntities(fileName, UsernameLoginFailureEntity.class); + for (UsernameLoginFailureEntity entity : userFailures) { + RealmModel realm = keycloakSession.getRealm(entity.getRealmId()); + UsernameLoginFailureModel model = realm.addUserLoginFailure(entity.getUsername()); + + this.propertiesManager.setBasicPropertiesToModel(model , entity); + + for (int i=0 ; iMarek Posolda + */ +public interface ExportImportIOProvider { + + String getId(); + + ExportWriter getExportWriter(); + + ImportReader getImportReader(); + +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java new file mode 100644 index 0000000000..c90a57f794 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ExportWriter.java @@ -0,0 +1,13 @@ +package org.keycloak.exportimport.io; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public interface ExportWriter { + + void writeEntities(String fileName, List entities); + + void closeExportWriter(); +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java new file mode 100644 index 0000000000..83faabd3cc --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/ImportReader.java @@ -0,0 +1,13 @@ +package org.keycloak.exportimport.io; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public interface ImportReader { + + List readEntities(String fileName, Class entityClass); + + void closeImportReader(); +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java new file mode 100644 index 0000000000..962c3acef8 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportImportIOProvider.java @@ -0,0 +1,36 @@ +package org.keycloak.exportimport.io.directory; + +import java.io.File; + +import org.keycloak.exportimport.io.ExportImportIOProvider; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.models.Config; + +/** + * Export/import into JSON files inside "tmp" directory. This implementation is used mainly for testing + * (shouldn't be used in production due to passwords in JSON files) + * + * @author Marek Posolda + */ +public class TmpDirExportImportIOProvider implements ExportImportIOProvider { + + public static final String PROVIDER_ID = "dir"; + + @Override + public ExportWriter getExportWriter() { + String dir = Config.getExportImportDir(); + return dir!=null ? new TmpDirExportWriter(new File(dir)) : new TmpDirExportWriter(); + } + + @Override + public ImportReader getImportReader() { + String dir = Config.getExportImportDir(); + return dir!=null ? new TmpDirImportReader(new File(dir)) : new TmpDirImportReader(); + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java new file mode 100644 index 0000000000..d1ee454128 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirExportWriter.java @@ -0,0 +1,82 @@ +package org.keycloak.exportimport.io.directory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; + +import org.codehaus.jackson.map.ObjectMapper; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class TmpDirExportWriter implements ExportWriter { + + private static final Logger logger = Logger.getLogger(TmpDirExportWriter.class); + + private final ObjectMapper objectMapper; + private final File rootDirectory; + + public TmpDirExportWriter() { + // 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()) { + recursiveDeleteDir(this.rootDirectory ); + } + this.rootDirectory.mkdirs(); + + logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath()); + this.objectMapper = getObjectMapper(); + } + + public TmpDirExportWriter(File rootDirectory) { + this.rootDirectory = rootDirectory; + this.rootDirectory.mkdirs(); + this.objectMapper = getObjectMapper(); + + logger.infof("Exporting into directory %s", this.rootDirectory.getAbsolutePath()); + } + + private ObjectMapper getObjectMapper() { + return JsonSerialization.prettyMapper; + } + + protected 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 writeEntities(String fileName, List entities) { + try { + File file = new File(this.rootDirectory, fileName); + FileOutputStream stream = new FileOutputStream(file); + this.objectMapper.writeValue(stream, entities); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public void closeExportWriter() { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java new file mode 100644 index 0000000000..942e23a93b --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/directory/TmpDirImportReader.java @@ -0,0 +1,67 @@ +package org.keycloak.exportimport.io.directory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; + +import org.codehaus.jackson.map.ObjectMapper; +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class TmpDirImportReader implements ImportReader { + + private static final Logger logger = Logger.getLogger(TmpDirImportReader.class); + + private final ObjectMapper objectMapper; + private final File rootDirectory; + + public TmpDirImportReader() { + // 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()); + this.objectMapper = getObjectMapper(); + } + + public TmpDirImportReader(File rootDirectory) { + this.rootDirectory = rootDirectory; + this.objectMapper = getObjectMapper(); + + logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath()); + } + + private ObjectMapper getObjectMapper() { + return JsonSerialization.prettyMapper; + } + + @Override + public List readEntities(String fileName, Class entityClass) { + try { + File file = new File(this.rootDirectory, fileName); + FileInputStream stream = new FileInputStream(file); + T[] template = (T[]) Array.newInstance(entityClass, 0); + T[] result = (T[])this.objectMapper.readValue(stream, template.getClass()); + return Arrays.asList(result); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public void closeImportReader() { + //TODO: Should directory be deleted after import? + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java new file mode 100644 index 0000000000..0b49940b84 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPExportWriter.java @@ -0,0 +1,66 @@ +package org.keycloak.exportimport.io.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.codehaus.jackson.map.ObjectMapper; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class EncryptedZIPExportWriter implements ExportWriter { + + private final File zipFile; + private final ObjectMapper objectMapper; + private final String password; + + private final AesZipFileEncrypter encrypter; + + + public EncryptedZIPExportWriter(String zipFileName, String password) { + try { + this.zipFile = new File(zipFileName); + if (zipFile.exists()) { + throw new IllegalStateException("File " + zipFileName + " already exists"); + } + + this.objectMapper = JsonSerialization.mapper; + + AESEncrypter encrypter = new AESEncrypterBC(); + this.encrypter = new AesZipFileEncrypter(this.zipFile, encrypter); + this.password = password; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public void writeEntities(String fileName, List entities) { + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + this.objectMapper.writeValue(stream, entities); + + byte[] byteArray = stream.toByteArray(); + this.encrypter.add(fileName, new ByteArrayInputStream(byteArray), this.password); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public void closeExportWriter() { + try { + this.encrypter.close(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java new file mode 100644 index 0000000000..837157e5e6 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPIOProvider.java @@ -0,0 +1,48 @@ +package org.keycloak.exportimport.io.zip; + +import org.jboss.logging.Logger; +import org.keycloak.exportimport.io.ExportImportIOProvider; +import org.keycloak.exportimport.io.ExportWriter; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.models.Config; + +/** + * @author Marek Posolda + */ +public class EncryptedZIPIOProvider implements ExportImportIOProvider { + + private static final Logger logger = Logger.getLogger(EncryptedZIPIOProvider.class); + + public static final String PROVIDER_ID = "zip"; + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public ExportWriter getExportWriter() { + String zipFile = Config.getExportImportZipFile(); + String zipPassword = Config.getExportImportZipPassword(); + logger.infof("Using zip for export: " + zipFile); + + if (zipFile==null || zipPassword==null) { + throw new IllegalArgumentException("zipFile or zipPassword are null"); + } + + return new EncryptedZIPExportWriter(zipFile, zipPassword); + } + + @Override + public ImportReader getImportReader() { + String zipFile = Config.getExportImportZipFile(); + String zipPassword = Config.getExportImportZipPassword(); + logger.infof("Using zip for import: " + zipFile); + + if (zipFile==null || zipPassword==null) { + throw new IllegalArgumentException("zipFile or zipPassword are null"); + } + + return new EncryptedZIPImportReader(zipFile, zipPassword); + } +} diff --git a/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java new file mode 100644 index 0000000000..d115b7d6e9 --- /dev/null +++ b/export-import/export-import-impl/src/main/java/org/keycloak/exportimport/io/zip/EncryptedZIPImportReader.java @@ -0,0 +1,70 @@ +package org.keycloak.exportimport.io.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; + +import de.idyl.winzipaes.AesZipFileDecrypter; +import de.idyl.winzipaes.impl.AESDecrypter; +import de.idyl.winzipaes.impl.AESDecrypterBC; +import org.codehaus.jackson.map.ObjectMapper; +import org.keycloak.exportimport.io.ImportReader; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class EncryptedZIPImportReader implements ImportReader { + + private final File zipFile; + private final ObjectMapper objectMapper; + private final String password; + + private final AesZipFileDecrypter decrypter; + + + public EncryptedZIPImportReader(String zipFileName, String password) { + try { + this.zipFile = new File(zipFileName); + if (!zipFile.exists()) { + throw new IllegalStateException("File " + zipFileName + " doesn't exists"); + } + + this.objectMapper = JsonSerialization.mapper; + + AESDecrypter decrypter = new AESDecrypterBC(); + this.decrypter = new AesZipFileDecrypter(this.zipFile, decrypter); + this.password = password; + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public List readEntities(String fileName, Class entityClass) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + this.decrypter.extractEntry(this.decrypter.getEntry(fileName), bos, this.password); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + T[] template = (T[]) Array.newInstance(entityClass, 0); + T[] result = (T[])this.objectMapper.readValue(bis, template.getClass()); + return Arrays.asList(result); + } catch (Exception ioe) { + throw new RuntimeException(ioe); + } + } + + @Override + public void closeImportReader() { + try { + this.decrypter.close(); + } catch (Exception ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider new file mode 100644 index 0000000000..353987ffb8 --- /dev/null +++ b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.ExportImportProvider @@ -0,0 +1 @@ +org.keycloak.exportimport.ExportImportProviderImpl diff --git a/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider new file mode 100644 index 0000000000..75c97ec96b --- /dev/null +++ b/export-import/export-import-impl/src/main/resources/META-INF/services/org.keycloak.exportimport.io.ExportImportIOProvider @@ -0,0 +1,2 @@ +org.keycloak.exportimport.io.directory.TmpDirExportImportIOProvider +org.keycloak.exportimport.io.zip.EncryptedZIPIOProvider 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 new file mode 100644 index 0000000000..1e39f24323 --- /dev/null +++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/ExportImportTestBase.java @@ -0,0 +1,110 @@ +package org.keycloak.exportimport; + +import java.util.Iterator; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.model.test.AbstractModelTest; +import org.keycloak.model.test.ImportTest; +import org.keycloak.models.Config; +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; + +/** + * @author Marek Posolda + */ +public abstract class ExportImportTestBase { + + protected KeycloakSessionFactory factory; + + protected KeycloakSession identitySession; + protected RealmManager realmManager; + + @Test + public void testExportImport() throws Exception { + // Init JPA model + Config.setModelProvider(getExportModelProvider()); + factory = KeycloakApplication.createSessionFactory(); + + // Bootstrap admin realm + beginTransaction(); + new ApplianceBootstrap().bootstrap(identitySession, "/auth"); + commitTransaction(); + + // Classic import of realm to JPA model + beginTransaction(); + RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json"); + realmManager = new RealmManager(identitySession); + RealmModel realm = realmManager.createRealm("demo", rep.getRealm()); + realmManager.importRealm(rep, realm); + + commitTransaction(); + + // Full export of realm + exportModel(factory); + + beginTransaction(); + realm = identitySession.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 + Config.setModelProvider(getImportModelProvider()); + factory = KeycloakApplication.createSessionFactory(); + + // Full import of previous export into mongo + importModel(factory); + + // Verify it's imported in mongo (reusing ImportTest) + beginTransaction(); + RealmModel importedRealm = identitySession.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() { + identitySession = factory.createSession(); + identitySession.getTransaction().begin(); + realmManager = new RealmManager(identitySession); + } + + protected void commitTransaction() { + identitySession.getTransaction().commit(); + identitySession.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 new file mode 100644 index 0000000000..8175bdb498 --- /dev/null +++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/JPAToMongoExportImportTest.java @@ -0,0 +1,37 @@ +package org.keycloak.exportimport; + +import org.keycloak.exportimport.io.directory.TmpDirExportImportIOProvider; +import org.keycloak.models.Config; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * Test for full export of data from JPA and import them to Mongo. Using "directory" provider + * + * @author Marek Posolda + */ +public class JPAToMongoExportImportTest extends ExportImportTestBase { + + @Override + protected String getExportModelProvider() { + return "jpa"; + } + + @Override + protected String getImportModelProvider() { + return "mongo"; + } + + @Override + protected void exportModel(KeycloakSessionFactory factory) { + Config.setExportImportAction(ExportImportProviderImpl.ACTION_EXPORT); + Config.setExportImportProvider(TmpDirExportImportIOProvider.PROVIDER_ID); + getExportImportProvider().checkExportImport(factory); + } + + @Override + protected void importModel(KeycloakSessionFactory factory) { + Config.setExportImportAction(ExportImportProviderImpl.ACTION_IMPORT); + Config.setExportImportProvider(TmpDirExportImportIOProvider.PROVIDER_ID); + getExportImportProvider().checkExportImport(factory); + } +} 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 new file mode 100644 index 0000000000..e715c9c662 --- /dev/null +++ b/export-import/export-import-impl/src/test/java/org/keycloak/exportimport/MongoToJPAExportImportTest.java @@ -0,0 +1,70 @@ +package org.keycloak.exportimport; + +import java.io.File; + +import org.junit.Assert; +import org.keycloak.exportimport.io.zip.EncryptedZIPIOProvider; +import org.keycloak.models.Config; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * Test for full export of data from Mongo and import them to JPA. Using export into encrypted ZIP and import from it + * + * @author Marek Posolda + */ +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) { + Config.setExportImportAction(ExportImportProviderImpl.ACTION_EXPORT); + Config.setExportImportProvider(EncryptedZIPIOProvider.PROVIDER_ID); + File zipFile = getZipFile(); + Config.setExportImportZipFile(zipFile.getAbsolutePath()); + Config.setExportImportZipPassword("password123"); + + if (zipFile.exists()) { + zipFile.delete(); + } + + new ExportImportProviderImpl().checkExportImport(factory); + } + + @Override + protected void importModel(KeycloakSessionFactory factory) { + Config.setExportImportAction(ExportImportProviderImpl.ACTION_IMPORT); + Config.setExportImportProvider(EncryptedZIPIOProvider.PROVIDER_ID); + File zipFile = getZipFile(); + Config.setExportImportZipFile(zipFile.getAbsolutePath()); + Config.setExportImportZipPassword("password-invalid"); + + // Try invalid password + try { + new ExportImportProviderImpl().checkExportImport(factory); + Assert.fail("Not expected to be here. Exception should be thrown"); + } catch (Exception e) {}; + + Config.setExportImportZipPassword("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"); + } +} diff --git a/export-import/pom.xml b/export-import/pom.xml new file mode 100644 index 0000000000..6df4145169 --- /dev/null +++ b/export-import/pom.xml @@ -0,0 +1,22 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-1-SNAPSHOT + ../pom.xml + + 4.0.0 + pom + + keycloak-export-import + Keycloak Export And Import + + + + export-import-api + export-import-impl + + + diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java index 1a4c52da31..8cc0bd8ee0 100644 --- a/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java +++ b/model/api/src/main/java/org/keycloak/models/AuthenticationLinkModel.java @@ -7,8 +7,10 @@ package org.keycloak.models; */ public class AuthenticationLinkModel { - private final String authProvider; - private final String authUserId; + private String authProvider; + private String authUserId; + + public AuthenticationLinkModel() {}; public AuthenticationLinkModel(String authProvider, String authUserId) { this.authProvider = authProvider; @@ -19,7 +21,15 @@ public class AuthenticationLinkModel { return authUserId; } + public void setAuthUserId(String authUserId) { + this.authUserId = authUserId; + } + public String getAuthProvider() { return authProvider; } + + public void setAuthProvider(String authProvider) { + this.authProvider = authProvider; + } } diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java index 7dea3c9698..8ef7abbf44 100644 --- a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java +++ b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java @@ -14,6 +14,8 @@ public class AuthenticationProviderModel { private boolean passwordUpdateSupported = true; private Map config; + public AuthenticationProviderModel() {}; + public AuthenticationProviderModel(String providerName, boolean passwordUpdateSupported, Map config) { this.providerName = providerName; this.passwordUpdateSupported = passwordUpdateSupported; diff --git a/model/api/src/main/java/org/keycloak/models/Config.java b/model/api/src/main/java/org/keycloak/models/Config.java index 2655eb3df0..e50ed9a71e 100644 --- a/model/api/src/main/java/org/keycloak/models/Config.java +++ b/model/api/src/main/java/org/keycloak/models/Config.java @@ -33,6 +33,16 @@ public class Config { public static final String TIMER_PROVIDER_KEY = "keycloak.timer"; public static final String TIMER_PROVIDER_DEFAULT = "basic"; + public static final String EXPORT_IMPORT_ACTION = "keycloak.migration.action"; + public static final String EXPORT_IMPORT_PROVIDER = "keycloak.migration.provider"; + public static final String EXPORT_IMPORT_PROVIDER_DEFAULT = "zip"; + // used for "directory" provider + public static final String EXPORT_IMPORT_DIR = "keycloak.migration.dir"; + // used for "zip" provider + public static final String EXPORT_IMPORT_ZIP_FILE = "keycloak.migration.zipFile"; + public static final String EXPORT_IMPORT_ZIP_PASSWORD = "keycloak.migration.zipPassword"; + + public static String getAdminRealm() { return System.getProperty(ADMIN_REALM_KEY, ADMIN_REALM_DEFAULT); } @@ -117,4 +127,45 @@ public class Config { System.setProperty(THEME_ADMIN_KEY, adminTheme); } + // EXPORT + IMPORT + + public static String getExportImportAction() { + return System.getProperty(EXPORT_IMPORT_ACTION); + } + + public static void setExportImportAction(String exportImportAction) { + System.setProperty(EXPORT_IMPORT_ACTION, exportImportAction); + } + + public static String getExportImportProvider() { + return System.getProperty(EXPORT_IMPORT_PROVIDER, EXPORT_IMPORT_PROVIDER_DEFAULT); + } + + public static void setExportImportProvider(String exportImportProvider) { + System.setProperty(EXPORT_IMPORT_PROVIDER, exportImportProvider); + } + + public static String getExportImportDir() { + return System.getProperty(EXPORT_IMPORT_DIR); + } + + public static void setExportImportDir(String exportImportDir) { + System.setProperty(EXPORT_IMPORT_DIR, exportImportDir); + } + + public static String getExportImportZipFile() { + return System.getProperty(EXPORT_IMPORT_ZIP_FILE); + } + + public static void setExportImportZipFile(String exportImportZipFile) { + System.setProperty(EXPORT_IMPORT_ZIP_FILE, exportImportZipFile); + } + + public static String getExportImportZipPassword() { + return System.getProperty(EXPORT_IMPORT_ZIP_PASSWORD); + } + + public static void setExportImportZipPassword(String exportImportZipPassword) { + System.setProperty(EXPORT_IMPORT_ZIP_PASSWORD, exportImportZipPassword); + } } diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java index 00b4db98d6..9aa55ee2d1 100755 --- a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java @@ -16,5 +16,7 @@ public interface KeycloakSession { List getRealms(); boolean removeRealm(String id); + void removeAllData(); + void close(); } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index 14d531ee4d..1c73b2ffb4 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -109,12 +109,18 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa void updateCredential(UserModel user, UserCredentialModel cred); + List getCredentialsDirectly(UserModel user); + + void updateCredentialDirectly(UserModel user, UserCredentialValueModel cred); + UserModel getUser(String name); UserModel getUserByEmail(String email); UserModel getUserById(String name); + UserModel addUser(String id, String username); + UserModel addUser(String username); boolean removeUser(String name); @@ -135,6 +141,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa ApplicationModel addApplication(String name); + ApplicationModel addApplication(String id, String name); + boolean removeApplication(String id); ApplicationModel getApplicationById(String id); @@ -160,12 +168,13 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa void setSocial(boolean social); - public boolean isUpdateProfileOnInitialSocialLogin(); + boolean isUpdateProfileOnInitialSocialLogin(); - public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin); + void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin); - public UsernameLoginFailureModel getUserLoginFailure(String username); + UsernameLoginFailureModel getUserLoginFailure(String username); UsernameLoginFailureModel addUserLoginFailure(String username); + List getAllUserLoginFailures(); List getUsers(); @@ -175,6 +184,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa OAuthClientModel addOAuthClient(String name); + OAuthClientModel addOAuthClient(String id, String name); + OAuthClientModel getOAuthClient(String name); OAuthClientModel getOAuthClientById(String id); boolean removeOAuthClient(String id); diff --git a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java index 62ecfdb7f4..2cad37153e 100755 --- a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java +++ b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java @@ -12,6 +12,8 @@ public interface RoleContainerModel { RoleModel addRole(String name); + RoleModel addRole(String id, String name); + boolean removeRole(RoleModel role); Set getRoles(); diff --git a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java index 76e892910a..802fa9e996 100755 --- a/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java +++ b/model/api/src/main/java/org/keycloak/models/SocialLinkModel.java @@ -9,6 +9,8 @@ public class SocialLinkModel { private String socialProvider; private String socialUsername; + public SocialLinkModel() {}; + public SocialLinkModel(String socialProvider, String socialUserId, String socialUsername) { this.socialUserId = socialUserId; this.socialProvider = socialProvider; diff --git a/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java new file mode 100644 index 0000000000..370227460f --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/UserCredentialValueModel.java @@ -0,0 +1,46 @@ +package org.keycloak.models; + +/** + * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import) + * + * @author Marek Posolda + */ +public class UserCredentialValueModel { + + private String type; + private String value; + private String device; + private byte[] salt; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public byte[] getSalt() { + return salt; + } + + public void setSalt(byte[] salt) { + this.salt = salt; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AbstractIdentifiableEntity.java similarity index 64% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/AbstractIdentifiableEntity.java index 86e6e2e31f..504a496176 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AbstractIdentifiableEntity.java @@ -1,11 +1,11 @@ -package org.keycloak.models.mongo.api; - -import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; +package org.keycloak.models.entities; /** + * Base for the identifiable entity + * * @author Marek Posolda */ -public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity { +public class AbstractIdentifiableEntity { private String id; @@ -17,11 +17,6 @@ public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity this.id = id; } - @Override - public void afterRemove(MongoStoreInvocationContext invocationContext) { - // Empty by default - } - @Override public boolean equals(Object o) { if (o == this) return true; @@ -30,7 +25,7 @@ public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity if (o == null || getClass() != o.getClass()) return false; - AbstractMongoIdentifiableEntity that = (AbstractMongoIdentifiableEntity) o; + AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o; if (!getId().equals(that.getId())) return false; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java old mode 100755 new mode 100644 similarity index 58% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java index 5f9efb6655..95fefb9a68 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ApplicationEntity.java @@ -1,20 +1,11 @@ -package org.keycloak.models.mongo.keycloak.entities; +package org.keycloak.models.entities; import java.util.ArrayList; import java.util.List; -import com.mongodb.DBObject; -import com.mongodb.QueryBuilder; -import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoIndex; -import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; - /** * @author Marek Posolda */ -@MongoCollection(collectionName = "applications") -@MongoIndex(fields = { "realmId", "name" }, unique = true) public class ApplicationEntity extends ClientEntity { private boolean surrogateAuthRequired; @@ -25,7 +16,6 @@ public class ApplicationEntity extends ClientEntity { // We are using names of defaultRoles (not ids) private List defaultRoles = new ArrayList(); - @MongoField public boolean isSurrogateAuthRequired() { return surrogateAuthRequired; } @@ -34,7 +24,6 @@ public class ApplicationEntity extends ClientEntity { this.surrogateAuthRequired = surrogateAuthRequired; } - @MongoField public String getManagementUrl() { return managementUrl; } @@ -43,7 +32,6 @@ public class ApplicationEntity extends ClientEntity { this.managementUrl = managementUrl; } - @MongoField public String getBaseUrl() { return baseUrl; } @@ -52,16 +40,6 @@ public class ApplicationEntity extends ClientEntity { this.baseUrl = baseUrl; } - @MongoField - public List getDefaultRoles() { - return defaultRoles; - } - - public void setDefaultRoles(List defaultRoles) { - this.defaultRoles = defaultRoles; - } - - @MongoField public boolean isBearerOnly() { return bearerOnly; } @@ -70,12 +48,12 @@ public class ApplicationEntity extends ClientEntity { this.bearerOnly = bearerOnly; } - @Override - public void afterRemove(MongoStoreInvocationContext context) { - // Remove all roles, which belongs to this application - DBObject query = new QueryBuilder() - .and("applicationId").is(getId()) - .get(); - context.getMongoStore().removeEntities(RoleEntity.class, query, context); + public List getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; } } + diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationLinkEntity.java similarity index 66% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/AuthenticationLinkEntity.java index 54ab0a7055..8475b54a47 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationLinkEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationLinkEntity.java @@ -1,17 +1,13 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; +package org.keycloak.models.entities; /** * @author Marek Posolda */ -public class AuthenticationLinkEntity implements MongoEntity { +public class AuthenticationLinkEntity { private String authUserId; private String authProvider; - @MongoField public String getAuthUserId() { return authUserId; } @@ -20,7 +16,6 @@ public class AuthenticationLinkEntity implements MongoEntity { this.authUserId = authUserId; } - @MongoField public String getAuthProvider() { return authProvider; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationProviderEntity.java similarity index 75% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/AuthenticationProviderEntity.java index cc4f2bfef4..147885d1f0 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/AuthenticationProviderEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationProviderEntity.java @@ -1,20 +1,16 @@ -package org.keycloak.models.mongo.keycloak.entities; +package org.keycloak.models.entities; import java.util.Map; -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; - /** * @author Marek Posolda */ -public class AuthenticationProviderEntity implements MongoEntity { +public class AuthenticationProviderEntity { private String providerName; private boolean passwordUpdateSupported; private Map config; - @MongoField public String getProviderName() { return providerName; } @@ -23,7 +19,6 @@ public class AuthenticationProviderEntity implements MongoEntity { this.providerName = providerName; } - @MongoField public boolean isPasswordUpdateSupported() { return passwordUpdateSupported; } @@ -32,7 +27,6 @@ public class AuthenticationProviderEntity implements MongoEntity { this.passwordUpdateSupported = passwordUpdateSupported; } - @MongoField public Map getConfig() { return config; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java similarity index 82% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ClientEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index 9b4e019ad6..e7439fc523 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -1,16 +1,12 @@ -package org.keycloak.models.mongo.keycloak.entities; +package org.keycloak.models.entities; import java.util.ArrayList; import java.util.List; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; - /** * @author Marek Posolda */ -public class ClientEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { +public class ClientEntity extends AbstractIdentifiableEntity { private String name; private boolean enabled; @@ -25,7 +21,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon private List redirectUris = new ArrayList(); private List scopeIds = new ArrayList(); - @MongoField public String getName() { return name; } @@ -34,7 +29,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.name = name; } - @MongoField public boolean isEnabled() { return enabled; } @@ -43,7 +37,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.enabled = enabled; } - @MongoField public String getSecret() { return secret; } @@ -52,7 +45,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.secret = secret; } - @MongoField public long getAllowedClaimsMask() { return allowedClaimsMask; } @@ -61,7 +53,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.allowedClaimsMask = allowedClaimsMask; } - @MongoField public int getNotBefore() { return notBefore; } @@ -70,7 +61,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.notBefore = notBefore; } - @MongoField public boolean isPublicClient() { return publicClient; } @@ -79,7 +69,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.publicClient = publicClient; } - @MongoField public String getRealmId() { return realmId; } @@ -88,7 +77,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.realmId = realmId; } - @MongoField public List getWebOrigins() { return webOrigins; } @@ -97,7 +85,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.webOrigins = webOrigins; } - @MongoField public List getRedirectUris() { return redirectUris; } @@ -106,7 +93,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon this.redirectUris = redirectUris; } - @MongoField public List getScopeIds() { return scopeIds; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java similarity index 72% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java index 6ab322da22..82bc922a59 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/CredentialEntity.java @@ -1,19 +1,15 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; +package org.keycloak.models.entities; /** * @author Marek Posolda */ -public class CredentialEntity implements MongoEntity { +public class CredentialEntity { private String type; private String value; private String device; private byte[] salt; - @MongoField public String getType() { return type; } @@ -22,7 +18,6 @@ public class CredentialEntity implements MongoEntity { this.type = type; } - @MongoField public String getValue() { return value; } @@ -31,7 +26,6 @@ public class CredentialEntity implements MongoEntity { this.value = value; } - @MongoField public String getDevice() { return device; } @@ -40,7 +34,6 @@ public class CredentialEntity implements MongoEntity { this.device = device; } - @MongoField public byte[] getSalt() { return salt; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java new file mode 100644 index 0000000000..368fcf76ae --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/entities/OAuthClientEntity.java @@ -0,0 +1,7 @@ +package org.keycloak.models.entities; + +/** + * @author Marek Posolda + */ +public class OAuthClientEntity extends ClientEntity { +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java old mode 100755 new mode 100644 similarity index 77% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 7e536d045f..479d266677 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -1,429 +1,357 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import com.mongodb.DBObject; -import com.mongodb.QueryBuilder; -import org.keycloak.models.ApplicationModel; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoIndex; -import org.keycloak.models.mongo.api.MongoIndexes; -import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; - -import java.util.ArrayList; -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 Marek Posolda - */ -@MongoCollection(collectionName = "realms") -@MongoIndex(fields = { "name" }, unique = true) -public class RealmEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { - - private String name; - private boolean enabled; - private boolean sslNotRequired; - private boolean registrationAllowed; - private boolean rememberMe; - private boolean verifyEmail; - private boolean resetPasswordAllowed; - private boolean social; - private boolean updateProfileOnInitialSocialLogin; - private String passwordPolicy; - //--- brute force settings - private boolean bruteForceProtected; - private int maxFailureWaitSeconds; - private int minimumQuickLoginWaitSeconds; - private int waitIncrementSeconds; - private long quickLoginCheckMilliSeconds; - private int maxDeltaTimeSeconds; - private int failureFactor; - //--- end brute force settings - - private int centralLoginLifespan; - private int accessTokenLifespan; - private int accessCodeLifespan; - private int accessCodeLifespanUserAction; - private int refreshTokenLifespan; - private int notBefore; - - private String publicKeyPem; - private String privateKeyPem; - - private String loginTheme; - private String accountTheme; - - // We are using names of defaultRoles (not ids) - private List defaultRoles = new ArrayList(); - - private List requiredCredentials = new ArrayList(); - private List authenticationProviders = new ArrayList(); - - private Map smtpConfig = new HashMap(); - private Map socialConfig = new HashMap(); - private Map ldapServerConfig; - - private boolean auditEnabled; - private long auditExpiration; - private List auditListeners = new ArrayList(); - - private String adminAppId; - - @MongoField - public String getName() { - return name; - } - - public void setName(String realmName) { - this.name = realmName; - } - - @MongoField - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - @MongoField - public boolean isSslNotRequired() { - return sslNotRequired; - } - - public void setSslNotRequired(boolean sslNotRequired) { - this.sslNotRequired = sslNotRequired; - } - - @MongoField - public boolean isRegistrationAllowed() { - return registrationAllowed; - } - - public void setRegistrationAllowed(boolean registrationAllowed) { - this.registrationAllowed = registrationAllowed; - } - - @MongoField - public boolean isRememberMe() { - return rememberMe; - } - - public void setRememberMe(boolean rememberMe) { - this.rememberMe = rememberMe; - } - - @MongoField - public boolean isVerifyEmail() { - return verifyEmail; - } - - public void setVerifyEmail(boolean verifyEmail) { - this.verifyEmail = verifyEmail; - } - - @MongoField - public boolean isResetPasswordAllowed() { - return resetPasswordAllowed; - } - - public void setResetPasswordAllowed(boolean resetPasswordAllowed) { - this.resetPasswordAllowed = resetPasswordAllowed; - } - - @MongoField - public boolean isSocial() { - return social; - } - - public void setSocial(boolean social) { - this.social = social; - } - - @MongoField - public boolean isUpdateProfileOnInitialSocialLogin() { - return updateProfileOnInitialSocialLogin; - } - - public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) { - this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin; - } - - @MongoField - public boolean isBruteForceProtected() { - return bruteForceProtected; - } - - public void setBruteForceProtected(boolean bruteForceProtected) { - this.bruteForceProtected = bruteForceProtected; - } - - @MongoField - public int getMaxFailureWaitSeconds() { - return maxFailureWaitSeconds; - } - - public void setMaxFailureWaitSeconds(int maxFailureWaitSeconds) { - this.maxFailureWaitSeconds = maxFailureWaitSeconds; - } - - @MongoField - public int getMinimumQuickLoginWaitSeconds() { - return minimumQuickLoginWaitSeconds; - } - - public void setMinimumQuickLoginWaitSeconds(int minimumQuickLoginWaitSeconds) { - this.minimumQuickLoginWaitSeconds = minimumQuickLoginWaitSeconds; - } - - @MongoField - public int getWaitIncrementSeconds() { - return waitIncrementSeconds; - } - - public void setWaitIncrementSeconds(int waitIncrementSeconds) { - this.waitIncrementSeconds = waitIncrementSeconds; - } - - @MongoField - public long getQuickLoginCheckMilliSeconds() { - return quickLoginCheckMilliSeconds; - } - - public void setQuickLoginCheckMilliSeconds(long quickLoginCheckMilliSeconds) { - this.quickLoginCheckMilliSeconds = quickLoginCheckMilliSeconds; - } - - @MongoField - public int getMaxDeltaTimeSeconds() { - return maxDeltaTimeSeconds; - } - - public void setMaxDeltaTimeSeconds(int maxDeltaTimeSeconds) { - this.maxDeltaTimeSeconds = maxDeltaTimeSeconds; - } - - @MongoField - public int getFailureFactor() { - return failureFactor; - } - - public void setFailureFactor(int failureFactor) { - this.failureFactor = failureFactor; - } - - @MongoField - public String getPasswordPolicy() { - return passwordPolicy; - } - - public void setPasswordPolicy(String passwordPolicy) { - this.passwordPolicy = passwordPolicy; - } - - @MongoField - public int getNotBefore() { - return notBefore; - } - - public void setNotBefore(int notBefore) { - this.notBefore = notBefore; - } - - @MongoField - public int getCentralLoginLifespan() { - return centralLoginLifespan; - } - - public void setCentralLoginLifespan(int centralLoginLifespan) { - this.centralLoginLifespan = centralLoginLifespan; - } - - @MongoField - public int getAccessTokenLifespan() { - return accessTokenLifespan; - } - - public void setAccessTokenLifespan(int accessTokenLifespan) { - this.accessTokenLifespan = accessTokenLifespan; - } - - @MongoField - public int getRefreshTokenLifespan() { - return refreshTokenLifespan; - } - - public void setRefreshTokenLifespan(int refreshTokenLifespan) { - this.refreshTokenLifespan = refreshTokenLifespan; - } - - @MongoField - public int getAccessCodeLifespan() { - return accessCodeLifespan; - } - - public void setAccessCodeLifespan(int accessCodeLifespan) { - this.accessCodeLifespan = accessCodeLifespan; - } - - @MongoField - public int getAccessCodeLifespanUserAction() { - return accessCodeLifespanUserAction; - } - - public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { - this.accessCodeLifespanUserAction = accessCodeLifespanUserAction; - } - - @MongoField - public String getPublicKeyPem() { - return publicKeyPem; - } - - public void setPublicKeyPem(String publicKeyPem) { - this.publicKeyPem = publicKeyPem; - } - - @MongoField - public String getPrivateKeyPem() { - return privateKeyPem; - } - - public void setPrivateKeyPem(String privateKeyPem) { - this.privateKeyPem = privateKeyPem; - } - - @MongoField - public String getLoginTheme() { - return loginTheme; - } - - public void setLoginTheme(String loginTheme) { - this.loginTheme = loginTheme; - } - - @MongoField - public String getAccountTheme() { - return accountTheme; - } - - public void setAccountTheme(String accountTheme) { - this.accountTheme = accountTheme; - } - - @MongoField - public List getDefaultRoles() { - return defaultRoles; - } - - public void setDefaultRoles(List defaultRoles) { - this.defaultRoles = defaultRoles; - } - - @MongoField - public List getRequiredCredentials() { - return requiredCredentials; - } - - public void setRequiredCredentials(List requiredCredentials) { - this.requiredCredentials = requiredCredentials; - } - - @MongoField - public List getAuthenticationProviders() { - return authenticationProviders; - } - - public void setAuthenticationProviders(List authenticationProviders) { - this.authenticationProviders = authenticationProviders; - } - - @MongoField - public Map getSmtpConfig() { - return smtpConfig; - } - - public void setSmtpConfig(Map smptConfig) { - this.smtpConfig = smptConfig; - } - - @MongoField - public Map getSocialConfig() { - return socialConfig; - } - - public void setSocialConfig(Map socialConfig) { - this.socialConfig = socialConfig; - } - - @MongoField - public Map getLdapServerConfig() { - return ldapServerConfig; - } - - public void setLdapServerConfig(Map ldapServerConfig) { - this.ldapServerConfig = ldapServerConfig; - } - - @MongoField - public boolean isAuditEnabled() { - return auditEnabled; - } - - public void setAuditEnabled(boolean auditEnabled) { - this.auditEnabled = auditEnabled; - } - - @MongoField - public long getAuditExpiration() { - return auditExpiration; - } - - public void setAuditExpiration(long auditExpiration) { - this.auditExpiration = auditExpiration; - } - - @MongoField - public List getAuditListeners() { - return auditListeners; - } - - public void setAuditListeners(List auditListeners) { - this.auditListeners = auditListeners; - } - - @MongoField - public String getAdminAppId() { - return adminAppId; - } - - public void setAdminAppId(String adminAppId) { - this.adminAppId = adminAppId; - } - - @Override - public void afterRemove(MongoStoreInvocationContext context) { - DBObject query = new QueryBuilder() - .and("realmId").is(getId()) - .get(); - - // Remove all users of this realm - context.getMongoStore().removeEntities(UserEntity.class, query, context); - - // Remove all roles of this realm - context.getMongoStore().removeEntities(RoleEntity.class, query, context); - - // Remove all applications of this realm - context.getMongoStore().removeEntities(ApplicationEntity.class, query, context); - - // Remove all clients of this realm - context.getMongoStore().removeEntities(OAuthClientEntity.class, query, context); - } -} +package org.keycloak.models.entities; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class RealmEntity extends AbstractIdentifiableEntity { + + private String name; + private boolean enabled; + private boolean sslNotRequired; + private boolean registrationAllowed; + private boolean rememberMe; + private boolean verifyEmail; + private boolean resetPasswordAllowed; + private boolean social; + private boolean updateProfileOnInitialSocialLogin; + private String passwordPolicy; + //--- brute force settings + private boolean bruteForceProtected; + private int maxFailureWaitSeconds; + private int minimumQuickLoginWaitSeconds; + private int waitIncrementSeconds; + private long quickLoginCheckMilliSeconds; + private int maxDeltaTimeSeconds; + private int failureFactor; + //--- end brute force settings + + private int centralLoginLifespan; + private int accessTokenLifespan; + private int accessCodeLifespan; + private int accessCodeLifespanUserAction; + private int refreshTokenLifespan; + private int notBefore; + + private String publicKeyPem; + private String privateKeyPem; + + private String loginTheme; + private String accountTheme; + + // We are using names of defaultRoles (not ids) + private List defaultRoles = new ArrayList(); + + private List requiredCredentials = new ArrayList(); + private List authenticationProviders = new ArrayList(); + + private Map smtpConfig = new HashMap(); + private Map socialConfig = new HashMap(); + private Map ldapServerConfig; + + private boolean auditEnabled; + private long auditExpiration; + private List auditListeners = new ArrayList(); + + private String adminAppId; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isSslNotRequired() { + return sslNotRequired; + } + + public void setSslNotRequired(boolean sslNotRequired) { + this.sslNotRequired = sslNotRequired; + } + + public boolean isRegistrationAllowed() { + return registrationAllowed; + } + + public void setRegistrationAllowed(boolean registrationAllowed) { + this.registrationAllowed = registrationAllowed; + } + + public boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(boolean rememberMe) { + this.rememberMe = rememberMe; + } + + public boolean isVerifyEmail() { + return verifyEmail; + } + + public void setVerifyEmail(boolean verifyEmail) { + this.verifyEmail = verifyEmail; + } + + public boolean isResetPasswordAllowed() { + return resetPasswordAllowed; + } + + public void setResetPasswordAllowed(boolean resetPasswordAllowed) { + this.resetPasswordAllowed = resetPasswordAllowed; + } + + public boolean isSocial() { + return social; + } + + public void setSocial(boolean social) { + this.social = social; + } + + public boolean isUpdateProfileOnInitialSocialLogin() { + return updateProfileOnInitialSocialLogin; + } + + public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) { + this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin; + } + + public String getPasswordPolicy() { + return passwordPolicy; + } + + public void setPasswordPolicy(String passwordPolicy) { + this.passwordPolicy = passwordPolicy; + } + + public boolean isBruteForceProtected() { + return bruteForceProtected; + } + + public void setBruteForceProtected(boolean bruteForceProtected) { + this.bruteForceProtected = bruteForceProtected; + } + + public int getMaxFailureWaitSeconds() { + return maxFailureWaitSeconds; + } + + public void setMaxFailureWaitSeconds(int maxFailureWaitSeconds) { + this.maxFailureWaitSeconds = maxFailureWaitSeconds; + } + + public int getMinimumQuickLoginWaitSeconds() { + return minimumQuickLoginWaitSeconds; + } + + public void setMinimumQuickLoginWaitSeconds(int minimumQuickLoginWaitSeconds) { + this.minimumQuickLoginWaitSeconds = minimumQuickLoginWaitSeconds; + } + + public int getWaitIncrementSeconds() { + return waitIncrementSeconds; + } + + public void setWaitIncrementSeconds(int waitIncrementSeconds) { + this.waitIncrementSeconds = waitIncrementSeconds; + } + + public long getQuickLoginCheckMilliSeconds() { + return quickLoginCheckMilliSeconds; + } + + public void setQuickLoginCheckMilliSeconds(long quickLoginCheckMilliSeconds) { + this.quickLoginCheckMilliSeconds = quickLoginCheckMilliSeconds; + } + + public int getMaxDeltaTimeSeconds() { + return maxDeltaTimeSeconds; + } + + public void setMaxDeltaTimeSeconds(int maxDeltaTimeSeconds) { + this.maxDeltaTimeSeconds = maxDeltaTimeSeconds; + } + + public int getFailureFactor() { + return failureFactor; + } + + public void setFailureFactor(int failureFactor) { + this.failureFactor = failureFactor; + } + + public int getCentralLoginLifespan() { + return centralLoginLifespan; + } + + public void setCentralLoginLifespan(int centralLoginLifespan) { + this.centralLoginLifespan = centralLoginLifespan; + } + + public int getAccessTokenLifespan() { + return accessTokenLifespan; + } + + public void setAccessTokenLifespan(int accessTokenLifespan) { + this.accessTokenLifespan = accessTokenLifespan; + } + + public int getAccessCodeLifespan() { + return accessCodeLifespan; + } + + public void setAccessCodeLifespan(int accessCodeLifespan) { + this.accessCodeLifespan = accessCodeLifespan; + } + + public int getAccessCodeLifespanUserAction() { + return accessCodeLifespanUserAction; + } + + public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { + this.accessCodeLifespanUserAction = accessCodeLifespanUserAction; + } + + public int getRefreshTokenLifespan() { + return refreshTokenLifespan; + } + + public void setRefreshTokenLifespan(int refreshTokenLifespan) { + this.refreshTokenLifespan = refreshTokenLifespan; + } + + public int getNotBefore() { + return notBefore; + } + + public void setNotBefore(int notBefore) { + this.notBefore = notBefore; + } + + public String getPublicKeyPem() { + return publicKeyPem; + } + + public void setPublicKeyPem(String publicKeyPem) { + this.publicKeyPem = publicKeyPem; + } + + public String getPrivateKeyPem() { + return privateKeyPem; + } + + public void setPrivateKeyPem(String privateKeyPem) { + this.privateKeyPem = privateKeyPem; + } + + public String getLoginTheme() { + return loginTheme; + } + + public void setLoginTheme(String loginTheme) { + this.loginTheme = loginTheme; + } + + public String getAccountTheme() { + return accountTheme; + } + + public void setAccountTheme(String accountTheme) { + this.accountTheme = accountTheme; + } + + public List getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; + } + + public List getRequiredCredentials() { + return requiredCredentials; + } + + public void setRequiredCredentials(List requiredCredentials) { + this.requiredCredentials = requiredCredentials; + } + + public List getAuthenticationProviders() { + return authenticationProviders; + } + + public void setAuthenticationProviders(List authenticationProviders) { + this.authenticationProviders = authenticationProviders; + } + + public Map getSmtpConfig() { + return smtpConfig; + } + + public void setSmtpConfig(Map smtpConfig) { + this.smtpConfig = smtpConfig; + } + + public Map getSocialConfig() { + return socialConfig; + } + + public void setSocialConfig(Map socialConfig) { + this.socialConfig = socialConfig; + } + + public Map getLdapServerConfig() { + return ldapServerConfig; + } + + public void setLdapServerConfig(Map ldapServerConfig) { + this.ldapServerConfig = ldapServerConfig; + } + + 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; + } + + public String getAdminAppId() { + return adminAppId; + } + + public void setAdminAppId(String adminAppId) { + this.adminAppId = adminAppId; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RequiredCredentialEntity.java similarity index 73% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/RequiredCredentialEntity.java index f39e327f43..a4b31b0eb8 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RequiredCredentialEntity.java @@ -1,19 +1,15 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; +package org.keycloak.models.entities; /** * @author Marek Posolda */ -public class RequiredCredentialEntity implements MongoEntity { +public class RequiredCredentialEntity { private String type; private boolean input; private boolean secret; private String formLabel; - @MongoField public String getType() { return type; } @@ -22,7 +18,6 @@ public class RequiredCredentialEntity implements MongoEntity { this.type = type; } - @MongoField public boolean isInput() { return input; } @@ -31,7 +26,6 @@ public class RequiredCredentialEntity implements MongoEntity { this.input = input; } - @MongoField public boolean isSecret() { return secret; } @@ -40,7 +34,6 @@ public class RequiredCredentialEntity implements MongoEntity { this.secret = secret; } - @MongoField public String getFormLabel() { return formLabel; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java new file mode 100644 index 0000000000..a961c70a78 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java @@ -0,0 +1,57 @@ +package org.keycloak.models.entities; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public class RoleEntity extends AbstractIdentifiableEntity { + + private String name; + private String description; + + private List compositeRoleIds; + + private String realmId; + private String applicationId; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getCompositeRoleIds() { + return compositeRoleIds; + } + + public void setCompositeRoleIds(List compositeRoleIds) { + this.compositeRoleIds = compositeRoleIds; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java b/model/api/src/main/java/org/keycloak/models/entities/SocialLinkEntity.java similarity index 86% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/SocialLinkEntity.java index a000412974..858968f98f 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/SocialLinkEntity.java @@ -1,18 +1,14 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; +package org.keycloak.models.entities; /** * @author Marek Posolda */ -public class SocialLinkEntity implements MongoEntity { +public class SocialLinkEntity { private String socialUserId; private String socialUsername; private String socialProvider; - @MongoField public String getSocialUserId() { return socialUserId; } @@ -21,7 +17,6 @@ public class SocialLinkEntity implements MongoEntity { this.socialUserId = socialUserId; } - @MongoField public String getSocialUsername() { return socialUsername; } @@ -30,7 +25,6 @@ public class SocialLinkEntity implements MongoEntity { this.socialUsername = socialUsername; } - @MongoField public String getSocialProvider() { return socialProvider; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java old mode 100755 new mode 100644 similarity index 68% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/UserEntity.java index bd5338c7ed..f1248b087d --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java @@ -1,26 +1,15 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoIndex; -import org.keycloak.models.mongo.api.MongoIndexes; +package org.keycloak.models.entities; import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.keycloak.models.UserModel; + /** * @author Marek Posolda */ -@MongoCollection(collectionName = "users") -@MongoIndexes({ - @MongoIndex(fields = { "realmId", "loginName" }, unique = true), - @MongoIndex(fields = { "emailIndex" }, unique = true, sparse = true), -}) -public class UserEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { +public class UserEntity extends AbstractIdentifiableEntity { private String loginName; private String firstName; @@ -30,11 +19,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo private boolean totp; private boolean enabled; private int notBefore; - private int failedLoginNotBefore; - private int numFailures; - private long lastFailure; - private String lastIPFailure; - private String realmId; @@ -46,7 +30,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo private List socialLinks; private AuthenticationLinkEntity authenticationLink; - @MongoField public String getLoginName() { return loginName; } @@ -55,7 +38,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.loginName = loginName; } - @MongoField public String getFirstName() { return firstName; } @@ -64,7 +46,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.firstName = firstName; } - @MongoField public String getLastName() { return lastName; } @@ -73,7 +54,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.lastName = lastName; } - @MongoField public String getEmail() { return email; } @@ -82,16 +62,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.email = email; } - @MongoField - // TODO This is required as Mongo doesn't support sparse indexes with compound keys (see https://jira.mongodb.org/browse/SERVER-2193) - public String getEmailIndex() { - return email != null ? realmId + "//" + email : null; - } - - public void setEmailIndex(String ignored) { - } - - @MongoField public boolean isEmailVerified() { return emailVerified; } @@ -100,16 +70,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.emailVerified = emailVerified; } - @MongoField - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - @MongoField public boolean isTotp() { return totp; } @@ -118,7 +78,14 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.totp = totp; } - @MongoField + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public int getNotBefore() { return notBefore; } @@ -127,7 +94,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.notBefore = notBefore; } - @MongoField public String getRealmId() { return realmId; } @@ -136,7 +102,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.realmId = realmId; } - @MongoField public List getRoleIds() { return roleIds; } @@ -145,8 +110,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.roleIds = roleIds; } - - @MongoField public Map getAttributes() { return attributes; } @@ -155,7 +118,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.attributes = attributes; } - @MongoField public List getRequiredActions() { return requiredActions; } @@ -164,7 +126,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.requiredActions = requiredActions; } - @MongoField public List getCredentials() { return credentials; } @@ -173,7 +134,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.credentials = credentials; } - @MongoField public List getSocialLinks() { return socialLinks; } @@ -182,7 +142,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.socialLinks = socialLinks; } - @MongoField public AuthenticationLinkEntity getAuthenticationLink() { return authenticationLink; } @@ -191,3 +150,4 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo this.authenticationLink = authenticationLink; } } + diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UsernameLoginFailureEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UsernameLoginFailureEntity.java old mode 100755 new mode 100644 similarity index 64% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UsernameLoginFailureEntity.java rename to model/api/src/main/java/org/keycloak/models/entities/UsernameLoginFailureEntity.java index 4852626f4d..e25e717408 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UsernameLoginFailureEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/UsernameLoginFailureEntity.java @@ -1,76 +1,63 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.MongoField; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -@MongoCollection(collectionName = "userFailures") -public class UsernameLoginFailureEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { - private String username; - private int failedLoginNotBefore; - private int numFailures; - private long lastFailure; - private String lastIPFailure; - - - private String realmId; - - @MongoField - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - @MongoField - public int getFailedLoginNotBefore() { - return failedLoginNotBefore; - } - - public void setFailedLoginNotBefore(int failedLoginNotBefore) { - this.failedLoginNotBefore = failedLoginNotBefore; - } - - @MongoField - public int getNumFailures() { - return numFailures; - } - - public void setNumFailures(int numFailures) { - this.numFailures = numFailures; - } - - @MongoField - public long getLastFailure() { - return lastFailure; - } - - public void setLastFailure(long lastFailure) { - this.lastFailure = lastFailure; - } - - @MongoField - public String getLastIPFailure() { - return lastIPFailure; - } - - public void setLastIPFailure(String lastIPFailure) { - this.lastIPFailure = lastIPFailure; - } - - @MongoField - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; - } -} +package org.keycloak.models.entities; + +/** + * @author Marek Posolda + */ +public class UsernameLoginFailureEntity extends AbstractIdentifiableEntity { + + private String username; + private int failedLoginNotBefore; + private int numFailures; + private long lastFailure; + private String lastIPFailure; + + private String realmId; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getFailedLoginNotBefore() { + return failedLoginNotBefore; + } + + public void setFailedLoginNotBefore(int failedLoginNotBefore) { + this.failedLoginNotBefore = failedLoginNotBefore; + } + + public int getNumFailures() { + return numFailures; + } + + public void setNumFailures(int numFailures) { + this.numFailures = numFailures; + } + + public long getLastFailure() { + return lastFailure; + } + + public void setLastFailure(long lastFailure) { + this.lastFailure = lastFailure; + } + + public String getLastIPFailure() { + return lastIPFailure; + } + + public void setLastIPFailure(String lastIPFailure) { + this.lastIPFailure = lastIPFailure; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java new file mode 100644 index 0000000000..51e2a3aa5c --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/AnnotatedPropertyCriteria.java @@ -0,0 +1,23 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * A criteria that matches a property based on its annotations + * + * @see PropertyCriteria + */ +public class AnnotatedPropertyCriteria implements PropertyCriteria { + private final Class annotationClass; + + public AnnotatedPropertyCriteria(Class annotationClass) { + this.annotationClass = annotationClass; + } + + @Override + public boolean methodMatches(Method m) { + return m.isAnnotationPresent(annotationClass); + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java new file mode 100644 index 0000000000..868fdbae50 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodProperty.java @@ -0,0 +1,8 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.Method; + +public interface MethodProperty extends Property { + + Method getAnnotatedElement(); +} \ No newline at end of file diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java new file mode 100644 index 0000000000..16cc1657a3 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/MethodPropertyImpl.java @@ -0,0 +1,220 @@ +package org.keycloak.models.utils.reflection; + +import java.beans.Introspector; +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * A bean property based on the value represented by a getter/setter method pair + */ +class MethodPropertyImpl implements MethodProperty { + private static final String GETTER_METHOD_PREFIX = "get"; + private static final String SETTER_METHOD_PREFIX = "set"; + private static final String BOOLEAN_GETTER_METHOD_PREFIX = "is"; + + private static final int GETTER_METHOD_PREFIX_LENGTH = GETTER_METHOD_PREFIX.length(); + private static final int SETTER_METHOD_PREFIX_LENGTH = SETTER_METHOD_PREFIX.length(); + private static final int BOOLEAN_GETTER_METHOD_PREFIX_LENGTH = BOOLEAN_GETTER_METHOD_PREFIX.length(); + + private final Method getterMethod; + private final String propertyName; + private final Method setterMethod; + + public MethodPropertyImpl(Method method) { + final String accessorMethodPrefix; + final String propertyNameInAccessorMethod; + + if (method.getName().startsWith(GETTER_METHOD_PREFIX)) { + if (method.getReturnType() == Void.TYPE) { + throw new IllegalArgumentException( + "Invalid accessor method, must have return value if starts with 'get'. Method: " + method); + } else if (method.getParameterTypes().length > 0) { + throw new IllegalArgumentException( + "Invalid accessor method, must have zero arguments if starts with 'get'. Method: " + method); + } + propertyNameInAccessorMethod = method.getName().substring(GETTER_METHOD_PREFIX_LENGTH); + accessorMethodPrefix = GETTER_METHOD_PREFIX; + } else if (method.getName().startsWith(SETTER_METHOD_PREFIX)) { + if (method.getReturnType() != Void.TYPE) { + throw new IllegalArgumentException( + "Invalid accessor method, must not have return value if starts with 'set'. Method: " + method); + } else if (method.getParameterTypes().length != 1) { + throw new IllegalArgumentException( + "Invalid accessor method, must have one argument if starts with 'set'. Method: " + method); + } + propertyNameInAccessorMethod = method.getName().substring(SETTER_METHOD_PREFIX_LENGTH); + accessorMethodPrefix = SETTER_METHOD_PREFIX; + } else if (method.getName().startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) { + if (method.getReturnType() != Boolean.TYPE || !method.getReturnType().isPrimitive()) { + throw new IllegalArgumentException( + "Invalid accessor method, must return boolean primitive if starts with 'is'. Method: " + + method); + } + propertyNameInAccessorMethod = method.getName().substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH); + accessorMethodPrefix = BOOLEAN_GETTER_METHOD_PREFIX; + } else { + throw new IllegalArgumentException( + "Invalid accessor method, must start with 'get', 'set' or 'is'. " + "Method: " + method); + } + + if (propertyNameInAccessorMethod.length() == 0 || + !Character.isUpperCase(propertyNameInAccessorMethod.charAt(0))) { + throw new IllegalArgumentException("Invalid accessor method, prefix '" + accessorMethodPrefix + + "' must be followed a non-empty property name, capitalized. Method: " + method); + } + + this.propertyName = Introspector.decapitalize(propertyNameInAccessorMethod); + this.getterMethod = getGetterMethod(method.getDeclaringClass(), propertyName); + this.setterMethod = getSetterMethod(method.getDeclaringClass(), propertyName); + } + + @Override + public String getName() { + return propertyName; + } + + @SuppressWarnings("unchecked") + @Override + public Class getJavaClass() { + return (Class) getterMethod.getReturnType(); + } + + @Override + public Type getBaseType() { + return getterMethod.getGenericReturnType(); + } + + @Override + public Method getAnnotatedElement() { + return getterMethod; + } + + @Override + public Member getMember() { + return getterMethod; + } + + @Override + public V getValue(Object instance) { + if (getterMethod == null) { + throw new UnsupportedOperationException("Property " + + this.setterMethod.getDeclaringClass() + "." + propertyName + + " cannot be read, as there is no getter method."); + } + return Reflections.cast(Reflections.invokeMethod(getterMethod, instance)); + } + + @Override + public void setValue(Object instance, V value) { + if (setterMethod == null) { + // if the setter method is null may be because the declaring type is an interface which does not declare + // a setter method. We just check if the instance is assignable from the property declaring class and + // try to find a overridden method. + if (getDeclaringClass().isAssignableFrom(instance.getClass())) { + Method instanceSetterMethod = getSetterMethod(instance.getClass(), getName()); + + if (instanceSetterMethod != null) { + Reflections.invokeMethod(instanceSetterMethod, instance, value); + return; + } + } + + throw new UnsupportedOperationException("Property " + + this.getterMethod.getDeclaringClass() + "." + propertyName + + " is read only, as there is no setter method."); + } + Reflections.invokeMethod(setterMethod, instance, value); + } + + private static Method getSetterMethod(Class clazz, String name) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + String methodName = method.getName(); + if (methodName.startsWith(SETTER_METHOD_PREFIX) && method.getParameterTypes().length == 1) { + if (Introspector.decapitalize(methodName.substring(SETTER_METHOD_PREFIX_LENGTH)).equals(name)) { + return method; + } + } + } + return null; + } + + private static Method getGetterMethod(Class clazz, String name) { + for (Method method : clazz.getDeclaredMethods()) { + String methodName = method.getName(); + if (method.getParameterTypes().length == 0) { + if (methodName.startsWith(GETTER_METHOD_PREFIX)) { + if (Introspector.decapitalize(methodName.substring(GETTER_METHOD_PREFIX_LENGTH)).equals(name)) { + return method; + } + } else if (methodName.startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) { + if (Introspector.decapitalize( + methodName.substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH)).equals(name)) { + return method; + } + } + } + } + throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + name); + } + + @Override + public Class getDeclaringClass() { + return getterMethod.getDeclaringClass(); + } + + @Override + public boolean isReadOnly() { + return setterMethod == null; + } + + @Override + public void setAccessible() { + if (setterMethod != null) { + Reflections.setAccessible(setterMethod); + } + if (getterMethod != null) { + Reflections.setAccessible(getterMethod); + } + } + + @Override + public boolean isAnnotationPresent(final Class annotation) { + return getAnnotatedElement() != null && getAnnotatedElement().isAnnotationPresent(annotation); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (isReadOnly()) { + builder.append("read-only ").append(setterMethod.toString()).append("; "); + } + builder.append(getterMethod.toString()); + return builder.toString(); + } + + @Override + public int hashCode() { + int hash = 1; + hash = hash * 31 + (setterMethod == null ? 0 : setterMethod.hashCode()); + hash = hash * 31 + getterMethod.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof MethodPropertyImpl) { + MethodPropertyImpl that = (MethodPropertyImpl) obj; + if (this.setterMethod == null) { + return that.setterMethod == null && this.getterMethod.equals(that.getterMethod); + } else { + return this.setterMethod.equals(that.setterMethod) && this.getterMethod.equals(that.getterMethod); + } + } else { + return false; + } + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java new file mode 100644 index 0000000000..3348d8b876 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Properties.java @@ -0,0 +1,44 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +/** + * Utility class for working with JavaBean style properties + * + * @see Property + */ +public class Properties { + + private Properties() { + } + + /** + * Create a JavaBean style property from the specified method + * + * @param + * @param method + * + * @return + * + * @throws IllegalArgumentException if the method does not match JavaBean conventions + * @see http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html + */ + public static MethodProperty createProperty(Method method) { + return new MethodPropertyImpl(method); + } + + /** + * Indicates whether this method is a valid property method. + */ + public static boolean isProperty(Method method) { + try { + new MethodPropertyImpl(method); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} + diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java new file mode 100644 index 0000000000..6cf392df6c --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Property.java @@ -0,0 +1,105 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Member; +import java.lang.reflect.Type; + +/** + * A representation of a JavaBean style property + * + * @param the type of the properties value + * + * @see Properties + */ +public interface Property { + + /** + * Returns the name of the property. If the property is a field, then the field name is returned. Otherwise, if the + * property is a method, then the name that is returned is the getter method name without the "get" or "is" prefix, + * and a lower case first letter. + * + * @return The name of the property + */ + String getName(); + + /** + * Returns the property type + * + * @return The property type + */ + Type getBaseType(); + + /** + * Returns the property type + * + * @return The property type + */ + Class getJavaClass(); + + /** + * Get the element responsible for retrieving the property value + * + * @return + */ + AnnotatedElement getAnnotatedElement(); + + /** + * Get the member responsible for retrieving the property value + * + * @return + */ + Member getMember(); + + /** + * Returns the property value for the specified bean. The property to be returned is either a field or getter + * method. + * + * @param bean The bean to read the property from + * + * @return The property value + * + * @throws ClassCastException if the value is not of the type V + */ + V getValue(Object instance); + + /** + * This method sets the property value for a specified bean to the specified value. The property to be set is either + * a field or setter method. + * + * @param bean The bean containing the property to set + * @param value The new property value + */ + void setValue(Object instance, V value); + + /** + * Returns the class that declares the property + * + * @return + */ + Class getDeclaringClass(); + + /** + * Indicates whether this is a read-only property + * + * @return + */ + boolean isReadOnly(); + + /** + * Calls the setAccessible method on the underlying member(s). + *

+ * The operation should be performed within a {@link PrivilegedAction} + */ + void setAccessible(); + + /** + * Indicates whether the given annotation is defined for this property. This method will consider + * the annotations present in both field and accessor method. + * + * @param annotation The Annotation to check. + * + * @return True if the annotation is defined. Otherwise is false. + */ + boolean isAnnotationPresent(Class annotation); +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java new file mode 100644 index 0000000000..2d2c41d64e --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyCriteria.java @@ -0,0 +1,27 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + *

A property criteria can be used to filter the properties found by a {@link PropertyQuery}

+ * DeltaSpike provides a number of property queries ( {@link TypedPropertyCriteria}, {@link NamedPropertyCriteria} and + * {@link AnnotatedPropertyCriteria}), or you can create a custom query by implementing this interface.

+ * + * @see PropertyQuery#addCriteria(PropertyCriteria) + * @see PropertyQueries + * @see TypedPropertyCriteria + * @see AnnotatedPropertyCriteria + * @see NamedPropertyCriteria + */ +public interface PropertyCriteria { + + /** + * Tests whether the specified method matches the criteria + * + * @param m + * + * @return true if the method matches + */ + boolean methodMatches(Method m); +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java new file mode 100644 index 0000000000..d8cc8d0fcf --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQueries.java @@ -0,0 +1,25 @@ +package org.keycloak.models.utils.reflection; + +/** + * Utilities for working with property queries + * + * @see PropertyQuery + */ +public class PropertyQueries { + + private PropertyQueries() { + } + + /** + * Create a new {@link PropertyQuery} + * + * @param + * @param targetClass + * + * @return + */ + public static PropertyQuery createQuery(Class targetClass) { + return new PropertyQuery(targetClass); + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java new file mode 100644 index 0000000000..5aa38337a8 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/PropertyQuery.java @@ -0,0 +1,162 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Queries a target class for properties that match certain criteria. A property may either be a private or public + * field, declared by the target class or inherited from a superclass, or a public method declared by the target class + * or inherited from any of its superclasses. For properties that are exposed via a method, the property must be a + * JavaBean style property, i.e. it must provide both an accessor and mutator method according to the JavaBean + * specification.

This class is not thread-safe, however the result returned by the getResultList() method + * is.

+ * + * @see PropertyQueries + * @see PropertyCriteria + */ +public class PropertyQuery { + private final Class targetClass; + private final List criteria; + + PropertyQuery(Class targetClass) { + if (targetClass == null) { + throw new IllegalArgumentException("targetClass parameter may not be null"); + } + + this.targetClass = targetClass; + this.criteria = new ArrayList(); + } + + /** + * Add a criteria to query + * + * @param criteria the criteria to add + */ + public PropertyQuery addCriteria(PropertyCriteria criteria) { + this.criteria.add(criteria); + return this; + } + + /** + * Get the first result from the query, causing the query to be run. + * + * @return the first result, or null if there are no results + */ + public Property getFirstResult() { + Map> results = getResultList(); + return results.isEmpty() ? null : results.values().iterator().next(); + } + + /** + * Get the first result from the query that is not marked as read only, causing the query to be run. + * + * @return the first writable result, or null if there are no results + */ + public Property getFirstWritableResult() { + Map> results = getWritableResultList(); + return results.isEmpty() ? null : results.values().iterator().next(); + } + + /** + * Get a single result from the query, causing the query to be run. An exception is thrown if the query does not + * return exactly one result. + * + * @return the single result + * + * @throws RuntimeException if the query does not return exactly one result + */ + public Property getSingleResult() { + Map> results = getResultList(); + if (results.size() == 1) { + return results.values().iterator().next(); + } else if (results.isEmpty()) { + throw new RuntimeException( + "Expected one property match, but the criteria did not match any properties on " + + targetClass.getName()); + } else { + throw new RuntimeException("Expected one property match, but the criteria matched " + results.size() + + " properties on " + targetClass.getName()); + } + } + + /** + * Get a single result from the query that is not marked as read only, causing the query to be run. An exception is + * thrown if the query does not return exactly one result. + * + * @return the single writable result + * + * @throws RuntimeException if the query does not return exactly one result + */ + public Property getWritableSingleResult() { + Map> results = getWritableResultList(); + if (results.size() == 1) { + return results.values().iterator().next(); + } else if (results.isEmpty()) { + throw new RuntimeException( + "Expected one property match, but the criteria did not match any properties on " + + targetClass.getName()); + } else { + throw new RuntimeException("Expected one property match, but the criteria matched " + + results.size() + " properties on " + targetClass.getName()); + } + } + + /** + * Get the result from the query, causing the query to be run. + * + * @return the results, or an empty list if there are no results + */ + public Map> getResultList() { + return getResultList(false); + } + + /** + * Get the non read only results from the query, causing the query to be run. + * + * @return the results, or an empty list if there are no results + */ + public Map> getWritableResultList() { + return getResultList(true); + } + + /** + * Get the result from the query, causing the query to be run. + * + * @param writable if this query should only return properties that are not read only + * + * @return the results, or an empty list if there are no results + */ + private Map> getResultList(boolean writable) { + Map> properties = new HashMap>(); + + // First check public accessor methods (we ignore private methods) + for (Method method : targetClass.getMethods()) { + if (!(method.getName().startsWith("is") || method.getName().startsWith("get"))) { + continue; + } + + boolean match = true; + for (PropertyCriteria c : criteria) { + if (!c.methodMatches(method)) { + match = false; + break; + } + } + + if (match) { + MethodProperty property = Properties.createProperty(method); + + if (!writable || !property.isReadOnly()) { + properties.put(property.getName(), property); + } + } + } + + return Collections.unmodifiableMap(properties); + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java new file mode 100644 index 0000000000..e8491a4136 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Reflections.java @@ -0,0 +1,980 @@ +package org.keycloak.models.utils.reflection; + +import java.beans.Introspector; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.security.AccessController; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Utility class for working with JDK Reflection and also CDI's {link Annotated} metadata. + */ +public class Reflections { + /** + * An empty array of type {@link java.lang.annotation.Annotation}, useful converting lists to arrays. + */ + public static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + /** + * An empty array of type {@link Object}, useful for converting lists to arrays. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + public static final Type[] EMPTY_TYPES = {}; + + public static final Class[] EMPTY_CLASSES = new Class[0]; + + private Reflections() { + } + + /** + *

Perform a runtime cast. Similar to {@link Class#cast(Object)}, but useful when you do not have a {@link + * Class} object for type you wish to cast to.

{@link Class#cast(Object)} should be used if possible + *

+ * + * @param the type to cast to + * @param obj the object to perform the cast on + * + * @return the casted object + * + * @throws ClassCastException if the type T is not a subtype of the object + * @see Class#cast(Object) + */ + @SuppressWarnings("unchecked") + public static T cast(Object obj) { + return (T) obj; + } + + /** + * Get all the declared fields on the class hierarchy. This will return overridden fields. + * + * @param clazz The class to search + * + * @return the set of all declared fields or an empty set if there are none + */ + public static Set getAllDeclaredFields(Class clazz) { + HashSet fields = new HashSet(); + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + for (Field a : c.getDeclaredFields()) { + fields.add(a); + } + } + return fields; + } + + /** + * Search the class hierarchy for a field with the given name. Will return the nearest match, starting with the + * class specified and searching up the hierarchy. + * + * @param clazz The class to search + * @param name The name of the field to search for + * + * @return The field found, or null if no field is found + */ + public static Field findDeclaredField(Class clazz, String name) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + try { + return c.getDeclaredField(name); + } catch (NoSuchFieldException e) { + // No-op, we continue looking up the class hierarchy + } + } + return null; + } + + /** + * Search for annotations with the specified meta annotation type + * + * @param annotations The annotation set to search + * @param metaAnnotationType The type of the meta annotation to search for + * + * @return The set of annotations with the specified meta annotation, or an empty set if none are found + */ + public static Set getAnnotationsWithMetaAnnotation( + Set annotations, Class metaAnnotationType) { + Set set = new HashSet(); + for (Annotation annotation : annotations) { + if (annotation.annotationType().isAnnotationPresent(metaAnnotationType)) { + set.add(annotation); + } + } + return set; + } + + /** + * Determine if a method exists in a specified class hierarchy + * + * @param clazz The class to search + * @param name The name of the method + * + * @return true if a method is found, otherwise false + */ + public static boolean methodExists(Class clazz, String name) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + for (Method m : c.getDeclaredMethods()) { + if (m.getName().equals(name)) { + return true; + } + } + } + return false; + } + + /** + * Get all the declared methods on the class hierarchy. This will return overridden methods. + * + * @param clazz The class to search + * + * @return the set of all declared methods or an empty set if there are none + */ + public static Set getAllDeclaredMethods(Class clazz) { + HashSet methods = new HashSet(); + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + for (Method a : c.getDeclaredMethods()) { + methods.add(a); + } + } + return methods; + } + + /** + * Search the class hierarchy for a method with the given name and arguments. Will return the nearest match, + * starting with the class specified and searching up the hierarchy. + * + * @param clazz The class to search + * @param name The name of the method to search for + * @param args The arguments of the method to search for + * + * @return The method found, or null if no method is found + */ + public static Method findDeclaredMethod(Class clazz, String name, Class... args) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + try { + return c.getDeclaredMethod(name, args); + } catch (NoSuchMethodException e) { + // No-op, continue the search + } + } + return null; + } + + /** + * Search the class hierarchy for a constructor with the given arguments. Will return the nearest match, starting + * with the class specified and searching up the hierarchy. + * + * @param clazz The class to search + * @param args The arguments of the constructor to search for + * + * @return The constructor found, or null if no constructor is found + */ + public static Constructor findDeclaredConstructor(Class clazz, Class... args) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + try { + return c.getDeclaredConstructor(args); + } catch (NoSuchMethodException e) { + // No-op, continue the search + } + } + return null; + } + + /** + * Get all the declared constructors on the class hierarchy. This will return overridden constructors. + * + * @param clazz The class to search + * + * @return the set of all declared constructors or an empty set if there are none + */ + public static Set> getAllDeclaredConstructors(Class clazz) { + HashSet> constructors = new HashSet>(); + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + for (Constructor constructor : c.getDeclaredConstructors()) { + constructors.add(constructor); + } + } + return constructors; + } + + /** + * Get the type of the member + * + * @param member The member + * + * @return The type of the member + * + * @throws UnsupportedOperationException if the member is not a field, method, or constructor + */ + public static Class getMemberType(Member member) { + if (member instanceof Field) { + return ((Field) member).getType(); + } else if (member instanceof Method) { + return ((Method) member).getReturnType(); + } else if (member instanceof Constructor) { + return ((Constructor) member).getDeclaringClass(); + } else { + throw new UnsupportedOperationException("Cannot operate on a member of type " + member.getClass()); + } + } + + /** + *

Loads and initializes a class for the given name.

If the Thread Context Class Loader is + * available, it will be used, otherwise the classloader used to load {@link Reflections} will be used

+ * It is also possible to specify additional classloaders to attempt to load the class with. If the first attempt + * fails, then these additional loaders are tried in order.

+ * + * @param name the name of the class to load + * @param loaders additional classloaders to use to attempt to load the class + * + * @return the class object + * + * @throws ClassNotFoundException if the class cannot be found + */ + public static Class classForName(String name, ClassLoader... loaders) throws ClassNotFoundException { + try { + if (Thread.currentThread().getContextClassLoader() != null) { + return (Class) Class.forName(name, true, Thread.currentThread().getContextClassLoader()); + } else { + return (Class) Class.forName(name); + } + } catch (ClassNotFoundException e) { + for (ClassLoader l : loaders) { + try { + return (Class) Class.forName(name, true, l); + } catch (ClassNotFoundException ex) { + + } + } + } + if (Thread.currentThread().getContextClassLoader() != null) { + throw new ClassNotFoundException("Could not load class " + name + + " with the context class loader " + Thread.currentThread().getContextClassLoader().toString() + + " or any of the additional ClassLoaders: " + Arrays.toString(loaders)); + } else { + throw new ClassNotFoundException("Could not load class " + name + + " using Class.forName or using any of the additional ClassLoaders: " + + Arrays.toString(loaders)); + } + } + + private static String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args) { + StringBuilder message = new StringBuilder( + String.format("Exception invoking method [%s] on object [%s], using arguments [", + method.getName(), obj)); + if (args != null) { + for (int i = 0; i < args.length; i++) { + message.append((i > 0 ? "," : "") + args[i]); + } + } + message.append("]"); + return message.toString(); + } + + /** + *

Invoke the specified method on the provided instance, passing any additional arguments included in this + * method as arguments to the specified method.

This method provides the same functionality and throws + * the same exceptions as {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the + * expected return type set to {@link Object} and no change to the method's accessibility.

+ * + * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...) + * @see Method#invoke(Object, Object...) + */ + public static Object invokeMethod(Method method, Object instance, Object... args) { + return invokeMethod(false, method, Object.class, instance, args); + } + + /** + *

Invoke the specified method on the provided instance, passing any additional arguments included in this + * method as arguments to the specified method.

This method attempts to set the accessible flag of the + * method in a {link PrivilegedAction} before invoking the method if the first argument is true.

This + * method provides the same functionality and throws the same exceptions as {@link Reflections#invokeMethod(boolean, + * Method, Class, Object, Object...)}, with the expected return type set to {@link Object}.

+ * + * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...) + * @see Method#invoke(Object, Object...) + */ + public static Object invokeMethod(boolean setAccessible, Method method, Object instance, Object... args) { + return invokeMethod(setAccessible, method, Object.class, instance, args); + } + + /** + *

Invoke the specified method on the provided instance, passing any additional arguments included in this + * method as arguments to the specified method.

This method provides the same functionality and throws + * the same exceptions as {@link Reflections#invokeMethod(boolean, Method, Class, Object, Object...)}, with the + * expected return type set to {@link Object} and honoring the accessibility of the method.

+ * + * @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...) + * @see Method#invoke(Object, Object...) + */ + public static T invokeMethod(Method method, Class expectedReturnType, Object instance, Object... args) { + return invokeMethod(false, method, expectedReturnType, instance, args); + } + + /** + *

Invoke the method on the instance, with any arguments specified, casting the result of invoking the method to + * the expected return type.

This method wraps {@link Method#invoke(Object, Object...)}, converting + * the checked exceptions that {@link Method#invoke(Object, Object...)} specifies to runtime exceptions.

+ *

If instructed, this method attempts to set the accessible flag of the method in a {link PrivilegedAction} + * before invoking the method.

+ * + * @param setAccessible flag indicating whether method should first be set as accessible + * @param method the method to invoke + * @param instance the instance to invoke the method + * @param args the arguments to the method + * + * @return the result of invoking the method, or null if the method's return type is void + * + * @throws RuntimeException if this Method object enforces Java language access control and the + * underlying method is inaccessible or if the underlying method throws an exception or if the initialization + * provoked by this method fails. + * @throws IllegalArgumentException if the method is an instance method and the specified instance + * argument is not an instance of the class or interface declaring the underlying method (or of a subclass or + * implementor thereof); if the number of actual and formal parameters differ; if an unwrapping conversion for + * primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the + * corresponding formal parameter type by a method invocation conversion. + * @throws NullPointerException if the specified instance is null and the method is an instance + * method. + * @throws ClassCastException if the result of invoking the method cannot be cast to the expectedReturnType + * @throws ExceptionInInitializerError if the initialization provoked by this method fails. + * @see Method#invoke(Object, Object...) + */ + public static T invokeMethod(boolean setAccessible, Method method, + Class expectedReturnType, Object instance, Object... args) { + if (setAccessible && !method.isAccessible()) { + setAccessible(method); + } + + try { + return expectedReturnType.cast(method.invoke(instance, args)); + } catch (IllegalAccessException ex) { + throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, instance, args), ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException(buildInvokeMethodErrorMessage(method, instance, args), ex.getCause()); + } catch (NullPointerException ex) { + NullPointerException ex2 = new NullPointerException(buildInvokeMethodErrorMessage(method, instance, args)); + ex2.initCause(ex.getCause()); + throw ex2; + } catch (ExceptionInInitializerError e) { + ExceptionInInitializerError e2 = new ExceptionInInitializerError( + buildInvokeMethodErrorMessage(method, instance, args)); + e2.initCause(e.getCause()); + throw e2; + } + } + + /** + * Set the accessibility flag on the {@link AccessibleObject} as described in {@link + * AccessibleObject#setAccessible(boolean)} within the context of a {link PrivilegedAction}. + * + * @param member the accessible object type + * @param member the accessible object + * + * @return the accessible object after the accessible flag has been altered + */ + public static A setAccessible(A member) { + AccessController.doPrivileged(new SetAccessiblePrivilegedAction(member)); + return member; + } + + private static String buildSetFieldValueErrorMessage(Field field, Object obj, Object value) { + return String.format("Exception setting [%s] field on object [%s] to value [%s]", field.getName(), obj, value); + } + + private static String buildGetFieldValueErrorMessage(Field field, Object obj) { + return String.format("Exception reading [%s] field from object [%s].", field.getName(), obj); + } + + public static Object getFieldValue(Field field, Object instance) { + return getFieldValue(field, instance, Object.class); + } + + /** + *

Get the value of the field, on the specified instance, casting the value of the field to the expected type. + *

This method wraps {@link Field#get(Object)}, converting the checked exceptions that {@link + * Field#get(Object)} specifies to runtime exceptions.

+ * + * @param the type of the field's value + * @param field the field to operate on + * @param instance the instance from which to retrieve the value + * @param expectedType the expected type of the field's value + * + * @return the value of the field + * + * @throws RuntimeException if the underlying field is inaccessible. + * @throws IllegalArgumentException if the specified instance is not an instance of the class or + * interface declaring the underlying field (or a subclass or implementor thereof). + * @throws NullPointerException if the specified instance is null and the field is an instance field. + * @throws ExceptionInInitializerError if the initialization provoked by this method fails. + */ + public static T getFieldValue(Field field, Object instance, Class expectedType) { + try { + return Reflections.cast(field.get(instance)); + } catch (IllegalAccessException e) { + throw new RuntimeException(buildGetFieldValueErrorMessage(field, instance), e); + } catch (NullPointerException ex) { + NullPointerException ex2 = new NullPointerException(buildGetFieldValueErrorMessage(field, instance)); + ex2.initCause(ex.getCause()); + throw ex2; + } catch (ExceptionInInitializerError e) { + ExceptionInInitializerError e2 = new ExceptionInInitializerError( + buildGetFieldValueErrorMessage(field, instance)); + e2.initCause(e.getCause()); + throw e2; + } + } + + /** + * Extract the raw type, given a type. + * + * @param the type + * @param type the type to extract the raw type from + * + * @return the raw type, or null if the raw type cannot be determined. + */ + @SuppressWarnings("unchecked") + public static Class getRawType(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + if (((ParameterizedType) type).getRawType() instanceof Class) { + return (Class) ((ParameterizedType) type).getRawType(); + } + } + return null; + } + + /** + * Check if a class is serializable. + * + * @param clazz The class to check + * + * @return true if the class implements serializable or is a primitive + */ + public static boolean isSerializable(Class clazz) { + return clazz.isPrimitive() || Serializable.class.isAssignableFrom(clazz); + } + + + public static Map, Type> buildTypeMap(Set types) { + Map, Type> map = new HashMap, Type>(); + for (Type type : types) { + if (type instanceof Class) { + map.put((Class) type, type); + } else if (type instanceof ParameterizedType) { + if (((ParameterizedType) type).getRawType() instanceof Class) { + map.put((Class) ((ParameterizedType) type).getRawType(), type); + } + } else if (type instanceof TypeVariable) { + + } + } + return map; + } + + public static boolean isCacheable(Set annotations) { + for (Annotation qualifier : annotations) { + Class clazz = qualifier.getClass(); + if (clazz.isAnonymousClass() || (clazz.isMemberClass() && isStatic(clazz))) { + return false; + } + } + return true; + } + + public static boolean isCacheable(Annotation[] annotations) { + for (Annotation qualifier : annotations) { + Class clazz = qualifier.getClass(); + if (clazz.isAnonymousClass() || (clazz.isMemberClass() && isStatic(clazz))) { + return false; + } + } + return true; + } + + /** + * Gets the property name from a getter method. + *

+ * We extend JavaBean conventions, allowing the getter method to have parameters + * + * @param method The getter method + * + * @return The name of the property. Returns null if method wasn't JavaBean getter-styled + */ + public static String getPropertyName(Method method) { + String methodName = method.getName(); + if (methodName.matches("^(get).*")) { + return Introspector.decapitalize(methodName.substring(3)); + } else if (methodName.matches("^(is).*")) { + return Introspector.decapitalize(methodName.substring(2)); + } else { + return null; + } + + } + + /** + * Checks if class is final + * + * @param clazz The class to check + * + * @return True if final, false otherwise + */ + public static boolean isFinal(Class clazz) { + return Modifier.isFinal(clazz.getModifiers()); + } + + public static int getNesting(Class clazz) { + if (clazz.isMemberClass() && !isStatic(clazz)) { + return 1 + getNesting(clazz.getDeclaringClass()); + } else { + return 0; + } + } + + /** + * Checks if member is final + * + * @param member The member to check + * + * @return True if final, false otherwise + */ + public static boolean isFinal(Member member) { + return Modifier.isFinal(member.getModifiers()); + } + + /** + * Checks if member is private + * + * @param member The member to check + * + * @return True if final, false otherwise + */ + public static boolean isPrivate(Member member) { + return Modifier.isPrivate(member.getModifiers()); + } + + /** + * Checks if type or member is final + * + * @param type Type or member + * + * @return True if final, false otherwise + */ + public static boolean isTypeOrAnyMethodFinal(Class type) { + return getNonPrivateFinalMethodOrType(type) != null; + } + + public static Object getNonPrivateFinalMethodOrType(Class type) { + if (isFinal(type)) { + return type; + } + for (Method method : type.getDeclaredMethods()) { + if (isFinal(method) && !isPrivate(method)) { + return method; + } + } + return null; + } + + public static boolean isPackagePrivate(int mod) { + return !(Modifier.isPrivate(mod) || Modifier.isProtected(mod) || Modifier.isPublic(mod)); + } + + /** + * Checks if type is static + * + * @param type Type to check + * + * @return True if static, false otherwise + */ + public static boolean isStatic(Class type) { + return Modifier.isStatic(type.getModifiers()); + } + + /** + * Checks if member is static + * + * @param member Member to check + * + * @return True if static, false otherwise + */ + public static boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); + } + + public static boolean isTransient(Member member) { + return Modifier.isTransient(member.getModifiers()); + } + + /** + * Checks if a method is abstract + * + * @param method + * + * @return + */ + public static boolean isAbstract(Method method) { + return Modifier.isAbstract(method.getModifiers()); + } + + /** + * Checks if raw type is array type + * + * @param rawType The raw type to check + * + * @return True if array, false otherwise + */ + public static boolean isArrayType(Class rawType) { + return rawType.isArray(); + } + + /** + * Checks if type is parameterized type + * + * @param type The type to check + * + * @return True if parameterized, false otherwise + */ + public static boolean isParameterizedType(Class type) { + return type.getTypeParameters().length > 0; + } + + public static boolean isParamerterizedTypeWithWildcard(Class type) { + if (isParameterizedType(type)) { + return containsWildcards(type.getTypeParameters()); + } else { + return false; + } + } + + public static boolean containsWildcards(Type[] types) { + for (Type type : types) { + if (type instanceof WildcardType) { + return true; + } + } + return false; + } + + /** + * Check the assignability of one type to another, taking into account the actual type arguements + * + * @param rawType1 the raw type of the class to check + * @param actualTypeArguments1 the actual type arguements to check, or an empty array if not a parameterized type + * @param rawType2 the raw type of the class to check + * @param actualTypeArguments2 the actual type arguements to check, or an empty array if not a parameterized type + * + * @return + */ + public static boolean isAssignableFrom(Class rawType1, Type[] actualTypeArguments1, + Class rawType2, Type[] actualTypeArguments2) { + return Types.boxedClass(rawType1).isAssignableFrom(Types.boxedClass(rawType2)) && + isAssignableFrom(actualTypeArguments1, actualTypeArguments2); + } + + public static boolean matches(Class rawType1, Type[] actualTypeArguments1, + Class rawType2, Type[] actualTypeArguments2) { + return Types.boxedClass(rawType1).equals(Types.boxedClass(rawType2)) && + isAssignableFrom(actualTypeArguments1, actualTypeArguments2); + } + + public static boolean isAssignableFrom(Type[] actualTypeArguments1, Type[] actualTypeArguments2) { + for (int i = 0; i < actualTypeArguments1.length; i++) { + Type type1 = actualTypeArguments1[i]; + Type type2 = Object.class; + if (actualTypeArguments2.length > i) { + type2 = actualTypeArguments2[i]; + } + if (!isAssignableFrom(type1, type2)) { + return false; + } + } + return true; + } + + public static boolean isAssignableFrom(Type type1, Set types2) { + for (Type type2 : types2) { + if (isAssignableFrom(type1, type2)) { + return true; + } + } + return false; + } + + public static boolean matches(Type type1, Set types2) { + for (Type type2 : types2) { + if (matches(type1, type2)) { + return true; + } + } + return false; + } + + public static boolean isAssignableFrom(Type type1, Type[] types2) { + for (Type type2 : types2) { + if (isAssignableFrom(type1, type2)) { + return true; + } + } + return false; + } + + public static boolean isAssignableFrom(Type type1, Type type2) { + if (type1 instanceof Class) { + Class clazz = (Class) type1; + if (isAssignableFrom(clazz, EMPTY_TYPES, type2)) { + return true; + } + } + if (type1 instanceof ParameterizedType) { + ParameterizedType parameterizedType1 = (ParameterizedType) type1; + if (parameterizedType1.getRawType() instanceof Class) { + if (isAssignableFrom((Class) parameterizedType1.getRawType(), + parameterizedType1.getActualTypeArguments(), type2)) { + return true; + } + } + } + if (type1 instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type1; + if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) { + return true; + } + } + if (type2 instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type2; + if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) { + return true; + } + } + if (type1 instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type1; + if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) { + return true; + } + } + if (type2 instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type2; + if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) { + return true; + } + } + return false; + } + + public static boolean matches(Type type1, Type type2) { + if (type1 instanceof Class) { + Class clazz = (Class) type1; + if (matches(clazz, EMPTY_TYPES, type2)) { + return true; + } + } + if (type1 instanceof ParameterizedType) { + ParameterizedType parameterizedType1 = (ParameterizedType) type1; + if (parameterizedType1.getRawType() instanceof Class) { + if (matches((Class) parameterizedType1.getRawType(), + parameterizedType1.getActualTypeArguments(), type2)) { + return true; + } + } + } + if (type1 instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type1; + if (isTypeBounded(type2, wildcardType.getLowerBounds(), wildcardType.getUpperBounds())) { + return true; + } + } + if (type2 instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type2; + if (isTypeBounded(type1, wildcardType.getUpperBounds(), wildcardType.getLowerBounds())) { + return true; + } + } + if (type1 instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type1; + if (isTypeBounded(type2, EMPTY_TYPES, typeVariable.getBounds())) { + return true; + } + } + if (type2 instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type2; + if (isTypeBounded(type1, typeVariable.getBounds(), EMPTY_TYPES)) { + return true; + } + } + return false; + } + + public static boolean isTypeBounded(Type type, Type[] lowerBounds, Type[] upperBounds) { + if (lowerBounds.length > 0) { + if (!isAssignableFrom(type, lowerBounds)) { + return false; + } + } + if (upperBounds.length > 0) { + if (!isAssignableFrom(upperBounds, type)) { + return false; + } + } + return true; + } + + public static boolean isAssignableFrom(Class rawType1, Type[] actualTypeArguments1, Type type2) { + if (type2 instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type2; + if (parameterizedType.getRawType() instanceof Class) { + if (isAssignableFrom(rawType1, actualTypeArguments1, (Class) parameterizedType.getRawType(), + parameterizedType.getActualTypeArguments())) { + return true; + } + } + } else if (type2 instanceof Class) { + Class clazz = (Class) type2; + if (isAssignableFrom(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) { + return true; + } + } else if (type2 instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type2; + if (isTypeBounded(rawType1, actualTypeArguments1, typeVariable.getBounds())) { + return true; + } + } + return false; + } + + public static boolean matches(Class rawType1, Type[] actualTypeArguments1, Type type2) { + if (type2 instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type2; + if (parameterizedType.getRawType() instanceof Class) { + if (matches(rawType1, actualTypeArguments1, (Class) parameterizedType.getRawType(), + parameterizedType.getActualTypeArguments())) { + return true; + } + } + } else if (type2 instanceof Class) { + Class clazz = (Class) type2; + if (matches(rawType1, actualTypeArguments1, clazz, EMPTY_TYPES)) { + return true; + } + } + return false; + } + + /** + * Check the assiginability of a set of flattened types. This algorithm will check whether any of the types1 + * matches a type in types2 + * + * @param types1 + * @param types2 + * + * @return + */ + public static boolean isAssignableFrom(Set types1, Set types2) { + for (Type type : types1) { + if (isAssignableFrom(type, types2)) { + return true; + } + } + return false; + } + + /** + * Check whether whether any of the types1 matches a type in types2 + * + * @param types1 + * @param types2 + * + * @return + */ + public static boolean matches(Set types1, Set types2) { + for (Type type : types1) { + if (matches(type, types2)) { + return true; + } + } + return false; + } + + /** + * Check the assignability of a set of flattened types. This algorithm will check whether any of the types1 + * matches a type in types2 + * + * @param types1 + * @param type2 + * + * @return + */ + public static boolean isAssignableFrom(Set types1, Type type2) { + for (Type type : types1) { + if (isAssignableFrom(type, type2)) { + return true; + } + } + return false; + } + + public static boolean isAssignableFrom(Type[] types1, Type type2) { + for (Type type : types1) { + if (isAssignableFrom(type, type2)) { + return true; + } + } + return false; + } + + public static boolean isPrimitive(Type type) { + Class rawType = getRawType(type); + return rawType == null ? false : rawType.isPrimitive(); + } + + /** + *

Creates a new instance of a class.

+ * + *

This method will use the same class loader of the given class to create the new instance.

+ * + * @param fromClass The class from where the instance should be created. + * + * @return A newly allocated instance of the class. + * + * @throws ClassNotFoundException + * @throws IllegalAccessException + * @throws InstantiationException + */ + public static T newInstance(final Class fromClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + return newInstance(fromClass, fromClass.getName()); + } + + /** + *

Creates a new instance of a class given its fullQualifiedName.

+ * + *

This method will use the same class loader of type to create the new instance.

+ * + * @param type The class that will be used to get the class loader from. + * @param fullQualifiedName The full qualified name of the class from which the instance will be created. + * + * @return A newly allocated instance of the class. + * + * @throws ClassNotFoundException + * @throws IllegalAccessException + * @throws InstantiationException + */ + public static T newInstance(final Class type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException { + return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance(); + } +} \ No newline at end of file diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java new file mode 100644 index 0000000000..3d10120517 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/SetAccessiblePrivilegedAction.java @@ -0,0 +1,22 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.AccessibleObject; +import java.security.PrivilegedAction; + +/** + * A {@link java.security.PrivilegedAction} that calls {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} + */ +public class SetAccessiblePrivilegedAction implements PrivilegedAction { + + private final AccessibleObject member; + + public SetAccessiblePrivilegedAction(AccessibleObject member) { + this.member = member; + } + + public Void run() { + member.setAccessible(true); + return null; + } + +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java b/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java new file mode 100644 index 0000000000..30015b4d68 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/reflection/Types.java @@ -0,0 +1,54 @@ +package org.keycloak.models.utils.reflection; + +import java.lang.reflect.Type; + +/** + * Utility class for Types + */ +public class Types { + private Types() { + + } + + /** + * Gets the boxed type of a class + * + * @param type The type + * + * @return The boxed type + */ + public static Type boxedType(Type type) { + if (type instanceof Class) { + return boxedClass((Class) type); + } else { + return type; + } + } + + public static Class boxedClass(Class type) { + if (!type.isPrimitive()) { + return type; + } else if (type.equals(Boolean.TYPE)) { + return Boolean.class; + } else if (type.equals(Character.TYPE)) { + return Character.class; + } else if (type.equals(Byte.TYPE)) { + return Byte.class; + } else if (type.equals(Short.TYPE)) { + return Short.class; + } else if (type.equals(Integer.TYPE)) { + return Integer.class; + } else if (type.equals(Long.TYPE)) { + return Long.class; + } else if (type.equals(Float.TYPE)) { + return Float.class; + } else if (type.equals(Double.TYPE)) { + return Double.class; + } else if (type.equals(Void.TYPE)) { + return Void.class; + } else { + // Vagaries of if/else statement, can't be reached ;-) + return type; + } + } +} \ No newline at end of file diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml index 3e4340a452..15ac823ca5 100755 --- a/model/jpa/pom.xml +++ b/model/jpa/pom.xml @@ -115,6 +115,21 @@ + + + org.apache.maven.plugins + maven-jar-plugin + + + package-tests-jar + package + + test-jar + + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java index 113fd0c64a..d23a0f45fa 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java @@ -7,6 +7,7 @@ import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.jpa.entities.*; +import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -99,7 +100,13 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode @Override public RoleModel addRole(String name) { + return this.addRole(KeycloakModelUtils.generateId(), name); + } + + @Override + public RoleModel addRole(String id, String name) { ApplicationRoleEntity roleEntity = new ApplicationRoleEntity(); + roleEntity.setId(id); roleEntity.setName(name); roleEntity.setApplication(applicationEntity); em.persist(roleEntity); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java index 9bcca9f49a..d54cf3c459 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java @@ -102,4 +102,13 @@ public class JpaKeycloakSession implements KeycloakSession { if (em.getTransaction().isActive()) em.getTransaction().rollback(); if (em.isOpen()) em.close(); } + + @Override + public void removeAllData() { + // Should be sufficient to delete all realms. Rest data should be removed in cascade + List realms = getRealms(); + for (RealmModel realm : realms) { + removeRealm(realm.getId()); + } + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index a7c32cafb3..4b6758d2ae 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -4,6 +4,7 @@ import org.keycloak.models.AuthenticationLinkModel; import org.keycloak.models.AuthenticationProviderModel; import org.keycloak.models.ClientModel; import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UsernameLoginFailureModel; import org.keycloak.models.jpa.entities.ApplicationEntity; import org.keycloak.models.jpa.entities.ApplicationRoleEntity; @@ -434,6 +435,17 @@ public class RealmAdapter implements RealmModel { return new UsernameLoginFailureAdapter(entity); } + @Override + public List getAllUserLoginFailures() { + TypedQuery query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class); + List entities = query.getResultList(); + List models = new ArrayList(); + for (UsernameLoginFailureEntity entity : entities) { + models.add(new UsernameLoginFailureAdapter(entity)); + } + return models; + } + @Override public UserModel getUserByEmail(String email) { TypedQuery query = em.createNamedQuery("getRealmUserByEmail", UserEntity.class); @@ -454,7 +466,13 @@ public class RealmAdapter implements RealmModel { @Override public UserModel addUser(String username) { + return this.addUser(KeycloakModelUtils.generateId(), username); + } + + @Override + public UserModel addUser(String id, String username) { UserEntity entity = new UserEntity(); + entity.setId(id); entity.setLoginName(username); entity.setRealm(realm); em.persist(entity); @@ -580,7 +598,13 @@ public class RealmAdapter implements RealmModel { @Override public ApplicationModel addApplication(String name) { + return this.addApplication(KeycloakModelUtils.generateId(), name); + } + + @Override + public ApplicationModel addApplication(String id, String name) { ApplicationEntity applicationData = new ApplicationEntity(); + applicationData.setId(id); applicationData.setName(name); applicationData.setEnabled(true); applicationData.setRealm(realm); @@ -805,7 +829,13 @@ public class RealmAdapter implements RealmModel { @Override public OAuthClientModel addOAuthClient(String name) { + return this.addOAuthClient(KeycloakModelUtils.generateId(), name); + } + + @Override + public OAuthClientModel addOAuthClient(String id, String name) { OAuthClientEntity data = new OAuthClientEntity(); + data.setId(id); data.setEnabled(true); data.setName(name); data.setRealm(realm); @@ -949,7 +979,13 @@ public class RealmAdapter implements RealmModel { @Override public RoleModel addRole(String name) { + return this.addRole(KeycloakModelUtils.generateId(), name); + } + + @Override + public RoleModel addRole(String id, String name) { RealmRoleEntity entity = new RealmRoleEntity(); + entity.setId(id); entity.setName(name); entity.setRealm(realm); realm.getRoles().add(entity); @@ -1170,13 +1206,9 @@ public class RealmAdapter implements RealmModel { @Override public void updateCredential(UserModel user, UserCredentialModel cred) { - CredentialEntity credentialEntity = null; UserEntity userEntity = ((UserAdapter) user).getUser(); - for (CredentialEntity entity : userEntity.getCredentials()) { - if (entity.getType().equals(cred.getType())) { - credentialEntity = entity; - } - } + CredentialEntity credentialEntity = getCredentialEntity(userEntity, cred.getType()); + if (credentialEntity == null) { credentialEntity = new CredentialEntity(); credentialEntity.setType(cred.getType()); @@ -1196,6 +1228,57 @@ public class RealmAdapter implements RealmModel { em.flush(); } + private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) { + for (CredentialEntity entity : userEntity.getCredentials()) { + if (entity.getType().equals(credType)) { + return entity; + } + } + + return null; + } + + @Override + public List getCredentialsDirectly(UserModel user) { + UserEntity userEntity = ((UserAdapter) user).getUser(); + List credentials = new ArrayList(userEntity.getCredentials()); + List result = new ArrayList(); + + if (credentials != null) { + for (CredentialEntity credEntity : credentials) { + UserCredentialValueModel credModel = new UserCredentialValueModel(); + credModel.setType(credEntity.getType()); + credModel.setDevice(credEntity.getDevice()); + credModel.setValue(credEntity.getValue()); + credModel.setSalt(credEntity.getSalt()); + + result.add(credModel); + } + } + + return result; + } + + @Override + public void updateCredentialDirectly(UserModel user, UserCredentialValueModel credModel) { + UserEntity userEntity = ((UserAdapter) user).getUser(); + CredentialEntity credentialEntity = getCredentialEntity(userEntity, credModel.getType()); + + if (credentialEntity == null) { + credentialEntity = new CredentialEntity(); + credentialEntity.setType(credModel.getType()); + credentialEntity.setUser(userEntity); + em.persist(credentialEntity); + userEntity.getCredentials().add(credentialEntity); + } + + credentialEntity.setValue(credModel.getValue()); + credentialEntity.setSalt(credModel.getSalt()); + credentialEntity.setDevice(credModel.getDevice()); + + em.flush(); + } + @Override public PasswordPolicy getPasswordPolicy() { if (passwordPolicy == null) { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java index cd23f5f2ad..96ac2c9b6f 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java @@ -29,8 +29,6 @@ import java.util.Set; @Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"realm", "name"})}) public abstract class ClientEntity { @Id - @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator") - @GeneratedValue(generator = "keycloak_generator") private String id; @Column(name = "name") private String name; @@ -63,6 +61,10 @@ public abstract class ClientEntity { return id; } + public void setId(String id) { + this.id = id; + } + public boolean isEnabled() { return enabled; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java index 6101caea2d..379299568c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java @@ -28,8 +28,6 @@ import org.hibernate.annotations.GenericGenerator; }) public abstract class RoleEntity { @Id - @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator") - @GeneratedValue(generator = "keycloak_generator") private String id; private String description; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java index f7ef212c4a..f51ed9435d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java @@ -44,8 +44,6 @@ import java.util.Set; }) public class UserEntity { @Id - @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator") - @GeneratedValue(generator = "uuid_generator") protected String id; protected String loginName; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java index 6011cccfdc..e3336e0887 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UsernameLoginFailureEntity.java @@ -3,12 +3,17 @@ package org.keycloak.models.jpa.entities; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; /** * @author
Bill Burke * @version $Revision: 1 $ */ @Entity +@NamedQueries({ + @NamedQuery(name="getAllFailures", query="select failure from UsernameLoginFailureEntity failure"), +}) public class UsernameLoginFailureEntity { // we manually set the id to be username-realmid // we may have a concurrent creation of the same login failure entry that we want to avoid diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml index 6f8110d426..01bce2f149 100755 --- a/model/mongo/pom.xml +++ b/model/mongo/pom.xml @@ -61,11 +61,6 @@ jboss-logging provided - - org.picketlink - picketlink-common - provided - org.mongodb mongo-java-driver diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java index f16400c26d..56951ebb10 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java @@ -40,4 +40,9 @@ public interface MongoStore { boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context); boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context); + + /** + * Completely remove all data from DB + */ + void removeAllEntities(); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java index b6ff046bf2..8a68e969ce 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java @@ -1,12 +1,9 @@ package org.keycloak.models.mongo.impl; import org.keycloak.models.mongo.api.MongoEntity; -import org.picketlink.common.properties.Property; +import org.keycloak.models.utils.reflection.Property; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -14,24 +11,19 @@ import java.util.Map; */ public class EntityInfo { - private final Class entityClass; + private final Class entityClass; private final String dbCollectionName; private final Map> properties; - public EntityInfo(Class entityClass, String dbCollectionName, List> properties) { + public EntityInfo(Class entityClass, String dbCollectionName, Map> properties) { this.entityClass = entityClass; this.dbCollectionName = dbCollectionName; - - Map> props= new HashMap>(); - for (Property property : properties) { - props.put(property.getName(), property); - } - this.properties = Collections.unmodifiableMap(props); + this.properties = properties; } - public Class getEntityClass() { + public Class getEntityClass() { return entityClass; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java index e7468adfd3..79b0e95610 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java @@ -32,15 +32,16 @@ import org.keycloak.models.mongo.impl.types.MongoEntityMapper; import org.keycloak.models.mongo.impl.types.SimpleMapper; import org.keycloak.models.mongo.impl.types.StringToEnumMapper; import org.keycloak.models.utils.KeycloakModelUtils; -import org.picketlink.common.properties.Property; -import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; -import org.picketlink.common.properties.query.PropertyQueries; +import org.keycloak.models.utils.reflection.AnnotatedPropertyCriteria; +import org.keycloak.models.utils.reflection.Property; +import org.keycloak.models.utils.reflection.PropertyQueries; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -55,19 +56,19 @@ public class MongoStoreImpl implements MongoStore { private static final Logger logger = Logger.getLogger(MongoStoreImpl.class); private final MapperRegistry mapperRegistry; - private ConcurrentMap, EntityInfo> entityInfoCache = - new ConcurrentHashMap, EntityInfo>(); + private ConcurrentMap, EntityInfo> entityInfoCache = + new ConcurrentHashMap, EntityInfo>(); - public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class[] managedEntityTypes) { + public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class[] managedEntityTypes) { this.database = database; mapperRegistry = new MapperRegistry(); - for (Class simpleConverterClass : SIMPLE_TYPES) { - SimpleMapper converter = new SimpleMapper(simpleConverterClass); - mapperRegistry.addAppObjectMapper(converter); - mapperRegistry.addDBObjectMapper(converter); + for (Class simpleMapperClass : SIMPLE_TYPES) { + SimpleMapper mapper = new SimpleMapper(simpleMapperClass); + mapperRegistry.addAppObjectMapper(mapper); + mapperRegistry.addDBObjectMapper(mapper); } // Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list idm will be ArrayList) @@ -83,7 +84,7 @@ public class MongoStoreImpl implements MongoStore { mapperRegistry.addAppObjectMapper(new EnumToStringMapper()); mapperRegistry.addDBObjectMapper(new StringToEnumMapper()); - for (Class type : managedEntityTypes) { + for (Class type : managedEntityTypes) { getEntityInfo(type); mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type)); mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type)); @@ -102,9 +103,9 @@ public class MongoStoreImpl implements MongoStore { logger.info("Database " + this.database.getName() + " dropped in MongoDB"); } - // Don't drop database, but just clear all data in managed collections (useful for development) - protected void clearManagedCollections(Class[] managedEntityTypes) { - for (Class clazz : managedEntityTypes) { + // Don't drop database, but just clear all data in managed collections (useful for export/import or during development) + protected void clearManagedCollections(Class[] managedEntityTypes) { + for (Class clazz : managedEntityTypes) { DBCollection dbCollection = getDBCollectionForType(clazz); if (dbCollection != null) { dbCollection.remove(new BasicDBObject()); @@ -113,8 +114,8 @@ public class MongoStoreImpl implements MongoStore { } } - protected void initManagedCollections(Class[] managedEntityTypes) { - for (Class clazz : managedEntityTypes) { + protected void initManagedCollections(Class[] managedEntityTypes) { + for (Class clazz : managedEntityTypes) { EntityInfo entityInfo = getEntityInfo(clazz); String dbCollectionName = entityInfo.getDbCollectionName(); if (dbCollectionName != null && !database.collectionExists(dbCollectionName)) { @@ -416,6 +417,13 @@ public class MongoStoreImpl implements MongoStore { } } + @Override + public void removeAllEntities() { + Set> managedTypes = this.entityInfoCache.keySet(); + Class[] arrayTemplate = (Class[])new Class[0]; + this.clearManagedCollections(managedTypes.toArray(arrayTemplate)); + } + // Possibility to add user-defined mappers public void addAppObjectConverter(Mapper mapper) { mapperRegistry.addAppObjectMapper(mapper); @@ -425,10 +433,10 @@ public class MongoStoreImpl implements MongoStore { mapperRegistry.addDBObjectMapper(mapper); } - public EntityInfo getEntityInfo(Class entityClass) { + public EntityInfo getEntityInfo(Class entityClass) { EntityInfo entityInfo = entityInfoCache.get(entityClass); if (entityInfo == null) { - List> properties = PropertyQueries.createQuery(entityClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList(); + Map> properties = PropertyQueries.createQuery(entityClass).getWritableResultList(); MongoCollection classAnnotation = entityClass.getAnnotation(MongoCollection.class); @@ -473,7 +481,7 @@ public class MongoStoreImpl implements MongoStore { return object; } - protected DBCollection getDBCollectionForType(Class type) { + protected DBCollection getDBCollectionForType(Class type) { EntityInfo entityInfo = getEntityInfo(type); String dbCollectionName = entityInfo.getDbCollectionName(); return dbCollectionName==null ? null : database.getCollection(dbCollectionName); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java index 19cfa06ee1..2d0adc00c9 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java @@ -14,13 +14,13 @@ import org.keycloak.models.mongo.api.types.MapperContext; import org.keycloak.models.mongo.api.types.MapperRegistry; import org.keycloak.models.mongo.impl.MongoStoreImpl; import org.keycloak.models.mongo.impl.EntityInfo; -import org.picketlink.common.properties.Property; -import org.picketlink.common.reflection.Types; +import org.keycloak.models.utils.reflection.Property; +import org.keycloak.models.utils.reflection.Types; /** * @author Marek Posolda */ -public class BasicDBObjectMapper implements Mapper { +public class BasicDBObjectMapper implements Mapper { private static final Logger logger = Logger.getLogger(BasicDBObjectMapper.class); @@ -73,7 +73,7 @@ public class BasicDBObjectMapper implements MapperMarek Posolda */ -public class MongoEntityMapper implements Mapper { +public class MongoEntityMapper implements Mapper { private final MongoStoreImpl mongoStoreImpl; private final MapperRegistry mapperRegistry; @@ -37,11 +36,14 @@ public class MongoEntityMapper implements Mapper> props = entityInfo.getProperties(); for (Property property : props) { String propName = property.getName(); - Object propValue = property.getValue(applicationObject); - if (propValue != null) { - Object dbValue = mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class); - dbObject.put(propName, dbValue); + // Ignore "id" property + if (!"id".equals(propName)) { + Object propValue = property.getValue(applicationObject); + if (propValue != null) { + Object dbValue = propValue == null ? null : mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class); + dbObject.put(propName, dbValue); + } } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java index 6fcae4c2f6..42b10cdf07 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractMongoAdapter.java @@ -1,13 +1,13 @@ package org.keycloak.models.mongo.keycloak.adapters; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** * @author Marek Posolda */ -public abstract class AbstractMongoAdapter { +public abstract class AbstractMongoAdapter { protected final MongoStoreInvocationContext invocationContext; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java index 83694320fa..c30db32d1f 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java @@ -8,8 +8,8 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; -import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; import org.keycloak.models.mongo.utils.MongoModelUtils; import java.util.ArrayList; @@ -20,9 +20,9 @@ import java.util.Set; /** * @author Marek Posolda */ -public class ApplicationAdapter extends ClientAdapter implements ApplicationModel { +public class ApplicationAdapter extends ClientAdapter implements ApplicationModel { - public ApplicationAdapter(RealmModel realm, ApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) { + public ApplicationAdapter(RealmModel realm, MongoApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) { super(realm, applicationEntity, invContext); } @@ -103,7 +103,7 @@ public class ApplicationAdapter extends ClientAdapter impleme .and("name").is(name) .and("applicationId").is(getId()) .get(); - RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext); + MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext); if (role == null) { return null; } else { @@ -113,7 +113,13 @@ public class ApplicationAdapter extends ClientAdapter impleme @Override public RoleAdapter addRole(String name) { - RoleEntity roleEntity = new RoleEntity(); + return this.addRole(null, name); + } + + @Override + public RoleAdapter addRole(String id, String name) { + MongoRoleEntity roleEntity = new MongoRoleEntity(); + roleEntity.setId(id); roleEntity.setName(name); roleEntity.setApplicationId(getId()); @@ -124,7 +130,7 @@ public class ApplicationAdapter extends ClientAdapter impleme @Override public boolean removeRole(RoleModel role) { - return getMongoStore().removeEntity(RoleEntity.class, role.getId(), invocationContext); + return getMongoStore().removeEntity(MongoRoleEntity.class, role.getId(), invocationContext); } @Override @@ -132,10 +138,10 @@ public class ApplicationAdapter extends ClientAdapter impleme DBObject query = new QueryBuilder() .and("applicationId").is(getId()) .get(); - List roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); + List roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext); Set result = new HashSet(); - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { result.add(new RoleAdapter(getRealm(), role, this, invocationContext)); } @@ -145,9 +151,9 @@ public class ApplicationAdapter extends ClientAdapter impleme @Override public Set getApplicationRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); + List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { result.add(new RoleAdapter(getRealm(), role, this, invocationContext)); } @@ -158,9 +164,9 @@ public class ApplicationAdapter extends ClientAdapter impleme @Override public Set getApplicationScopeMappings(ClientModel client) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext); + List roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext); - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { result.add(new RoleAdapter(getRealm(), role, this, invocationContext)); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index 181b8123f3..f651f3a744 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java @@ -7,13 +7,14 @@ import java.util.Set; import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.entities.ClientEntity; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.ClientEntity; /** * @author Marek Posolda */ -public class ClientAdapter extends AbstractMongoAdapter implements ClientModel { +public class ClientAdapter extends AbstractMongoAdapter implements ClientModel { protected final T clientEntity; private final RealmModel realm; @@ -29,6 +30,11 @@ public class ClientAdapter extends AbstractMongoAdapter< return clientEntity; } + // ClientEntity doesn't extend MongoIdentifiableEntity + public ClientEntity getMongoEntityAsClient() { + return (ClientEntity)getMongoEntity(); + } + @Override public String getId() { return getMongoEntity().getId(); @@ -36,25 +42,25 @@ public class ClientAdapter extends AbstractMongoAdapter< @Override public String getClientId() { - return getMongoEntity().getName(); + return getMongoEntityAsClient().getName(); } @Override public long getAllowedClaimsMask() { - return getMongoEntity().getAllowedClaimsMask(); + return getMongoEntityAsClient().getAllowedClaimsMask(); } @Override public void setAllowedClaimsMask(long mask) { - getMongoEntity().setAllowedClaimsMask(mask); + getMongoEntityAsClient().setAllowedClaimsMask(mask); updateMongoEntity(); } @Override public Set getWebOrigins() { Set result = new HashSet(); - if (getMongoEntity().getWebOrigins() != null) { - result.addAll(clientEntity.getWebOrigins()); + if (getMongoEntityAsClient().getWebOrigins() != null) { + result.addAll(getMongoEntityAsClient().getWebOrigins()); } return result; } @@ -63,7 +69,7 @@ public class ClientAdapter extends AbstractMongoAdapter< public void setWebOrigins(Set webOrigins) { List result = new ArrayList(); result.addAll(webOrigins); - clientEntity.setWebOrigins(result); + getMongoEntityAsClient().setWebOrigins(result); updateMongoEntity(); } @@ -80,8 +86,8 @@ public class ClientAdapter extends AbstractMongoAdapter< @Override public Set getRedirectUris() { Set result = new HashSet(); - if (clientEntity.getRedirectUris() != null) { - result.addAll(clientEntity.getRedirectUris()); + if (getMongoEntityAsClient().getRedirectUris() != null) { + result.addAll(getMongoEntityAsClient().getRedirectUris()); } return result; } @@ -90,7 +96,7 @@ public class ClientAdapter extends AbstractMongoAdapter< public void setRedirectUris(Set redirectUris) { List result = new ArrayList(); result.addAll(redirectUris); - clientEntity.setRedirectUris(result); + getMongoEntityAsClient().setRedirectUris(result); updateMongoEntity(); } @@ -106,39 +112,39 @@ public class ClientAdapter extends AbstractMongoAdapter< @Override public boolean isEnabled() { - return clientEntity.isEnabled(); + return getMongoEntityAsClient().isEnabled(); } @Override public void setEnabled(boolean enabled) { - clientEntity.setEnabled(enabled); + getMongoEntityAsClient().setEnabled(enabled); updateMongoEntity(); } @Override public boolean validateSecret(String secret) { - return secret.equals(clientEntity.getSecret()); + return secret.equals(getMongoEntityAsClient().getSecret()); } @Override public String getSecret() { - return clientEntity.getSecret(); + return getMongoEntityAsClient().getSecret(); } @Override public void setSecret(String secret) { - clientEntity.setSecret(secret); + getMongoEntityAsClient().setSecret(secret); updateMongoEntity(); } @Override public boolean isPublicClient() { - return clientEntity.isPublicClient(); + return getMongoEntityAsClient().isPublicClient(); } @Override public void setPublicClient(boolean flag) { - clientEntity.setPublicClient(flag); + getMongoEntityAsClient().setPublicClient(flag); updateMongoEntity(); } @@ -149,12 +155,12 @@ public class ClientAdapter extends AbstractMongoAdapter< @Override public int getNotBefore() { - return clientEntity.getNotBefore(); + return getMongoEntityAsClient().getNotBefore(); } @Override public void setNotBefore(int notBefore) { - clientEntity.setNotBefore(notBefore); + getMongoEntityAsClient().setNotBefore(notBefore); updateMongoEntity(); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java index e8baa311a2..0c13eb9c8e 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java @@ -9,7 +9,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.RealmEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.ArrayList; @@ -39,6 +39,11 @@ public class MongoKeycloakSession implements KeycloakSession { // TODO } + @Override + public void removeAllData() { + getMongoStore().removeAllEntities(); + } + @Override public RealmModel createRealm(String name) { return createRealm(KeycloakModelUtils.generateId(), name); @@ -46,7 +51,7 @@ public class MongoKeycloakSession implements KeycloakSession { @Override public RealmModel createRealm(String id, String name) { - RealmEntity newRealm = new RealmEntity(); + MongoRealmEntity newRealm = new MongoRealmEntity(); newRealm.setId(id); newRealm.setName(name); @@ -57,17 +62,17 @@ public class MongoKeycloakSession implements KeycloakSession { @Override public RealmModel getRealm(String id) { - RealmEntity realmEntity = getMongoStore().loadEntity(RealmEntity.class, id, invocationContext); + MongoRealmEntity realmEntity = getMongoStore().loadEntity(MongoRealmEntity.class, id, invocationContext); return realmEntity != null ? new RealmAdapter(realmEntity, invocationContext) : null; } @Override public List getRealms() { DBObject query = new BasicDBObject(); - List realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext); + List realms = getMongoStore().loadEntities(MongoRealmEntity.class, query, invocationContext); List results = new ArrayList(); - for (RealmEntity realmEntity : realms) { + for (MongoRealmEntity realmEntity : realms) { results.add(new RealmAdapter(realmEntity, invocationContext)); } return results; @@ -78,7 +83,7 @@ public class MongoKeycloakSession implements KeycloakSession { DBObject query = new QueryBuilder() .and("name").is(name) .get(); - RealmEntity realm = getMongoStore().loadSingleEntity(RealmEntity.class, query, invocationContext); + MongoRealmEntity realm = getMongoStore().loadSingleEntity(MongoRealmEntity.class, query, invocationContext); if (realm == null) return null; return new RealmAdapter(realm, invocationContext); @@ -86,7 +91,7 @@ public class MongoKeycloakSession implements KeycloakSession { @Override public boolean removeRealm(String id) { - return getMongoStore().removeEntity(RealmEntity.class, id, invocationContext); + return getMongoStore().removeEntity(MongoRealmEntity.class, id, invocationContext); } protected MongoStore getMongoStore() { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java index dcd787d0e8..d431803703 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java @@ -3,20 +3,20 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.entities.AuthenticationLinkEntity; +import org.keycloak.models.entities.AuthenticationProviderEntity; +import org.keycloak.models.entities.CredentialEntity; +import org.keycloak.models.entities.RequiredCredentialEntity; +import org.keycloak.models.entities.SocialLinkEntity; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.impl.MongoStoreImpl; import org.keycloak.models.mongo.keycloak.config.MongoClientProvider; -import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; -import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity; -import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity; -import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; -import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; -import org.keycloak.models.mongo.keycloak.entities.RealmEntity; -import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity; -import org.keycloak.models.mongo.keycloak.entities.RoleEntity; -import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity; -import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity; /** * KeycloakSessionFactory implementation based on MongoDB @@ -26,17 +26,18 @@ import org.keycloak.models.mongo.keycloak.entities.UserEntity; public class MongoKeycloakSessionFactory implements KeycloakSessionFactory { protected static final Logger logger = Logger.getLogger(MongoKeycloakSessionFactory.class); - private static final Class[] MANAGED_ENTITY_TYPES = (Class[])new Class[] { - RealmEntity.class, - UserEntity.class, - RoleEntity.class, + private static final Class[] MANAGED_ENTITY_TYPES = (Class[])new Class[] { + MongoRealmEntity.class, + MongoUserEntity.class, + MongoRoleEntity.class, RequiredCredentialEntity.class, AuthenticationProviderEntity.class, CredentialEntity.class, SocialLinkEntity.class, AuthenticationLinkEntity.class, - ApplicationEntity.class, - OAuthClientEntity.class + MongoApplicationEntity.class, + MongoOAuthClientEntity.class, + MongoUsernameLoginFailureEntity.class }; private final MongoClientProvider mongoClientProvider; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java index b9b5e04284..fbb2b3e5d2 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java @@ -3,14 +3,14 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.RealmModel; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity; /** * @author Marek Posolda */ -public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel { +public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel { - public OAuthClientAdapter(RealmModel realm, OAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) { + public OAuthClientAdapter(RealmModel realm, MongoOAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) { super(realm, oauthClientEntity, invContext); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 10793bd69a..52308de234 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -14,19 +14,21 @@ import org.keycloak.models.RequiredCredentialModel; 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.models.UsernameLoginFailureModel; +import org.keycloak.models.entities.AuthenticationLinkEntity; +import org.keycloak.models.entities.AuthenticationProviderEntity; +import org.keycloak.models.entities.CredentialEntity; +import org.keycloak.models.entities.RequiredCredentialEntity; +import org.keycloak.models.entities.SocialLinkEntity; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; -import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity; -import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity; -import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; -import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; -import org.keycloak.models.mongo.keycloak.entities.RealmEntity; -import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity; -import org.keycloak.models.mongo.keycloak.entities.RoleEntity; -import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity; -import org.keycloak.models.mongo.keycloak.entities.UserEntity; -import org.keycloak.models.mongo.keycloak.entities.UsernameLoginFailureEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity; import org.keycloak.models.mongo.utils.MongoModelUtils; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.Pbkdf2PasswordEncoder; @@ -39,7 +41,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -48,18 +49,18 @@ import java.util.regex.Pattern; /** * @author Marek Posolda */ -public class RealmAdapter extends AbstractMongoAdapter implements RealmModel { +public class RealmAdapter extends AbstractMongoAdapter implements RealmModel { private static final Logger logger = Logger.getLogger(RealmAdapter.class); - private final RealmEntity realm; + private final MongoRealmEntity realm; protected volatile transient PublicKey publicKey; protected volatile transient PrivateKey privateKey; private volatile transient PasswordPolicy passwordPolicy; - public RealmAdapter(RealmEntity realmEntity, MongoStoreInvocationContext invocationContext) { + public RealmAdapter(MongoRealmEntity realmEntity, MongoStoreInvocationContext invocationContext) { super(invocationContext); this.realm = realmEntity; } @@ -132,6 +133,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setBruteForceProtected(boolean value) { realm.setBruteForceProtected(value); + updateRealm(); } @Override @@ -142,6 +144,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setMaxFailureWaitSeconds(int val) { realm.setMaxFailureWaitSeconds(val); + updateRealm(); } @Override @@ -152,6 +155,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setWaitIncrementSeconds(int val) { realm.setWaitIncrementSeconds(val); + updateRealm(); } @Override @@ -162,6 +166,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setQuickLoginCheckMilliSeconds(long val) { realm.setQuickLoginCheckMilliSeconds(val); + updateRealm(); } @Override @@ -172,6 +177,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setMinimumQuickLoginWaitSeconds(int val) { realm.setMinimumQuickLoginWaitSeconds(val); + updateRealm(); } @@ -183,6 +189,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setMaxDeltaTimeSeconds(int val) { realm.setMaxDeltaTimeSeconds(val); + updateRealm(); } @Override @@ -193,6 +200,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setFailureFactor(int failureFactor) { realm.setFailureFactor(failureFactor); + updateRealm(); } @@ -403,7 +411,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("loginName").is(name) .and("realmId").is(getId()) .get(); - UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); + MongoUserEntity user = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext); if (user == null) { return null; @@ -418,7 +426,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("username").is(name) .and("realmId").is(getId()) .get(); - UsernameLoginFailureEntity user = getMongoStore().loadSingleEntity(UsernameLoginFailureEntity.class, query, invocationContext); + MongoUsernameLoginFailureEntity user = getMongoStore().loadSingleEntity(MongoUsernameLoginFailureEntity.class, query, invocationContext); if (user == null) { return null; @@ -434,23 +442,38 @@ public class RealmAdapter extends AbstractMongoAdapter implements R return userLoginFailure; } - UsernameLoginFailureEntity userEntity = new UsernameLoginFailureEntity(); + MongoUsernameLoginFailureEntity userEntity = new MongoUsernameLoginFailureEntity(); userEntity.setUsername(username); - // Compatibility with JPA model, which has user disabled by default - // userEntity.setEnabled(true); userEntity.setRealmId(getId()); getMongoStore().insertEntity(userEntity, invocationContext); return new UsernameLoginFailureAdapter(invocationContext, userEntity); } + @Override + public List getAllUserLoginFailures() { + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + List failures = getMongoStore().loadEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext); + + List result = new ArrayList(); + + if (failures == null) return result; + for (MongoUsernameLoginFailureEntity failure : failures) { + result.add(new UsernameLoginFailureAdapter(invocationContext, failure)); + } + + return result; + } + @Override public UserModel getUserByEmail(String email) { DBObject query = new QueryBuilder() .and("email").is(email) .and("realmId").is(getId()) .get(); - UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); + MongoUserEntity user = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext); if (user == null) { return null; @@ -461,7 +484,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public UserModel getUserById(String id) { - UserEntity user = getMongoStore().loadEntity(UserEntity.class, id, invocationContext); + MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext); // Check that it's user from this realm if (user == null || !getId().equals(user.getRealmId())) { @@ -473,7 +496,12 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public UserAdapter addUser(String username) { - UserAdapter userModel = addUserEntity(username); + return this.addUser(null, username); + } + + @Override + public UserAdapter addUser(String id, String username) { + UserAdapter userModel = addUserEntity(id, username); for (String r : getDefaultRoles()) { grantRole(userModel, getRole(r)); @@ -488,9 +516,9 @@ public class RealmAdapter extends AbstractMongoAdapter implements R return userModel; } - // Add just user entity without defaultRoles - protected UserAdapter addUserEntity(String username) { - UserEntity userEntity = new UserEntity(); + protected UserAdapter addUserEntity(String id, String username) { + MongoUserEntity userEntity = new MongoUserEntity(); + userEntity.setId(id); userEntity.setLoginName(username); // Compatibility with JPA model, which has user disabled by default // userEntity.setEnabled(true); @@ -506,7 +534,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("loginName").is(name) .and("realmId").is(getId()) .get(); - return getMongoStore().removeEntities(UserEntity.class, query, invocationContext); + return getMongoStore().removeEntities(MongoUserEntity.class, query, invocationContext); } @Override @@ -515,7 +543,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("name").is(name) .and("realmId").is(getId()) .get(); - RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext); + MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext); if (role == null) { return null; } else { @@ -525,7 +553,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public RoleModel addRole(String name) { - RoleEntity roleEntity = new RoleEntity(); + return this.addRole(null, name); + } + + @Override + public RoleModel addRole(String id, String name) { + MongoRoleEntity roleEntity = new MongoRoleEntity(); + roleEntity.setId(id); roleEntity.setName(name); roleEntity.setRealmId(getId()); @@ -541,7 +575,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public boolean removeRoleById(String id) { - return getMongoStore().removeEntity(RoleEntity.class, id, invocationContext); + return getMongoStore().removeEntity(MongoRoleEntity.class, id, invocationContext); } @Override @@ -549,12 +583,12 @@ public class RealmAdapter extends AbstractMongoAdapter implements R DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); + List roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext); Set result = new HashSet(); if (roles == null) return result; - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { result.add(new RoleAdapter(this, role, this, invocationContext)); } @@ -563,7 +597,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public RoleModel getRoleById(String id) { - RoleEntity role = getMongoStore().loadEntity(RoleEntity.class, id, invocationContext); + MongoRoleEntity role = getMongoStore().loadEntity(MongoRoleEntity.class, id, invocationContext); if (role == null) return null; if (role.getRealmId() != null) { if (!role.getRealmId().equals(this.getId())) return null; @@ -615,7 +649,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public ApplicationModel getApplicationById(String id) { - ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, id, invocationContext); + MongoApplicationEntity appData = getMongoStore().loadEntity(MongoApplicationEntity.class, id, invocationContext); // Check if application belongs to this realm if (appData == null || !getId().equals(appData.getRealmId())) { @@ -631,7 +665,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("realmId").is(getId()) .and("name").is(name) .get(); - ApplicationEntity appEntity = getMongoStore().loadSingleEntity(ApplicationEntity.class, query, invocationContext); + MongoApplicationEntity appEntity = getMongoStore().loadSingleEntity(MongoApplicationEntity.class, query, invocationContext); return appEntity == null ? null : new ApplicationAdapter(this, appEntity, invocationContext); } @@ -649,10 +683,10 @@ public class RealmAdapter extends AbstractMongoAdapter implements R DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List appDatas = getMongoStore().loadEntities(ApplicationEntity.class, query, invocationContext); + List appDatas = getMongoStore().loadEntities(MongoApplicationEntity.class, query, invocationContext); List result = new ArrayList(); - for (ApplicationEntity appData : appDatas) { + for (MongoApplicationEntity appData : appDatas) { result.add(new ApplicationAdapter(this, appData, invocationContext)); } return result; @@ -660,7 +694,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public ApplicationModel addApplication(String name) { - ApplicationEntity appData = new ApplicationEntity(); + return this.addApplication(null, name); + } + + @Override + public ApplicationModel addApplication(String id, String name) { + MongoApplicationEntity appData = new MongoApplicationEntity(); + appData.setId(id); appData.setName(name); appData.setRealmId(getId()); appData.setEnabled(true); @@ -671,7 +711,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public boolean removeApplication(String id) { - return getMongoStore().removeEntity(ApplicationEntity.class, id, invocationContext); + return getMongoStore().removeEntity(MongoApplicationEntity.class, id, invocationContext); } @Override @@ -687,16 +727,16 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void grantRole(UserModel user, RoleModel role) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); getMongoStore().pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext); } @Override public Set getRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); + List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { if (getId().equals(role.getRealmId())) { result.add(new RoleAdapter(this, role, this, invocationContext)); } else { @@ -714,7 +754,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? Set realmRoles = new HashSet(); for (RoleModel role : allRoles) { - RoleEntity roleEntity = ((RoleAdapter) role).getRole(); + MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole(); if (getId().equals(roleEntity.getRealmId())) { realmRoles.add(role); @@ -727,16 +767,16 @@ public class RealmAdapter extends AbstractMongoAdapter implements R public void deleteRoleMapping(UserModel user, RoleModel role) { if (user == null || role == null) return; - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); getMongoStore().pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext); } @Override public Set getScopeMappings(ClientModel client) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext); + List roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext); - for (RoleEntity role : roles) { + for (MongoRoleEntity role : roles) { if (getId().equals(role.getRealmId())) { result.add(new RoleAdapter(this, role, this, invocationContext)); } else { @@ -754,7 +794,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? Set realmRoles = new HashSet(); for (RoleModel role : allScopes) { - RoleEntity roleEntity = ((RoleAdapter) role).getRole(); + MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole(); if (getId().equals(roleEntity.getRealmId())) { realmRoles.add(role); @@ -787,7 +827,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public OAuthClientModel addOAuthClient(String name) { - OAuthClientEntity oauthClient = new OAuthClientEntity(); + return this.addOAuthClient(null, name); + } + + @Override + public OAuthClientModel addOAuthClient(String id, String name) { + MongoOAuthClientEntity oauthClient = new MongoOAuthClientEntity(); + oauthClient.setId(id); oauthClient.setRealmId(getId()); oauthClient.setName(name); getMongoStore().insertEntity(oauthClient, invocationContext); @@ -797,7 +843,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public boolean removeOAuthClient(String id) { - return getMongoStore().removeEntity(OAuthClientEntity.class, id, invocationContext); + return getMongoStore().removeEntity(MongoOAuthClientEntity.class, id, invocationContext); } @Override @@ -806,13 +852,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("realmId").is(getId()) .and("name").is(name) .get(); - OAuthClientEntity oauthClient = getMongoStore().loadSingleEntity(OAuthClientEntity.class, query, invocationContext); + MongoOAuthClientEntity oauthClient = getMongoStore().loadSingleEntity(MongoOAuthClientEntity.class, query, invocationContext); return oauthClient == null ? null : new OAuthClientAdapter(this, oauthClient, invocationContext); } @Override public OAuthClientModel getOAuthClientById(String id) { - OAuthClientEntity clientEntity = getMongoStore().loadEntity(OAuthClientEntity.class, id, invocationContext); + MongoOAuthClientEntity clientEntity = getMongoStore().loadEntity(MongoOAuthClientEntity.class, id, invocationContext); // Check if client belongs to this realm if (clientEntity == null || !getId().equals(clientEntity.getRealmId())) return null; @@ -825,9 +871,9 @@ public class RealmAdapter extends AbstractMongoAdapter implements R DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List results = getMongoStore().loadEntities(OAuthClientEntity.class, query, invocationContext); + List results = getMongoStore().loadEntities(MongoOAuthClientEntity.class, query, invocationContext); List list = new ArrayList(); - for (OAuthClientEntity data : results) { + for (MongoOAuthClientEntity data : results) { list.add(new OAuthClientAdapter(this, data, invocationContext)); } return list; @@ -923,13 +969,8 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void updateCredential(UserModel user, UserCredentialModel cred) { - CredentialEntity credentialEntity = null; - UserEntity userEntity = ((UserAdapter) user).getUser(); - for (CredentialEntity entity : userEntity.getCredentials()) { - if (entity.getType().equals(cred.getType())) { - credentialEntity = entity; - } - } + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); + CredentialEntity credentialEntity = getCredentialEntity(userEntity, cred.getType()); if (credentialEntity == null) { credentialEntity = new CredentialEntity(); @@ -949,6 +990,52 @@ public class RealmAdapter extends AbstractMongoAdapter implements R getMongoStore().updateEntity(userEntity, invocationContext); } + private CredentialEntity getCredentialEntity(MongoUserEntity userEntity, String credType) { + for (CredentialEntity entity : userEntity.getCredentials()) { + if (entity.getType().equals(credType)) { + return entity; + } + } + + return null; + } + + @Override + public List getCredentialsDirectly(UserModel user) { + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); + List credentials = userEntity.getCredentials(); + List result = new ArrayList(); + for (CredentialEntity credEntity : credentials) { + UserCredentialValueModel credModel = new UserCredentialValueModel(); + credModel.setType(credEntity.getType()); + credModel.setDevice(credEntity.getDevice()); + credModel.setValue(credEntity.getValue()); + credModel.setSalt(credEntity.getSalt()); + + result.add(credModel); + } + + return result; + } + + @Override + public void updateCredentialDirectly(UserModel user, UserCredentialValueModel credModel) { + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); + CredentialEntity credentialEntity = getCredentialEntity(userEntity, credModel.getType()); + + if (credentialEntity == null) { + credentialEntity = new CredentialEntity(); + credentialEntity.setType(credModel.getType()); + userEntity.getCredentials().add(credentialEntity); + } + + credentialEntity.setValue(credModel.getValue()); + credentialEntity.setSalt(credModel.getSalt()); + credentialEntity.setDevice(credModel.getDevice()); + + getMongoStore().updateEntity(userEntity, invocationContext); + } + @Override public UserModel getUserBySocialLink(SocialLinkModel socialLink) { DBObject query = new QueryBuilder() @@ -956,13 +1043,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R .and("socialLinks.socialUserId").is(socialLink.getSocialUserId()) .and("realmId").is(getId()) .get(); - UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); + MongoUserEntity userEntity = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext); return userEntity == null ? null : new UserAdapter(userEntity, invocationContext); } @Override public Set getSocialLinks(UserModel user) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); List linkEntities = userEntity.getSocialLinks(); if (linkEntities == null) { @@ -985,7 +1072,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void addSocialLink(UserModel user, SocialLinkModel socialLink) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); SocialLinkEntity socialLinkEntity = new SocialLinkEntity(); socialLinkEntity.setSocialProvider(socialLink.getSocialProvider()); socialLinkEntity.setSocialUserId(socialLink.getSocialUserId()); @@ -1000,13 +1087,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R if (socialLinkEntity == null) { return false; } - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); return getMongoStore().pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext); } private SocialLinkEntity findSocialLink(UserModel user, String socialProvider) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); List linkEntities = userEntity.getSocialLinks(); if (linkEntities == null) { return null; @@ -1022,7 +1109,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public AuthenticationLinkModel getAuthenticationLink(UserModel user) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink(); if (authLinkEntity == null) { @@ -1034,7 +1121,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) { - UserEntity userEntity = ((UserAdapter) user).getUser(); + MongoUserEntity userEntity = ((UserAdapter) user).getUser(); AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity(); authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider()); authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId()); @@ -1060,7 +1147,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List users = getMongoStore().loadEntities(UserEntity.class, query, invocationContext); + List users = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext); return convertUserEntities(users); } @@ -1100,7 +1187,7 @@ public class RealmAdapter extends AbstractMongoAdapter implements R ).get() ); - List users = getMongoStore().loadEntities(UserEntity.class, builder.get(), invocationContext); + List users = getMongoStore().loadEntities(MongoUserEntity.class, builder.get(), invocationContext); return convertUserEntities(users); } @@ -1122,13 +1209,13 @@ public class RealmAdapter extends AbstractMongoAdapter implements R queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } } - List users = getMongoStore().loadEntities(UserEntity.class, queryBuilder.get(), invocationContext); + List users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), invocationContext); return convertUserEntities(users); } - protected List convertUserEntities(List userEntities) { + protected List convertUserEntities(List userEntities) { List userModels = new ArrayList(); - for (UserEntity user : userEntities) { + for (MongoUserEntity user : userEntities) { userModels.add(new UserAdapter(user, invocationContext)); } return userModels; @@ -1232,17 +1319,18 @@ public class RealmAdapter extends AbstractMongoAdapter implements R @Override public ApplicationModel getAdminApp() { - ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, realm.getAdminAppId(), invocationContext); - return new ApplicationAdapter(this, appData, invocationContext); + MongoApplicationEntity appData = getMongoStore().loadEntity(MongoApplicationEntity.class, realm.getAdminAppId(), invocationContext); + return appData != null ? new ApplicationAdapter(this, appData, invocationContext) : null; } @Override public void setAdminApp(ApplicationModel app) { realm.setAdminAppId(app.getId()); + updateRealm(); } @Override - public RealmEntity getMongoEntity() { + public MongoRealmEntity getMongoEntity() { return realm; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java index 14cd478b3d..3d8ef1afc3 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java @@ -10,11 +10,10 @@ import com.mongodb.QueryBuilder; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; -import org.keycloak.models.mongo.keycloak.entities.RealmEntity; -import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; /** @@ -22,17 +21,17 @@ import org.keycloak.models.utils.KeycloakModelUtils; * * @author Marek Posolda */ -public class RoleAdapter extends AbstractMongoAdapter implements RoleModel { +public class RoleAdapter extends AbstractMongoAdapter implements RoleModel { - private final RoleEntity role; + private final MongoRoleEntity role; private RoleContainerModel roleContainer; private RealmModel realm; - public RoleAdapter(RealmModel realm, RoleEntity roleEntity, MongoStoreInvocationContext invContext) { + public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, MongoStoreInvocationContext invContext) { this(realm, roleEntity, null, invContext); } - public RoleAdapter(RealmModel realm, RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) { + public RoleAdapter(RealmModel realm, MongoRoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) { super(invContext); this.role = roleEntity; this.roleContainer = roleContainer; @@ -94,10 +93,10 @@ public class RoleAdapter extends AbstractMongoAdapter implements Rol DBObject query = new QueryBuilder() .and("_id").in(role.getCompositeRoleIds()) .get(); - List childRoles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); + List childRoles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext); Set set = new HashSet(); - for (RoleEntity childRole : childRoles) { + for (MongoRoleEntity childRole : childRoles) { set.add(new RoleAdapter(realm, childRole, invocationContext)); } return set; @@ -108,13 +107,13 @@ public class RoleAdapter extends AbstractMongoAdapter implements Rol if (roleContainer == null) { // Compute it if (role.getRealmId() != null) { - RealmEntity realm = getMongoStore().loadEntity(RealmEntity.class, role.getRealmId(), invocationContext); + MongoRealmEntity realm = getMongoStore().loadEntity(MongoRealmEntity.class, role.getRealmId(), invocationContext); if (realm == null) { throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists"); } roleContainer = new RealmAdapter(realm, invocationContext); } else if (role.getApplicationId() != null) { - ApplicationEntity appEntity = getMongoStore().loadEntity(ApplicationEntity.class, role.getApplicationId(), invocationContext); + MongoApplicationEntity appEntity = getMongoStore().loadEntity(MongoApplicationEntity.class, role.getApplicationId(), invocationContext); if (appEntity == null) { throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists"); } @@ -135,12 +134,12 @@ public class RoleAdapter extends AbstractMongoAdapter implements Rol return KeycloakModelUtils.searchFor(role, this, visited); } - public RoleEntity getRole() { + public MongoRoleEntity getRole() { return role; } @Override - public RoleEntity getMongoEntity() { + public MongoRoleEntity getMongoEntity() { return role; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 608082e761..c713b9f391 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -2,7 +2,7 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import java.util.Collections; import java.util.HashMap; @@ -15,11 +15,11 @@ import java.util.Set; * * @author Marek Posolda */ -public class UserAdapter extends AbstractMongoAdapter implements UserModel { +public class UserAdapter extends AbstractMongoAdapter implements UserModel { - private final UserEntity user; + private final MongoUserEntity user; - public UserAdapter(UserEntity userEntity, MongoStoreInvocationContext invContext) { + public UserAdapter(MongoUserEntity userEntity, MongoStoreInvocationContext invContext) { super(invContext); this.user = userEntity; } @@ -133,7 +133,7 @@ public class UserAdapter extends AbstractMongoAdapter implements Use return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes()); } - public UserEntity getUser() { + public MongoUserEntity getUser() { return user; } @@ -173,7 +173,7 @@ public class UserAdapter extends AbstractMongoAdapter implements Use } @Override - public UserEntity getMongoEntity() { + public MongoUserEntity getMongoEntity() { return user; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java index 0722945a2d..131a08bff8 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UsernameLoginFailureAdapter.java @@ -2,23 +2,22 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.UsernameLoginFailureModel; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.keycloak.entities.UserEntity; -import org.keycloak.models.mongo.keycloak.entities.UsernameLoginFailureEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UsernameLoginFailureAdapter extends AbstractMongoAdapter implements UsernameLoginFailureModel { - protected UsernameLoginFailureEntity user; +public class UsernameLoginFailureAdapter extends AbstractMongoAdapter implements UsernameLoginFailureModel { + protected MongoUsernameLoginFailureEntity user; - public UsernameLoginFailureAdapter(MongoStoreInvocationContext invocationContext, UsernameLoginFailureEntity user) { + public UsernameLoginFailureAdapter(MongoStoreInvocationContext invocationContext, MongoUsernameLoginFailureEntity user) { super(invocationContext); this.user = user; } @Override - protected UsernameLoginFailureEntity getMongoEntity() { + protected MongoUsernameLoginFailureEntity getMongoEntity() { return user; } @@ -35,6 +34,7 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapterMarek Posolda + */ +@MongoCollection(collectionName = "applications") +@MongoIndex(fields = { "realmId", "name" }, unique = true) +public class MongoApplicationEntity extends ApplicationEntity implements MongoIdentifiableEntity { + + @Override + public void afterRemove(MongoStoreInvocationContext context) { + // Remove all roles, which belongs to this application + DBObject query = new QueryBuilder() + .and("applicationId").is(getId()) + .get(); + context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java new file mode 100755 index 0000000000..f9b69597b0 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java @@ -0,0 +1,19 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.entities.OAuthClientEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoIndex; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "oauthClients") +@MongoIndex(fields = { "realmId", "name" }, unique = true) +public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity { + + @Override + public void afterRemove(MongoStoreInvocationContext invocationContext) { + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java new file mode 100755 index 0000000000..1bd2f900ed --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java @@ -0,0 +1,36 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.keycloak.models.entities.RealmEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoIndex; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "realms") +@MongoIndex(fields = { "name" }, unique = true) +public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEntity { + + @Override + public void afterRemove(MongoStoreInvocationContext context) { + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + + // Remove all users of this realm + context.getMongoStore().removeEntities(MongoUserEntity.class, query, context); + + // Remove all roles of this realm + context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context); + + // Remove all applications of this realm + context.getMongoStore().removeEntities(MongoApplicationEntity.class, query, context); + + // Remove all clients of this realm + context.getMongoStore().removeEntities(MongoOAuthClientEntity.class, query, context); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java similarity index 54% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java index 43fc068d4f..f931a91b9a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java @@ -3,10 +3,10 @@ package org.keycloak.models.mongo.keycloak.entities; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.jboss.logging.Logger; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; +import org.keycloak.models.entities.RoleEntity; import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoIndex; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; @@ -18,21 +18,17 @@ import java.util.List; */ @MongoCollection(collectionName = "roles") @MongoIndex(fields = "nameIndex", unique = true) -public class RoleEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { +public class MongoRoleEntity extends RoleEntity implements MongoIdentifiableEntity { - private static final Logger logger = Logger.getLogger(RoleEntity.class); - - private String name; - private String description; - - private List compositeRoleIds; - - private String realmId; - private String applicationId; + private static final Logger logger = Logger.getLogger(MongoRoleEntity.class); @MongoField // TODO This is required as Mongo doesn't support sparse indexes with compound keys (see https://jira.mongodb.org/browse/SERVER-2193) public String getNameIndex() { + String realmId = getRealmId(); + String applicationId = getApplicationId(); + String name = getName(); + if (realmId != null) { return realmId + "//" + name; } else { @@ -43,51 +39,6 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo public void setNameIndex(String ignored) { } - @MongoField - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @MongoField - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @MongoField - public List getCompositeRoleIds() { - return compositeRoleIds; - } - - public void setCompositeRoleIds(List compositeRoleIds) { - this.compositeRoleIds = compositeRoleIds; - } - - @MongoField - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; - } - - @MongoField - public String getApplicationId() { - return applicationId; - } - - public void setApplicationId(String applicationId) { - this.applicationId = applicationId; - } - @Override public void afterRemove(MongoStoreInvocationContext invContext) { MongoStore mongoStore = invContext.getMongoStore(); @@ -97,8 +48,8 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo .and("roleIds").is(getId()) .get(); - List users = mongoStore.loadEntities(UserEntity.class, query, invContext); - for (UserEntity user : users) { + List users = mongoStore.loadEntities(MongoUserEntity.class, query, invContext); + for (MongoUserEntity user : users) { logger.info("Removing role " + getName() + " from user " + user.getLoginName()); mongoStore.pullItemFromList(user, "roleIds", getId(), invContext); } @@ -108,15 +59,15 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo .and("scopeIds").is(getId()) .get(); - users = mongoStore.loadEntities(UserEntity.class, query, invContext); - for (UserEntity user : users) { + users = mongoStore.loadEntities(MongoUserEntity.class, query, invContext); + for (MongoUserEntity user : users) { logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); mongoStore.pullItemFromList(user, "scopeIds", getId(), invContext); } // Remove defaultRoles from realm - if (realmId != null) { - RealmEntity realmEntity = mongoStore.loadEntity(RealmEntity.class, realmId, invContext); + if (getRealmId() != null) { + MongoRealmEntity realmEntity = mongoStore.loadEntity(MongoRealmEntity.class, getRealmId(), invContext); // Realm might be already removed at this point if (realmEntity != null) { @@ -125,8 +76,8 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo } // Remove defaultRoles from application - if (applicationId != null) { - ApplicationEntity appEntity = mongoStore.loadEntity(ApplicationEntity.class, applicationId, invContext); + if (getApplicationId() != null) { + MongoApplicationEntity appEntity = mongoStore.loadEntity(MongoApplicationEntity.class, getApplicationId(), invContext); // Application might be already removed at this point if (appEntity != null) { @@ -138,8 +89,8 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo query = new QueryBuilder() .and("compositeRoleIds").is(getId()) .get(); - List parentRoles = mongoStore.loadEntities(RoleEntity.class, query, invContext); - for (RoleEntity role : parentRoles) { + List parentRoles = mongoStore.loadEntities(MongoRoleEntity.class, query, invContext); + for (MongoRoleEntity role : parentRoles) { mongoStore.pullItemFromList(role, "compositeRoleIds", getId(), invContext); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java new file mode 100755 index 0000000000..e9dd8519ed --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java @@ -0,0 +1,32 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.entities.UserEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoIndex; +import org.keycloak.models.mongo.api.MongoIndexes; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "users") +@MongoIndexes({ + @MongoIndex(fields = { "realmId", "loginName" }, unique = true), + @MongoIndex(fields = { "emailIndex" }, unique = true, sparse = true), +}) +public class MongoUserEntity extends UserEntity implements MongoIdentifiableEntity { + + + public String getEmailIndex() { + return getEmail() != null ? getRealmId() + "//" + getEmail() : null; + } + + public void setEmailIndex(String ignored) { + } + + @Override + public void afterRemove(MongoStoreInvocationContext invocationContext) { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java new file mode 100755 index 0000000000..62533f5898 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUsernameLoginFailureEntity.java @@ -0,0 +1,18 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.entities.UsernameLoginFailureEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@MongoCollection(collectionName = "userFailures") +public class MongoUsernameLoginFailureEntity extends UsernameLoginFailureEntity implements MongoIdentifiableEntity { + + @Override + public void afterRemove(MongoStoreInvocationContext invocationContext) { + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java deleted file mode 100755 index 1cd96708f4..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.keycloak.models.mongo.keycloak.entities; - -import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoIndex; - -import java.util.List; - -/** - * @author Marek Posolda - */ -@MongoCollection(collectionName = "oauthClients") -@MongoIndex(fields = { "realmId", "name" }, unique = true) -public class OAuthClientEntity extends ClientEntity { - -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java index d485615cfc..a6d40b95b8 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java @@ -7,12 +7,12 @@ import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.models.ClientModel; import org.keycloak.models.UserModel; +import org.keycloak.models.entities.ClientEntity; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter; import org.keycloak.models.mongo.keycloak.adapters.UserAdapter; -import org.keycloak.models.mongo.keycloak.entities.ClientEntity; -import org.keycloak.models.mongo.keycloak.entities.RoleEntity; -import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; +import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; /** * @author Marek Posolda @@ -20,8 +20,8 @@ import org.keycloak.models.mongo.keycloak.entities.UserEntity; public class MongoModelUtils { // Get everything including both application and realm roles - public static List getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) { - UserEntity userEntity = ((UserAdapter)user).getUser(); + public static List getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) { + MongoUserEntity userEntity = ((UserAdapter)user).getUser(); List roleIds = userEntity.getRoleIds(); if (roleIds == null || roleIds.isEmpty()) { @@ -31,12 +31,12 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(roleIds) .get(); - return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext); + return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext); } // Get everything including both application and realm scopes - public static List getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) { - ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntity(); + public static List getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) { + ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntityAsClient(); List scopeIds = scopedEntity.getScopeIds(); if (scopeIds == null || scopeIds.isEmpty()) { @@ -46,6 +46,6 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(scopeIds) .get(); - return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext); + return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext); } } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java index 215a9fc44e..d549fa79c8 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java @@ -8,12 +8,11 @@ import java.util.List; /** * @author Marek Posolda */ -public class Address implements MongoEntity { +public class Address { private String street; private int number; - @MongoField public String getStreet() { return street; } @@ -22,7 +21,6 @@ public class Address implements MongoEntity { this.street = street; } - @MongoField public int getNumber() { return number; } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java index 0495f43cf1..7e13e94896 100644 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/AddressWithFlats.java @@ -13,7 +13,6 @@ public class AddressWithFlats extends Address { private List flatNumbers; - @MongoField public List getFlatNumbers() { return flatNumbers; } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java index 7200806a50..f48b372c58 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoStoreTest.java @@ -6,7 +6,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.impl.MongoStoreImpl; @@ -22,7 +21,7 @@ import java.util.List; */ public class MongoStoreTest { - private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { + private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { Person.class, Address.class, AddressWithFlats.class diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java index e3f804246f..3db661671c 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java @@ -1,8 +1,8 @@ package org.keycloak.models.mongo.test; -import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; -import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import java.util.HashMap; import java.util.List; @@ -12,8 +12,9 @@ import java.util.Map; * @author Marek Posolda */ @MongoCollection(collectionName = "persons") -public class Person extends AbstractMongoIdentifiableEntity { +public class Person implements MongoIdentifiableEntity { + private String id; private String firstName; private int age; private List kids; @@ -23,7 +24,14 @@ public class Person extends AbstractMongoIdentifiableEntity { private List genders; private Map attributes = new HashMap(); - @MongoField + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public String getFirstName() { return firstName; } @@ -32,7 +40,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.firstName = firstName; } - @MongoField public int getAge() { return age; } @@ -41,7 +48,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.age = age; } - @MongoField public Gender getGender() { return gender; } @@ -50,7 +56,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.gender = gender; } - @MongoField public List getGenders() { return genders; } @@ -59,7 +64,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.genders = genders; } - @MongoField public List getKids() { return kids; } @@ -68,7 +72,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.kids = kids; } - @MongoField public List getAddresses() { return addresses; } @@ -77,7 +80,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.addresses = addresses; } - @MongoField public Address getMainAddress() { return mainAddress; } @@ -86,7 +88,6 @@ public class Person extends AbstractMongoIdentifiableEntity { this.mainAddress = mainAddress; } - @MongoField public Map getAttributes() { return attributes; } @@ -103,6 +104,10 @@ public class Person extends AbstractMongoIdentifiableEntity { attributes.remove(key); } + @Override + public void afterRemove(MongoStoreInvocationContext invocationContext) { + } + public static enum Gender { MALE, FEMALE } 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 7fcbadbc0e..4270d298d2 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 @@ -164,6 +164,9 @@ public class AdapterTest extends AbstractModelTest { cred.setValue("password"); realmModel.updateCredential(user, cred); + commit(); + + realmModel = identitySession.getRealm("JUGGLER"); Assert.assertTrue(realmModel.removeUser("bburke")); Assert.assertFalse(realmModel.removeUser("bburke")); Assert.assertNull(realmModel.getUser("bburke")); @@ -218,6 +221,9 @@ public class AdapterTest extends AbstractModelTest { realmModel.addScopeMapping(app, realmRole); + commit(); + realmModel = identitySession.getRealm("JUGGLER"); + Assert.assertTrue(realmManager.removeRealm(realmModel)); Assert.assertFalse(realmManager.removeRealm(realmModel)); Assert.assertNull(realmManager.getRealm(realmModel.getId())); @@ -241,6 +247,10 @@ public class AdapterTest extends AbstractModelTest { RoleModel realmRole = realmModel.addRole("test"); realmModel.addScopeMapping(app, realmRole); + commit(); + realmModel = identitySession.getRealm("JUGGLER"); + app = realmModel.getApplicationByName("test-app"); + Assert.assertTrue(realmModel.removeRoleById(realmRole.getId())); Assert.assertFalse(realmModel.removeRoleById(realmRole.getId())); Assert.assertNull(realmModel.getRole(realmRole.getName())); @@ -521,7 +531,7 @@ public class AdapterTest extends AbstractModelTest { } commit(true); - // Ty to rename realm to duplicate name + // Try to rename realm to duplicate name realmManager.createRealm("JUGGLER2"); commit(); try { 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 4d94084c77..05fbe78c48 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 @@ -64,6 +64,15 @@ public class ApplicationModelTest extends AbstractModelTest { assertEquals(application, copy); } + @Test + public void testAddApplicationWithId() { + application = realm.addApplication("app-123", "application2"); + commit(); + application = realmManager.getRealm(realm.getId()).getApplicationById("app-123"); + Assert.assertNotNull(application); + } + + public static void assertEquals(ApplicationModel expected, ApplicationModel actual) { Assert.assertEquals(expected.getName(), actual.getName()); Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl()); diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java index db004b5aec..0552a9757b 100755 --- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java @@ -50,6 +50,16 @@ public class ImportTest extends AbstractModelTest { commit(); realm = realmManager.getRealm("demo"); + assertDataImportedInRealm(realm); + + commit(); + + realm = realmManager.getRealm("demo"); + realmManager.removeRealm(realm); + } + + // Moved to static method, so it's possible to test this from other places too (for example export-import tests) + public static void assertDataImportedInRealm(RealmModel realm) { Assert.assertTrue(realm.isVerifyEmail()); Assert.assertFalse(realm.isUpdateProfileOnInitialSocialLogin()); @@ -211,13 +221,6 @@ public class ImportTest extends AbstractModelTest { AuthenticationLinkModel authLink = realm.getAuthenticationLink(socialUser); Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authLink.getAuthProvider()); Assert.assertEquals("myUser1", authLink.getAuthUserId()); - - commit(); - - realm = realmManager.getRealm("demo"); - realmManager.removeRealm(realm); - - } @Test diff --git a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java index c783eaa7d8..35a09814f2 100755 --- a/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/MultipleRealmsTest.java @@ -48,6 +48,10 @@ public class MultipleRealmsTest extends AbstractModelTest { // Test searching Assert.assertEquals(2, realm1.searchForUser("user").size()); + commit(); + realm1 = identitySession.getRealm("id1"); + realm2 = identitySession.getRealm("id2"); + realm1.removeUser("user1"); realm1.removeUser("user2"); Assert.assertEquals(0, realm1.searchForUser("user").size()); diff --git a/pom.xml b/pom.xml index 640300d717..bc1e568b0b 100755 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,7 @@ timer bundled-war-example project-integrations + export-import @@ -323,6 +324,13 @@ 1.3.1b + + + de.idyl + winzipaes + 1.0.1 + + org.seleniumhq.selenium diff --git a/server/pom.xml b/server/pom.xml index 7fb498b502..3e29a02489 100755 --- a/server/pom.xml +++ b/server/pom.xml @@ -225,6 +225,21 @@ provided + + + org.keycloak + keycloak-export-import-api + ${project.version} + + + org.keycloak + keycloak-export-import-impl + ${project.version} + + + de.idyl + winzipaes + diff --git a/services/pom.xml b/services/pom.xml index bec5d06d9e..af526e7029 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -85,6 +85,12 @@ ${project.version} provided + + org.keycloak + keycloak-export-import-api + ${project.version} + provided + org.keycloak keycloak-picketlink-api 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 3012507887..158ad593b6 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -10,6 +10,7 @@ import org.keycloak.audit.AuditProvider; import org.keycloak.audit.AuditProviderFactory; import org.keycloak.authentication.AuthenticationProvider; import org.keycloak.authentication.AuthenticationProviderFactory; +import org.keycloak.exportimport.ExportImportProvider; import org.keycloak.models.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -31,6 +32,7 @@ import org.keycloak.models.utils.ModelProviderUtils; import org.keycloak.timer.TimerProvider; import org.keycloak.timer.TimerProviderFactory; import org.keycloak.util.JsonSerialization; +import org.keycloak.util.ProviderLoader; import javax.servlet.ServletContext; import javax.ws.rs.core.Application; @@ -43,6 +45,7 @@ import java.io.InputStream; import java.net.URI; import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import java.util.StringTokenizer; @@ -92,6 +95,8 @@ public class KeycloakApplication extends Application { setupScheduledTasks(providerSessionFactory, factory); importRealms(context); + + checkExportImportProvider(); } public String getContextPath() { @@ -267,5 +272,16 @@ public class KeycloakApplication extends Application { } } + protected void checkExportImportProvider() { + Iterator providers = ProviderLoader.load(ExportImportProvider.class).iterator(); + + if (providers.hasNext()) { + ExportImportProvider exportImport = providers.next(); + exportImport.checkExportImport(factory); + } else { + log.warn("No ExportImportProvider found!"); + } + } + } diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 5566e601ae..a83b4a7038 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -233,6 +233,17 @@ ${project.version} + + org.keycloak + keycloak-export-import-api + ${project.version} + + + org.keycloak + keycloak-export-import-impl + ${project.version} + + org.jboss.logging jboss-logging @@ -346,6 +357,12 @@ picketlink-common + + + de.idyl + winzipaes + + org.keycloak