Full export-import implemented. Remove dependency of mongo on picketlink-common

This commit is contained in:
mposolda 2014-04-29 11:44:37 +02:00
parent e4e02a2615
commit 0801c9c120
100 changed files with 4878 additions and 1004 deletions

View file

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-export-import</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-export-import-api</artifactId>
<name>Keycloak Export Import API</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,11 @@
package org.keycloak.exportimport;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportImportProvider {
void checkExportImport(KeycloakSessionFactory identitySessionFactory);
}

View file

@ -0,0 +1,228 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-export-import</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-export-import-impl</artifactId>
<name>Keycloak Export Import Impl</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<scope>provided</scope>
</dependency>
<!-- Encrypted ZIP -->
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-tests</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-mongo</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<keycloak.mongo.host>localhost</keycloak.mongo.host>
<keycloak.mongo.port>27018</keycloak.mongo.port>
<keycloak.mongo.db>keycloak</keycloak.mongo.db>
<keycloak.mongo.clearOnStartup>true</keycloak.mongo.clearOnStartup>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<keycloak.mongo.host>${keycloak.mongo.host}</keycloak.mongo.host>
<keycloak.mongo.port>${keycloak.mongo.port}</keycloak.mongo.port>
<keycloak.mongo.db>${keycloak.mongo.db}</keycloak.mongo.db>
<keycloak.mongo.clearOnStartup>${keycloak.mongo.clearOnStartup}</keycloak.mongo.clearOnStartup>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>default-test</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<!-- Embedded mongo -->
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
<executions>
<execution>
<id>start-mongodb</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<port>${keycloak.mongo.port}</port>
<logging>file</logging>
<logFile>${project.build.directory}/mongodb.log</logFile>
</configuration>
</execution>
<execution>
<id>stop-mongodb</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ExportImportPropertiesManager {
private static final Logger logger = Logger.getLogger(ExportImportPropertiesManager.class);
private Map<Class<?>, Map<String, Property<Object>>> objectProperties = new HashMap<Class<?>, Map<String, Property<Object>>>();
// Add properties of class to objectProperties
protected void initClassProperties(Class<?> clazz) {
Map<String, Property<Object>> 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<String, Property<Object>> modelProps = this.objectProperties.get(modelClass);
Map<String, Property<Object>> entityProps = this.objectProperties.get(entity.getClass());
Map<String, Property<Object>> entityPropsCopy = new HashMap<String, Property<Object>>(entityProps);
logger.debugf("Properties of entity %s: %s", entity, entityProps.keySet());
for (Property<Object> modelProperty : modelProps.values()) {
Property<Object> 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<String, Property<Object>> modelProps = this.objectProperties.get(modelClass);
Map<String, Property<Object>> entityProps = this.objectProperties.get(entity.getClass());
Map<String, Property<Object>> entityPropsCopy = new HashMap<String, Property<Object>>(entityProps);
logger.debugf("Properties of exported entity %s: %s", entity, entityProps.keySet());
for (Property<Object> modelProperty : modelProps.values()) {
Property<Object> 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<String> 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;
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<ExportImportIOProvider> providers = ProviderLoader.load(ExportImportIOProvider.class);
for (ExportImportIOProvider provider : providers) {
if (providerId.equals(provider.getId())) {
return provider;
}
}
throw new IllegalStateException("Provider " + providerId + " not found");
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<RealmModel> realms = keycloakSession.getRealms();
// Convert models to entities, which will be written into JSON file
List<RealmEntity> result = new LinkedList<RealmEntity>();
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<RequiredCredentialEntity> reqCredEntities = new ArrayList<RequiredCredentialEntity>();
List<RequiredCredentialModel> 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<AuthenticationProviderEntity> authProviderEntities = new ArrayList<AuthenticationProviderEntity>();
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<ApplicationModel> allApplications = getAllApplications(keycloakSession);
List<ApplicationEntity> result = new LinkedList<ApplicationEntity>();
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<String> scopeIds = getScopeIds(appModel);
appEntity.setScopeIds(scopeIds);
}
this.exportWriter.writeEntities(fileName, result);
logger.infof("Applications exported: " + result);
}
protected void exportOAuthClients(KeycloakSession keycloakSession, String fileName) {
List<RealmModel> realms = keycloakSession.getRealms();
List<OAuthClientModel> allClients = new ArrayList<OAuthClientModel>();
for (RealmModel realmModel : realms) {
allClients.addAll(realmModel.getOAuthClients());
}
List<OAuthClientEntity> result = new LinkedList<OAuthClientEntity>();
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<String> 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<RoleModel> allRoles = getAllRoles(keycloakSession);
List<RoleEntity> result = new LinkedList<RoleEntity>();
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<String> compositeRolesIds = null;
for (RoleModel composite : roleModel.getComposites()) {
// Lazy init
if (compositeRolesIds == null) {
compositeRolesIds = new ArrayList<String>();
}
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<RealmModel> realms = keycloakSession.getRealms();
List<UserEntity> result = new LinkedList<UserEntity>();
for (RealmModel realm : realms) {
List<UserModel> 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<SocialLinkModel> socialLinks = realm.getSocialLinks(userModel);
if (socialLinks != null && !socialLinks.isEmpty()) {
List<SocialLinkEntity> socialLinkEntities = new ArrayList<SocialLinkEntity>();
for (SocialLinkModel socialLink : socialLinks) {
SocialLinkEntity socialLinkEntity = new SocialLinkEntity();
this.propertiesManager.setBasicPropertiesFromModel(socialLink, socialLinkEntity);
socialLinkEntities.add(socialLinkEntity);
}
userEntity.setSocialLinks(socialLinkEntities);
}
// required actions
Set<UserModel.RequiredAction> requiredActions = userModel.getRequiredActions();
if (requiredActions != null && !requiredActions.isEmpty()) {
userEntity.setRequiredActions(new ArrayList<UserModel.RequiredAction>(requiredActions));
}
// attributes
userEntity.setAttributes(userModel.getAttributes());
// roleIds
Set<RoleModel> roles = realm.getRoleMappings(userModel);
List<String> roleIds = new ArrayList<String>();
for (RoleModel role : roles) {
roleIds.add(role.getId());
}
userEntity.setRoleIds(roleIds);
// credentials
List<UserCredentialValueModel> credentials = realm.getCredentialsDirectly(userModel);
List<CredentialEntity> credEntities = new ArrayList<CredentialEntity>();
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<RealmModel> realms = keycloakSession.getRealms();
List<UsernameLoginFailureModel> allFailures = new ArrayList<UsernameLoginFailureModel>();
for (RealmModel realmModel : realms) {
allFailures.addAll(realmModel.getAllUserLoginFailures());
}
List<UsernameLoginFailureEntity> result = new LinkedList<UsernameLoginFailureEntity>();
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<String> getScopeIds(ClientModel clientModel) {
Set<RoleModel> allScopes = clientModel.getRealm().getScopeMappings(clientModel);
List<String> scopeIds = new ArrayList<String>();
for (RoleModel role : allScopes) {
scopeIds.add(role.getId());
}
return scopeIds;
}
private List<ApplicationModel> getAllApplications(KeycloakSession keycloakSession) {
List<RealmModel> realms = keycloakSession.getRealms();
List<ApplicationModel> allApplications = new ArrayList<ApplicationModel>();
for (RealmModel realmModel : realms) {
allApplications.addAll(realmModel.getApplications());
}
return allApplications;
}
private List<RoleModel> getAllRoles(KeycloakSession keycloakSession) {
List<RoleModel> allRoles = new ArrayList<RoleModel>();
List<RealmModel> realms = keycloakSession.getRealms();
for (RealmModel realmModel : realms) {
allRoles.addAll(realmModel.getRoles());
}
List<ApplicationModel> allApplications = getAllApplications(keycloakSession);
for (ApplicationModel appModel : allApplications) {
allRoles.addAll(appModel.getRoles());
}
return allRoles;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<RealmEntity> 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<String> reqCredModels = new HashSet<String>();
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<AuthenticationProviderModel> authProviderModels = new ArrayList<AuthenticationProviderModel>();
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<ApplicationEntity> 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<String, RoleEntity> rolesMap = new HashMap<String, RoleEntity>();
List<RoleEntity> 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<String, RoleEntity> 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<RealmEntity> 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<ApplicationEntity> 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<OAuthClientEntity> 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<UserEntity> 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<SocialLinkEntity> 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<UserModel.RequiredAction> requiredActions = userEntity.getRequiredActions();
if (requiredActions != null && !requiredActions.isEmpty()) {
for (UserModel.RequiredAction reqAction : requiredActions) {
user.addRequiredAction(reqAction);
}
}
// attributes
if (userEntity.getAttributes() != null) {
for (Map.Entry<String, String> 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<CredentialEntity> 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<UsernameLoginFailureEntity> 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 ; i<entity.getNumFailures() ; i++) {
model.incrementFailures();
}
}
}
}

View file

@ -0,0 +1,14 @@
package org.keycloak.exportimport.io;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportImportIOProvider {
String getId();
ExportWriter getExportWriter();
ImportReader getImportReader();
}

View file

@ -0,0 +1,13 @@
package org.keycloak.exportimport.io;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ExportWriter {
<T> void writeEntities(String fileName, List<T> entities);
void closeExportWriter();
}

View file

@ -0,0 +1,13 @@
package org.keycloak.exportimport.io;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ImportReader {
<T> List<T> readEntities(String fileName, Class<T> entityClass);
void closeImportReader();
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 <T> void writeEntities(String fileName, List<T> 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.
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 <T> List<T> readEntities(String fileName, Class<T> 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?
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 <T> void writeEntities(String fileName, List<T> 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);
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 <T> List<T> readEntities(String fileName, Class<T> 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);
}
}
}

View file

@ -0,0 +1 @@
org.keycloak.exportimport.ExportImportProviderImpl

View file

@ -0,0 +1,2 @@
org.keycloak.exportimport.io.directory.TmpDirExportImportIOProvider
org.keycloak.exportimport.io.zip.EncryptedZIPIOProvider

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<ExportImportProvider> providers = ProviderLoader.load(ExportImportProvider.class).iterator();
if (providers.hasNext()) {
return providers.next();
} else {
throw new IllegalStateException("ExportImportProvider not found");
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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");
}
}

22
export-import/pom.xml Normal file
View file

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>keycloak-export-import</artifactId>
<name>Keycloak Export And Import</name>
<description/>
<modules>
<module>export-import-api</module>
<module>export-import-impl</module>
</modules>
</project>

View file

@ -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;
}
}

View file

@ -14,6 +14,8 @@ public class AuthenticationProviderModel {
private boolean passwordUpdateSupported = true;
private Map<String, String> config;
public AuthenticationProviderModel() {};
public AuthenticationProviderModel(String providerName, boolean passwordUpdateSupported, Map<String, String> config) {
this.providerName = providerName;
this.passwordUpdateSupported = passwordUpdateSupported;

View file

@ -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);
}
}

View file

@ -16,5 +16,7 @@ public interface KeycloakSession {
List<RealmModel> getRealms();
boolean removeRealm(String id);
void removeAllData();
void close();
}

View file

@ -109,12 +109,18 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
void updateCredential(UserModel user, UserCredentialModel cred);
List<UserCredentialValueModel> 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<UsernameLoginFailureModel> getAllUserLoginFailures();
List<UserModel> 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);

View file

@ -12,6 +12,8 @@ public interface RoleContainerModel {
RoleModel addRole(String name);
RoleModel addRole(String id, String name);
boolean removeRole(RoleModel role);
Set<RoleModel> getRoles();

View file

@ -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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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<String> defaultRoles = new ArrayList<String>();
@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<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> 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<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticationProviderEntity implements MongoEntity {
public class AuthenticationProviderEntity {
private String providerName;
private boolean passwordUpdateSupported;
private Map<String, String> 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<String, String> getConfig() {
return config;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> redirectUris = new ArrayList<String>();
private List<String> scopeIds = new ArrayList<String>();
@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<String> getWebOrigins() {
return webOrigins;
}
@ -97,7 +85,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon
this.webOrigins = webOrigins;
}
@MongoField
public List<String> getRedirectUris() {
return redirectUris;
}
@ -106,7 +93,6 @@ public class ClientEntity extends AbstractMongoIdentifiableEntity implements Mon
this.redirectUris = redirectUris;
}
@MongoField
public List<String> getScopeIds() {
return scopeIds;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -0,0 +1,7 @@
package org.keycloak.models.entities;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OAuthClientEntity extends ClientEntity {
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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<String> defaultRoles = new ArrayList<String>();
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
private Map<String, String> ldapServerConfig;
private boolean auditEnabled;
private long auditExpiration;
private List<String> auditListeners = new ArrayList<String>();
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<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
@MongoField
public List<RequiredCredentialEntity> getRequiredCredentials() {
return requiredCredentials;
}
public void setRequiredCredentials(List<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
@MongoField
public List<AuthenticationProviderEntity> getAuthenticationProviders() {
return authenticationProviders;
}
public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
this.authenticationProviders = authenticationProviders;
}
@MongoField
public Map<String, String> getSmtpConfig() {
return smtpConfig;
}
public void setSmtpConfig(Map<String, String> smptConfig) {
this.smtpConfig = smptConfig;
}
@MongoField
public Map<String, String> getSocialConfig() {
return socialConfig;
}
public void setSocialConfig(Map<String, String> socialConfig) {
this.socialConfig = socialConfig;
}
@MongoField
public Map<String, String> getLdapServerConfig() {
return ldapServerConfig;
}
public void setLdapServerConfig(Map<String, String> 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<String> getAuditListeners() {
return auditListeners;
}
public void setAuditListeners(List<String> 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String> defaultRoles = new ArrayList<String>();
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
private Map<String, String> ldapServerConfig;
private boolean auditEnabled;
private long auditExpiration;
private List<String> auditListeners = new ArrayList<String>();
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<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
public List<RequiredCredentialEntity> getRequiredCredentials() {
return requiredCredentials;
}
public void setRequiredCredentials(List<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
public List<AuthenticationProviderEntity> getAuthenticationProviders() {
return authenticationProviders;
}
public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
this.authenticationProviders = authenticationProviders;
}
public Map<String, String> getSmtpConfig() {
return smtpConfig;
}
public void setSmtpConfig(Map<String, String> smtpConfig) {
this.smtpConfig = smtpConfig;
}
public Map<String, String> getSocialConfig() {
return socialConfig;
}
public void setSocialConfig(Map<String, String> socialConfig) {
this.socialConfig = socialConfig;
}
public Map<String, String> getLdapServerConfig() {
return ldapServerConfig;
}
public void setLdapServerConfig(Map<String, String> 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<String> getAuditListeners() {
return auditListeners;
}
public void setAuditListeners(List<String> auditListeners) {
this.auditListeners = auditListeners;
}
public String getAdminAppId() {
return adminAppId;
}
public void setAdminAppId(String adminAppId) {
this.adminAppId = adminAppId;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -0,0 +1,57 @@
package org.keycloak.models.entities;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleEntity extends AbstractIdentifiableEntity {
private String name;
private String description;
private List<String> 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<String> getCompositeRoleIds() {
return compositeRoleIds;
}
public void setCompositeRoleIds(List<String> 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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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<SocialLinkEntity> 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<String> getRoleIds() {
return roleIds;
}
@ -145,8 +110,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
this.roleIds = roleIds;
}
@MongoField
public Map<String, String> getAttributes() {
return attributes;
}
@ -155,7 +118,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
this.attributes = attributes;
}
@MongoField
public List<UserModel.RequiredAction> getRequiredActions() {
return requiredActions;
}
@ -164,7 +126,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
this.requiredActions = requiredActions;
}
@MongoField
public List<CredentialEntity> getCredentials() {
return credentials;
}
@ -173,7 +134,6 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
this.credentials = credentials;
}
@MongoField
public List<SocialLinkEntity> 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;
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -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<? extends Annotation> annotationClass;
public AnnotatedPropertyCriteria(Class<? extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}
@Override
public boolean methodMatches(Method m) {
return m.isAnnotationPresent(annotationClass);
}
}

View file

@ -0,0 +1,8 @@
package org.keycloak.models.utils.reflection;
import java.lang.reflect.Method;
public interface MethodProperty<V> extends Property<V> {
Method getAnnotatedElement();
}

View file

@ -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<V> implements MethodProperty<V> {
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<V> getJavaClass() {
return (Class<V>) 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<? extends Annotation> 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;
}
}
}

View file

@ -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 <V>
* @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 <V> MethodProperty<V> createProperty(Method method) {
return new MethodPropertyImpl<V>(method);
}
/**
* Indicates whether this method is a valid property method.
*/
public static <V> boolean isProperty(Method method) {
try {
new MethodPropertyImpl<V>(method);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
}

View file

@ -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 <V> the type of the properties value
*
* @see Properties
*/
public interface Property<V> {
/**
* 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<V> 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).
* <p/>
* The operation should be performed within a {@link PrivilegedAction}
*/
void setAccessible();
/**
* Indicates whether the given <code>annotation</code> 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<? extends Annotation> annotation);
}

View file

@ -0,0 +1,27 @@
package org.keycloak.models.utils.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* <p> A property criteria can be used to filter the properties found by a {@link PropertyQuery} </p> <p/> <p>
* 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. </p>
*
* @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);
}

View file

@ -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 <V>
* @param targetClass
*
* @return
*/
public static <V> PropertyQuery<V> createQuery(Class<?> targetClass) {
return new PropertyQuery<V>(targetClass);
}
}

View file

@ -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;
/**
* <p> 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. </p> <p/> <p> This class is not thread-safe, however the result returned by the getResultList() method
* is. </p>
*
* @see PropertyQueries
* @see PropertyCriteria
*/
public class PropertyQuery<V> {
private final Class<?> targetClass;
private final List<PropertyCriteria> criteria;
PropertyQuery(Class<?> targetClass) {
if (targetClass == null) {
throw new IllegalArgumentException("targetClass parameter may not be null");
}
this.targetClass = targetClass;
this.criteria = new ArrayList<PropertyCriteria>();
}
/**
* Add a criteria to query
*
* @param criteria the criteria to add
*/
public PropertyQuery<V> 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<V> getFirstResult() {
Map<String, Property<V>> 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<V> getFirstWritableResult() {
Map<String, Property<V>> 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<V> getSingleResult() {
Map<String, Property<V>> 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<V> getWritableSingleResult() {
Map<String, Property<V>> 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<String, Property<V>> 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<String, Property<V>> 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<String, Property<V>> getResultList(boolean writable) {
Map<String, Property<V>> properties = new HashMap<String, Property<V>>();
// 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<V> property = Properties.<V>createProperty(method);
if (!writable || !property.isReadOnly()) {
properties.put(property.getName(), property);
}
}
}
return Collections.unmodifiableMap(properties);
}
}

View file

@ -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() {
}
/**
* <p> 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. </p> <p/> <p> {@link Class#cast(Object)} should be used if possible
* </p>
*
* @param <T> 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> T cast(Object obj) {
return (T) obj;
}
/**
* Get all the declared fields on the class hierarchy. This <b>will</b> 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<Field> getAllDeclaredFields(Class<?> clazz) {
HashSet<Field> fields = new HashSet<Field>();
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<Annotation> getAnnotationsWithMetaAnnotation(
Set<Annotation> annotations, Class<? extends Annotation> metaAnnotationType) {
Set<Annotation> set = new HashSet<Annotation>();
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 <b>will</b> 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<Method> getAllDeclaredMethods(Class<?> clazz) {
HashSet<Method> methods = new HashSet<Method>();
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 <b>will</b> 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<Constructor<?>> getAllDeclaredConstructors(Class<?> clazz) {
HashSet<Constructor<?>> constructors = new HashSet<Constructor<?>>();
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());
}
}
/**
* <p> Loads and initializes a class for the given name. </p> <p/> <p> If the Thread Context Class Loader is
* available, it will be used, otherwise the classloader used to load {@link Reflections} will be used </p> <p/> <p>
* 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. </p>
*
* @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 <T> Class<T> classForName(String name, ClassLoader... loaders) throws ClassNotFoundException {
try {
if (Thread.currentThread().getContextClassLoader() != null) {
return (Class<T>) Class.forName(name, true, Thread.currentThread().getContextClassLoader());
} else {
return (Class<T>) Class.forName(name);
}
} catch (ClassNotFoundException e) {
for (ClassLoader l : loaders) {
try {
return (Class<T>) 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();
}
/**
* <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
* method as arguments to the specified method. </p> <p/> <p>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.</p>
*
* @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);
}
/**
* <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
* method as arguments to the specified method. </p> <p/> <p> 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. </p> <p/> <p>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}.</p>
*
* @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);
}
/**
* <p> Invoke the specified method on the provided instance, passing any additional arguments included in this
* method as arguments to the specified method. </p> <p/> <p>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.</p>
*
* @see Reflections#invokeMethod(boolean, Method, Class, Object, Object...)
* @see Method#invoke(Object, Object...)
*/
public static <T> T invokeMethod(Method method, Class<T> expectedReturnType, Object instance, Object... args) {
return invokeMethod(false, method, expectedReturnType, instance, args);
}
/**
* <p> Invoke the method on the instance, with any arguments specified, casting the result of invoking the method to
* the expected return type. </p> <p/> <p> This method wraps {@link Method#invoke(Object, Object...)}, converting
* the checked exceptions that {@link Method#invoke(Object, Object...)} specifies to runtime exceptions. </p> <p/>
* <p> If instructed, this method attempts to set the accessible flag of the method in a {link PrivilegedAction}
* before invoking the method. </p>
*
* @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 <code>Method</code> 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 <code>instance</code>
* 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 <code>instance</code> 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> T invokeMethod(boolean setAccessible, Method method,
Class<T> 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 <A> member the accessible object type
* @param member the accessible object
*
* @return the accessible object after the accessible flag has been altered
*/
public static <A extends AccessibleObject> 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);
}
/**
* <p> Get the value of the field, on the specified instance, casting the value of the field to the expected type.
* </p> <p/> <p> This method wraps {@link Field#get(Object)}, converting the checked exceptions that {@link
* Field#get(Object)} specifies to runtime exceptions. </p>
*
* @param <T> 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 <code>instance</code> is not an instance of the class or
* interface declaring the underlying field (or a subclass or implementor thereof).
* @throws NullPointerException if the specified <code>instance</code> is null and the field is an instance field.
* @throws ExceptionInInitializerError if the initialization provoked by this method fails.
*/
public static <T> T getFieldValue(Field field, Object instance, Class<T> 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 <T> 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 <T> Class<T> getRawType(Type type) {
if (type instanceof Class<?>) {
return (Class<T>) type;
} else if (type instanceof ParameterizedType) {
if (((ParameterizedType) type).getRawType() instanceof Class<?>) {
return (Class<T>) ((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<Class<?>, Type> buildTypeMap(Set<Type> types) {
Map<Class<?>, Type> map = new HashMap<Class<?>, 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<Annotation> 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.
* <p/>
* 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<? extends Type> types2) {
for (Type type2 : types2) {
if (isAssignableFrom(type1, type2)) {
return true;
}
}
return false;
}
public static boolean matches(Type type1, Set<? extends Type> 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 <b>flattened</b> 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<Type> types1, Set<Type> 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<Type> types1, Set<Type> types2) {
for (Type type : types1) {
if (matches(type, types2)) {
return true;
}
}
return false;
}
/**
* Check the assignability of a set of <b>flattened</b> 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<Type> 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();
}
/**
* <p>Creates a new instance of a class.</p>
*
* <p>This method will use the same class loader of the given class to create the new instance.</p>
*
* @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> T newInstance(final Class<T> fromClass) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return newInstance(fromClass, fromClass.getName());
}
/**
* <p>Creates a new instance of a class given its <code>fullQualifiedName</code>.</p>
*
* <p>This method will use the same class loader of <code>type</code> to create the new instance.</p>
*
* @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> T newInstance(final Class<?> type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance();
}
}

View file

@ -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<Void> {
private final AccessibleObject member;
public SetAccessiblePrivilegedAction(AccessibleObject member) {
this.member = member;
}
public Void run() {
member.setAccessible(true);
return null;
}
}

View file

@ -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;
}
}
}

View file

@ -115,6 +115,21 @@
</configuration>
</plugin>
<!-- Test jar used in export-import -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>package-tests-jar</id>
<phase>package</phase>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>

View file

@ -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);

View file

@ -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<RealmModel> realms = getRealms();
for (RealmModel realm : realms) {
removeRealm(realm.getId());
}
}
}

View file

@ -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<UsernameLoginFailureModel> getAllUserLoginFailures() {
TypedQuery<UsernameLoginFailureEntity> query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class);
List<UsernameLoginFailureEntity> entities = query.getResultList();
List<UsernameLoginFailureModel> models = new ArrayList<UsernameLoginFailureModel>();
for (UsernameLoginFailureEntity entity : entities) {
models.add(new UsernameLoginFailureAdapter(entity));
}
return models;
}
@Override
public UserModel getUserByEmail(String email) {
TypedQuery<UserEntity> 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<UserCredentialValueModel> getCredentialsDirectly(UserModel user) {
UserEntity userEntity = ((UserAdapter) user).getUser();
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(userEntity.getCredentials());
List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
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) {

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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

View file

@ -61,11 +61,6 @@
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>

View file

@ -40,4 +40,9 @@ public interface MongoStore {
<S> boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context);
<S> boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context);
/**
* Completely remove all data from DB
*/
void removeAllEntities();
}

View file

@ -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<? extends MongoEntity> entityClass;
private final Class<?> entityClass;
private final String dbCollectionName;
private final Map<String, Property<Object>> properties;
public EntityInfo(Class<? extends MongoEntity> entityClass, String dbCollectionName, List<Property<Object>> properties) {
public EntityInfo(Class<?> entityClass, String dbCollectionName, Map<String, Property<Object>> properties) {
this.entityClass = entityClass;
this.dbCollectionName = dbCollectionName;
Map<String, Property<Object>> props= new HashMap<String, Property<Object>>();
for (Property<Object> property : properties) {
props.put(property.getName(), property);
}
this.properties = Collections.unmodifiableMap(props);
this.properties = properties;
}
public Class<? extends MongoEntity> getEntityClass() {
public Class<?> getEntityClass() {
return entityClass;
}

View file

@ -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<Class<? extends MongoEntity>, EntityInfo> entityInfoCache =
new ConcurrentHashMap<Class<? extends MongoEntity>, EntityInfo>();
private ConcurrentMap<Class<?>, EntityInfo> entityInfoCache =
new ConcurrentHashMap<Class<?>, EntityInfo>();
public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<? extends MongoEntity>[] 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<? extends MongoEntity> 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<? extends MongoEntity>[] managedEntityTypes) {
for (Class<? extends MongoEntity> 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<? extends MongoEntity>[] managedEntityTypes) {
for (Class<? extends MongoEntity> 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<Class<?>> managedTypes = this.entityInfoCache.keySet();
Class<? extends MongoEntity>[] arrayTemplate = (Class<? extends MongoEntity>[])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<? extends MongoEntity> entityClass) {
public EntityInfo getEntityInfo(Class<?> entityClass) {
EntityInfo entityInfo = entityInfoCache.get(entityClass);
if (entityInfo == null) {
List<Property<Object>> properties = PropertyQueries.createQuery(entityClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList();
Map<String, Property<Object>> 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<? extends MongoEntity> type) {
protected DBCollection getDBCollectionForType(Class<?> type) {
EntityInfo entityInfo = getEntityInfo(type);
String dbCollectionName = entityInfo.getDbCollectionName();
return dbCollectionName==null ? null : database.getCollection(dbCollectionName);

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBObjectMapper<S extends MongoEntity> implements Mapper<BasicDBObject, S> {
public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
private static final Logger logger = Logger.getLogger(BasicDBObjectMapper.class);
@ -73,7 +73,7 @@ public class BasicDBObjectMapper<S extends MongoEntity> implements Mapper<BasicD
return entity;
}
private void setPropertyValue(MongoEntity entity, Object valueFromDB, Property property) {
private void setPropertyValue(Object entity, Object valueFromDB, Property property) {
if (valueFromDB == null) {
property.setValue(entity, null);
return;

View file

@ -1,20 +1,19 @@
package org.keycloak.models.mongo.impl.types;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.types.Mapper;
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.keycloak.models.utils.reflection.Property;
import java.util.Collection;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, BasicDBObject> {
public class MongoEntityMapper<T> implements Mapper<T, BasicDBObject> {
private final MongoStoreImpl mongoStoreImpl;
private final MapperRegistry mapperRegistry;
@ -37,11 +36,14 @@ public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, Basic
Collection<Property<Object>> props = entityInfo.getProperties();
for (Property<Object> 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);
}
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractMongoAdapter<T extends AbstractMongoIdentifiableEntity> {
public abstract class AbstractMongoAdapter<T extends MongoIdentifiableEntity> {
protected final MongoStoreInvocationContext invocationContext;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ApplicationAdapter extends ClientAdapter<ApplicationEntity> implements ApplicationModel {
public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> 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<ApplicationEntity> 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<ApplicationEntity> 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<ApplicationEntity> 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<ApplicationEntity> impleme
DBObject query = new QueryBuilder()
.and("applicationId").is(getId())
.get();
List<RoleEntity> roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> result = new HashSet<RoleModel>();
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<ApplicationEntity> impleme
@Override
public Set<RoleModel> getApplicationRoleMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
List<MongoRoleEntity> 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<ApplicationEntity> impleme
@Override
public Set<RoleModel> getApplicationScopeMappings(ClientModel client) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
List<MongoRoleEntity> 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));
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<T> implements ClientModel {
public class ClientAdapter<T extends MongoIdentifiableEntity> extends AbstractMongoAdapter<T> implements ClientModel {
protected final T clientEntity;
private final RealmModel realm;
@ -29,6 +30,11 @@ public class ClientAdapter<T extends ClientEntity> 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<T extends ClientEntity> 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<String> getWebOrigins() {
Set<String> result = new HashSet<String>();
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<T extends ClientEntity> extends AbstractMongoAdapter<
public void setWebOrigins(Set<String> webOrigins) {
List<String> result = new ArrayList<String>();
result.addAll(webOrigins);
clientEntity.setWebOrigins(result);
getMongoEntityAsClient().setWebOrigins(result);
updateMongoEntity();
}
@ -80,8 +86,8 @@ public class ClientAdapter<T extends ClientEntity> extends AbstractMongoAdapter<
@Override
public Set<String> getRedirectUris() {
Set<String> result = new HashSet<String>();
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<T extends ClientEntity> extends AbstractMongoAdapter<
public void setRedirectUris(Set<String> redirectUris) {
List<String> result = new ArrayList<String>();
result.addAll(redirectUris);
clientEntity.setRedirectUris(result);
getMongoEntityAsClient().setRedirectUris(result);
updateMongoEntity();
}
@ -106,39 +112,39 @@ public class ClientAdapter<T extends ClientEntity> 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<T extends ClientEntity> 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();
}

View file

@ -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<RealmModel> getRealms() {
DBObject query = new BasicDBObject();
List<RealmEntity> realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext);
List<MongoRealmEntity> realms = getMongoStore().loadEntities(MongoRealmEntity.class, query, invocationContext);
List<RealmModel> results = new ArrayList<RealmModel>();
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() {

View file

@ -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<? extends MongoEntity>[] MANAGED_ENTITY_TYPES = (Class<? extends MongoEntity>[])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;

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OAuthClientAdapter extends ClientAdapter<OAuthClientEntity> implements OAuthClientModel {
public class OAuthClientAdapter extends ClientAdapter<MongoOAuthClientEntity> implements OAuthClientModel {
public OAuthClientAdapter(RealmModel realm, OAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
public OAuthClientAdapter(RealmModel realm, MongoOAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
super(realm, oauthClientEntity, invContext);
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements RealmModel {
public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> 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<RealmEntity> implements R
@Override
public void setBruteForceProtected(boolean value) {
realm.setBruteForceProtected(value);
updateRealm();
}
@Override
@ -142,6 +144,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMaxFailureWaitSeconds(int val) {
realm.setMaxFailureWaitSeconds(val);
updateRealm();
}
@Override
@ -152,6 +155,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setWaitIncrementSeconds(int val) {
realm.setWaitIncrementSeconds(val);
updateRealm();
}
@Override
@ -162,6 +166,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setQuickLoginCheckMilliSeconds(long val) {
realm.setQuickLoginCheckMilliSeconds(val);
updateRealm();
}
@Override
@ -172,6 +177,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMinimumQuickLoginWaitSeconds(int val) {
realm.setMinimumQuickLoginWaitSeconds(val);
updateRealm();
}
@ -183,6 +189,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setMaxDeltaTimeSeconds(int val) {
realm.setMaxDeltaTimeSeconds(val);
updateRealm();
}
@Override
@ -193,6 +200,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
@Override
public void setFailureFactor(int failureFactor) {
realm.setFailureFactor(failureFactor);
updateRealm();
}
@ -403,7 +411,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<UsernameLoginFailureModel> getAllUserLoginFailures() {
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<MongoUsernameLoginFailureEntity> failures = getMongoStore().loadEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
List<UsernameLoginFailureModel> result = new ArrayList<UsernameLoginFailureModel>();
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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<RoleEntity> roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> result = new HashSet<RoleModel>();
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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<ApplicationEntity> appDatas = getMongoStore().loadEntities(ApplicationEntity.class, query, invocationContext);
List<MongoApplicationEntity> appDatas = getMongoStore().loadEntities(MongoApplicationEntity.class, query, invocationContext);
List<ApplicationModel> result = new ArrayList<ApplicationModel>();
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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RoleModel> getRoleMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
List<MongoRoleEntity> 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<RealmEntity> 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<RoleModel> realmRoles = new HashSet<RoleModel>();
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<RealmEntity> 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<RoleModel> getScopeMappings(ClientModel client) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext);
List<MongoRoleEntity> 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<RealmEntity> 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<RoleModel> realmRoles = new HashSet<RoleModel>();
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<RealmEntity> 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<RealmEntity> 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<RealmEntity> 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<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<OAuthClientEntity> results = getMongoStore().loadEntities(OAuthClientEntity.class, query, invocationContext);
List<MongoOAuthClientEntity> results = getMongoStore().loadEntities(MongoOAuthClientEntity.class, query, invocationContext);
List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
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<RealmEntity> 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<RealmEntity> 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<UserCredentialValueModel> getCredentialsDirectly(UserModel user) {
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<CredentialEntity> credentials = userEntity.getCredentials();
List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
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<RealmEntity> 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<SocialLinkModel> getSocialLinks(UserModel user) {
UserEntity userEntity = ((UserAdapter) user).getUser();
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
List<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
@ -985,7 +1072,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> 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<RealmEntity> 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<SocialLinkEntity> linkEntities = userEntity.getSocialLinks();
if (linkEntities == null) {
return null;
@ -1022,7 +1109,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> 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<RealmEntity> 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<RealmEntity> implements R
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, query, invocationContext);
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
return convertUserEntities(users);
}
@ -1100,7 +1187,7 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
).get()
);
List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, builder.get(), invocationContext);
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, builder.get(), invocationContext);
return convertUserEntities(users);
}
@ -1122,13 +1209,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)"));
}
}
List<UserEntity> users = getMongoStore().loadEntities(UserEntity.class, queryBuilder.get(), invocationContext);
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, queryBuilder.get(), invocationContext);
return convertUserEntities(users);
}
protected List<UserModel> convertUserEntities(List<UserEntity> userEntities) {
protected List<UserModel> convertUserEntities(List<MongoUserEntity> userEntities) {
List<UserModel> userModels = new ArrayList<UserModel>();
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<RealmEntity> 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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleAdapter extends AbstractMongoAdapter<RoleEntity> implements RoleModel {
public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> 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<RoleEntity> implements Rol
DBObject query = new QueryBuilder()
.and("_id").in(role.getCompositeRoleIds())
.get();
List<RoleEntity> childRoles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
List<MongoRoleEntity> childRoles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
Set<RoleModel> set = new HashSet<RoleModel>();
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<RoleEntity> 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<RoleEntity> 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;
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements UserModel {
public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> 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<UserEntity> 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<UserEntity> implements Use
}
@Override
public UserEntity getMongoEntity() {
public MongoUserEntity getMongoEntity() {
return user;
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameLoginFailureEntity> implements UsernameLoginFailureModel {
protected UsernameLoginFailureEntity user;
public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<MongoUsernameLoginFailureEntity> 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 AbstractMongoAdapter<UsernameL
@Override
public void setFailedLoginNotBefore(int notBefore) {
user.setFailedLoginNotBefore(notBefore);
updateMongoEntity();
}
@Override
@ -45,11 +45,13 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void incrementFailures() {
user.setNumFailures(getNumFailures() + 1);
updateMongoEntity();
}
@Override
public void clearFailures() {
user.setNumFailures(0);
updateMongoEntity();
}
@Override
@ -60,6 +62,7 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void setLastFailure(long lastFailure) {
user.setLastFailure(lastFailure);
updateMongoEntity();
}
@Override
@ -70,4 +73,5 @@ public class UsernameLoginFailureAdapter extends AbstractMongoAdapter<UsernameL
@Override
public void setLastIPFailure(String ip) {
user.setLastIPFailure(ip);
updateMongoEntity();
}}

View file

@ -15,12 +15,12 @@ public class SystemPropertiesMongoClientProvider implements MongoClientProvider
protected static final Logger logger = Logger.getLogger(SystemPropertiesMongoClientProvider.class);
private static final String MONGO_HOST = "keycloak.mongo.host";
private static final String MONGO_PORT = "keycloak.mongo.port";
private static final String MONGO_DB_NAME = "keycloak.mongo.db";
private static final String MONGO_CLEAR_ON_STARTUP = "keycloak.mongo.clearOnStartup";
public static final String MONGO_HOST = "keycloak.mongo.host";
public static final String MONGO_PORT = "keycloak.mongo.port";
public static final String MONGO_DB_NAME = "keycloak.mongo.db";
public static final String MONGO_CLEAR_ON_STARTUP = "keycloak.mongo.clearOnStartup";
// Property names from Liveoak . Those are used as fallback in case that original value is not available
// Property names from Liveoak . Those are used as fallback
private static final String MONGO_HOST_2 = "mongo.host";
private static final String MONGO_PORT_2 = "mongo.port";
private static final String MONGO_DB_NAME_2 = "mongo.db";

View file

@ -0,0 +1,26 @@
package org.keycloak.models.mongo.keycloak.entities;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.entities.ApplicationEntity;
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "oauthClients")
@MongoIndex(fields = { "realmId", "name" }, unique = true)
public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity {
@Override
public void afterRemove(MongoStoreInvocationContext invocationContext) {
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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);
}
}

View file

@ -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<String> 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<String> getCompositeRoleIds() {
return compositeRoleIds;
}
public void setCompositeRoleIds(List<String> 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<UserEntity> users = mongoStore.loadEntities(UserEntity.class, query, invContext);
for (UserEntity user : users) {
List<MongoUserEntity> 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<RoleEntity> parentRoles = mongoStore.loadEntities(RoleEntity.class, query, invContext);
for (RoleEntity role : parentRoles) {
List<MongoRoleEntity> parentRoles = mongoStore.loadEntities(MongoRoleEntity.class, query, invContext);
for (MongoRoleEntity role : parentRoles) {
mongoStore.pullItemFromList(role, "compositeRoleIds", getId(), invContext);
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@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.
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@MongoCollection(collectionName = "userFailures")
public class MongoUsernameLoginFailureEntity extends UsernameLoginFailureEntity implements MongoIdentifiableEntity {
@Override
public void afterRemove(MongoStoreInvocationContext invocationContext) {
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "oauthClients")
@MongoIndex(fields = { "realmId", "name" }, unique = true)
public class OAuthClientEntity extends ClientEntity {
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -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<RoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
UserEntity userEntity = ((UserAdapter)user).getUser();
public static List<MongoRoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
MongoUserEntity userEntity = ((UserAdapter)user).getUser();
List<String> 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<RoleEntity> getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) {
ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntity();
public static List<MongoRoleEntity> getAllScopesOfClient(ClientModel client, MongoStoreInvocationContext invContext) {
ClientEntity scopedEntity = ((ClientAdapter)client).getMongoEntityAsClient();
List<String> 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);
}
}

View file

@ -8,12 +8,11 @@ import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}

View file

@ -13,7 +13,6 @@ public class AddressWithFlats extends Address {
private List<String> flatNumbers;
@MongoField
public List<String> getFlatNumbers() {
return flatNumbers;
}

View file

@ -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<? extends MongoEntity>[] MANAGED_DATA_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
private static final Class<?>[] MANAGED_DATA_TYPES = (Class<?>[])new Class<?>[] {
Person.class,
Address.class,
AddressWithFlats.class

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "persons")
public class Person extends AbstractMongoIdentifiableEntity {
public class Person implements MongoIdentifiableEntity {
private String id;
private String firstName;
private int age;
private List<String> kids;
@ -23,7 +24,14 @@ public class Person extends AbstractMongoIdentifiableEntity {
private List<Gender> genders;
private Map<String, String> attributes = new HashMap<String, String>();
@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<Gender> getGenders() {
return genders;
}
@ -59,7 +64,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.genders = genders;
}
@MongoField
public List<String> getKids() {
return kids;
}
@ -68,7 +72,6 @@ public class Person extends AbstractMongoIdentifiableEntity {
this.kids = kids;
}
@MongoField
public List<AddressWithFlats> 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<String, String> 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
}

View file

@ -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 {

View file

@ -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());

View file

@ -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

View file

@ -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());

View file

@ -102,6 +102,7 @@
<module>timer</module>
<module>bundled-war-example</module>
<module>project-integrations</module>
<module>export-import</module>
</modules>
<dependencyManagement>
@ -323,6 +324,13 @@
<version>1.3.1b</version>
</dependency>
<!-- Encrypted ZIP -->
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
<version>1.0.1</version>
</dependency>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>

View file

@ -225,6 +225,21 @@
<scope>provided</scope>
</dependency>
<!-- export/import -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
</dependency>
</dependencies>

View file

@ -85,6 +85,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-picketlink-api</artifactId>

View file

@ -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<ExportImportProvider> providers = ProviderLoader.load(ExportImportProvider.class).iterator();
if (providers.hasNext()) {
ExportImportProvider exportImport = providers.next();
exportImport.checkExportImport(factory);
} else {
log.warn("No ExportImportProvider found!");
}
}
}

View file

@ -233,6 +233,17 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
@ -346,6 +357,12 @@
<artifactId>picketlink-common</artifactId>
</dependency>
<!-- Encrypted ZIP -->
<dependency>
<groupId>de.idyl</groupId>
<artifactId>winzipaes</artifactId>
</dependency>
<!-- This adds couple of other dependencies (like picketlink) -->
<dependency>
<groupId>org.keycloak</groupId>