Refactoring Mongo model. All unit tests passing with mongo

This commit is contained in:
mposolda 2014-02-01 00:08:40 +01:00
parent f54e041070
commit 81ff7b0c6d
88 changed files with 2638 additions and 2823 deletions

View file

@ -17,6 +17,17 @@
<groupId>net.iharder</groupId>
<artifactId>base64</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View file

@ -1,15 +0,0 @@
package org.keycloak.models;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class IdGenerator {
private static AtomicLong counter = new AtomicLong(1);
public static String generateId() {
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
}
}

View file

@ -0,0 +1,86 @@
package org.keycloak.models.utils;
import java.io.IOException;
import java.io.StringWriter;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.models.RoleModel;
import org.keycloak.util.PemUtils;
/**
* Set of helper methods, which are useful in various model implementations.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public final class KeycloakModelUtils {
private KeycloakModelUtils() {
}
private static AtomicLong counter = new AtomicLong(1);
public static String generateId() {
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
}
public static PublicKey getPublicKey(String publicKeyPem) {
if (publicKeyPem != null) {
try {
return PemUtils.decodePublicKey(publicKeyPem);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
public static PrivateKey getPrivateKey(String privateKeyPem) {
if (privateKeyPem != null) {
try {
return PemUtils.decodePrivateKey(privateKeyPem);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return null;
}
public static String getPemFromKey(Key key) {
StringWriter writer = new StringWriter();
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(key);
pemWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
return PemUtils.removeBeginEnd(s);
}
/**
* Deep search if given role is descendant of composite role
*
* @param role role to check
* @param composite composite role
* @param visited set of already visited roles (used for recursion)
* @return true if "role" is descendant of "composite"
*/
public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
if (visited.contains(composite)) return false;
visited.add(composite);
Set<RoleModel> composites = composite.getComposites();
if (composites.contains(role)) return true;
for (RoleModel contained : composites) {
if (!contained.isComposite()) continue;
if (searchFor(role, contained, visited)) return true;
}
return false;
}
}

View file

@ -1,18 +0,0 @@
package org.keycloak.models.utils;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public final class KeycloakSessionUtils {
private KeycloakSessionUtils() {
}
private static AtomicLong counter = new AtomicLong(1);
public static String generateId() {
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
}
}

View file

@ -2,7 +2,7 @@ package org.keycloak.models.jpa;
import org.keycloak.models.*;
import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.utils.KeycloakSessionUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@ -28,7 +28,7 @@ public class JpaKeycloakSession implements KeycloakSession {
@Override
public RealmModel createRealm(String name) {
return createRealm(KeycloakSessionUtils.generateId(), name);
return createRealm(KeycloakModelUtils.generateId(), name);
}
@Override

View file

@ -13,6 +13,7 @@ import org.keycloak.models.jpa.entities.SocialLinkEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
import org.keycloak.models.jpa.entities.UserScopeMappingEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.PemUtils;
import org.keycloak.models.ApplicationModel;
@ -187,59 +188,29 @@ public class RealmAdapter implements RealmModel {
@Override
public PublicKey getPublicKey() {
if (publicKey != null) return publicKey;
String pem = getPublicKeyPem();
if (pem != null) {
try {
publicKey = PemUtils.decodePublicKey(pem);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
StringWriter writer = new StringWriter();
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(publicKey);
pemWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
setPublicKeyPem(PemUtils.removeBeginEnd(s));
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
setPublicKeyPem(publicKeyPem);
}
@Override
public PrivateKey getPrivateKey() {
if (privateKey != null) return privateKey;
String pem = getPrivateKeyPem();
if (pem != null) {
try {
privateKey = PemUtils.decodePrivateKey(pem);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@Override
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
StringWriter writer = new StringWriter();
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(privateKey);
pemWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
setPrivateKeyPem(PemUtils.removeBeginEnd(s));
String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
setPrivateKeyPem(privateKeyPem);
}
protected RequiredCredentialModel initRequiredCredentialModel(String type) {

View file

@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
import org.keycloak.models.jpa.entities.RealmRoleEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import java.util.HashSet;
@ -94,27 +95,13 @@ public class RoleAdapter implements RoleModel {
return set;
}
public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
if (visited.contains(composite)) return false;
visited.add(composite);
Set<RoleModel> composites = composite.getComposites();
if (composites.contains(role)) return true;
for (RoleModel contained : composites) {
if (!contained.isComposite()) continue;
if (searchFor(role, contained, visited)) return true;
}
return false;
}
@Override
public boolean hasRole(RoleModel role) {
if (this.equals(role)) return true;
if (!isComposite()) return false;
Set<RoleModel> visited = new HashSet<RoleModel>();
return searchFor(role, this, visited);
return KeycloakModelUtils.searchFor(role, this, visited);
}
@Override

View file

@ -5,7 +5,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-alpha-1</version>
<version>1.0-alpha-2-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -41,11 +41,6 @@
<artifactId>picketlink-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
@ -54,7 +49,7 @@
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>

View file

@ -1,57 +0,0 @@
package org.keycloak.test.common;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.MongodConfig;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.services.utils.PropertiesManager;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext {
protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class);
private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS;
private MongodExecutable mongodExe;
private MongodProcess mongod;
@Override
public void beforeTestClass() {
logger.info("Bootstrapping MongoDB on localhost, port " + PORT);
try {
mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, PORT, Network.localhostIsIPv6()));
mongod = mongodExe.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("MongoDB bootstrapped successfully");
}
@Override
public void afterTestClass() {
if (mongodExe != null) {
if (mongod != null) {
mongod.stop();
}
mongodExe.stop();
}
logger.info("MongoDB stopped successfully");
// Reset this, so other tests are not affected
PropertiesManager.setDefaultSessionFactoryType();
}
@Override
public void initEnvironment() {
PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO);
PropertiesManager.setMongoHost("localhost");
PropertiesManager.setMongoPort(PORT);
PropertiesManager.setMongoDbName("keycloakTest");
PropertiesManager.setDropDatabaseOnStartup(true);
}
}

View file

@ -1,53 +0,0 @@
package org.keycloak.services.listeners;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.MongodConfig;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.services.utils.PropertiesManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoRunnerListener implements ServletContextListener {
protected static final Logger logger = Logger.getLogger(MongoRunnerListener.class);
private MongodExecutable mongodExe;
private MongodProcess mongod;
@Override
public void contextInitialized(ServletContextEvent sce) {
if (PropertiesManager.bootstrapEmbeddedMongoAtContextInit()) {
int port = PropertiesManager.getMongoPort();
logger.info("Going to start embedded MongoDB on port=" + port);
try {
mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, port, Network.localhostIsIPv6()));
mongod = mongodExe.start();
} catch (Exception e) {
logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage());
if (logger.isDebugEnabled()) {
logger.debug("Failed to start MongoDB", e);
}
}
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (mongodExe != null) {
if (mongod != null) {
logger.info("Going to stop embedded MongoDB.");
mongod.stop();
}
mongodExe.stop();
}
}
}

View file

@ -1,58 +0,0 @@
package org.keycloak.models.mongo;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PropertiesManager {
private static final String MONGO_HOST = "keycloak.mongodb.host";
private static final String MONGO_PORT = "keycloak.mongodb.port";
private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
// Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
// Port where unit tests will start embedded MongoDB instance
public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
public static String getMongoHost() {
return System.getProperty(MONGO_HOST, "localhost");
}
public static void setMongoHost(String mongoHost) {
System.setProperty(MONGO_HOST, mongoHost);
}
public static int getMongoPort() {
return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
}
public static void setMongoPort(int mongoPort) {
System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
}
public static String getMongoDbName() {
return System.getProperty(MONGO_DB_NAME, "keycloak");
}
public static void setMongoDbName(String mongoMongoDbName) {
System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
}
public static boolean dropDatabaseOnStartup() {
return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
}
public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
}
public static boolean bootstrapEmbeddedMongoAtContextInit() {
return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
}
}

View file

@ -1,37 +0,0 @@
package org.keycloak.models.mongo.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject implements AttributedNoSQLObject {
// Simple hashMap for now (no thread-safe)
private Map<String, String> attributes = new HashMap<String, String>();
@Override
public void setAttribute(String name, String value) {
attributes.put(name, value);
}
@Override
public void removeAttribute(String name) {
// attributes.remove(name);
// ensure that particular attribute has null value, so it will be deleted in DB. TODO: needs to be improved
attributes.put(name, null);
}
@Override
public String getAttribute(String name) {
return attributes.get(name);
}
@Override
public Map<String, String> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
}

View file

@ -3,10 +3,10 @@ package org.keycloak.models.mongo.api;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractNoSQLObject implements NoSQLObject {
public abstract class AbstractMongoEntity implements MongoEntity {
@Override
public void afterRemove(NoSQL noSQL) {
public void afterRemove(MongoStore mongoStore) {
// Empty by default
}
}

View file

@ -1,17 +0,0 @@
package org.keycloak.models.mongo.api;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AttributedNoSQLObject extends NoSQLObject {
void setAttribute(String name, String value);
void removeAttribute(String name);
String getAttribute(String name);
Map<String, String> getAttributes();
}

View file

@ -15,7 +15,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Inherited
public @interface NoSQLCollection {
public @interface MongoCollection {
String collectionName();
}

View file

@ -1,16 +1,16 @@
package org.keycloak.models.mongo.api;
/**
* Base interface for object, which is persisted in NoSQL database
* Base interface for object, which is persisted in Mongo
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface NoSQLObject {
public interface MongoEntity {
/**
* Lifecycle callback, which is called after removal of this object from NoSQL database.
* Lifecycle callback, which is called after removal of this object from Mongo.
* It may be useful for triggering removal of wired objects.
*/
void afterRemove(NoSQL noSQL);
void afterRemove(MongoStore mongoStore);
}

View file

@ -14,7 +14,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD})
@Documented
@Retention(RUNTIME)
public @interface NoSQLField {
public @interface MongoField {
// TODO: fieldName add lazy loading?
}

View file

@ -14,5 +14,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({METHOD, FIELD})
@Documented
@Retention(RUNTIME)
public @interface NoSQLId {
public @interface MongoId {
}

View file

@ -0,0 +1,43 @@
package org.keycloak.models.mongo.api;
import com.mongodb.DBObject;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface MongoStore {
/**
* Insert new object
*
* @param object to update
*/
void insertObject(MongoEntity object);
/**
* Update existing object
*
* @param object to update
*/
void updateObject(MongoEntity object);
<T extends MongoEntity> T loadObject(Class<T> type, String oid);
<T extends MongoEntity> T loadSingleObject(Class<T> type, DBObject query);
<T extends MongoEntity> List<T> loadObjects(Class<T> type, DBObject query);
// Object must have filled oid
boolean removeObject(MongoEntity object);
boolean removeObject(Class<? extends MongoEntity> type, String oid);
boolean removeObjects(Class<? extends MongoEntity> type, DBObject query);
<S> boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent);
<S> void pullItemFromList(MongoEntity object, String listPropertyName, S itemToPull);
}

View file

@ -1,36 +0,0 @@
package org.keycloak.models.mongo.api;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface NoSQL {
/**
* Insert object if it's oid is null. Otherwise update
*/
void saveObject(NoSQLObject object);
<T extends NoSQLObject> T loadObject(Class<T> type, String oid);
<T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query);
<T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query);
// Object must have filled oid
void removeObject(NoSQLObject object);
void removeObject(Class<? extends NoSQLObject> type, String oid);
void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query);
NoSQLQueryBuilder createQueryBuilder();
<S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush);
<S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull);
}

View file

@ -1,26 +0,0 @@
package org.keycloak.models.mongo.api.query;
import java.util.Collections;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class NoSQLQuery {
private final Map<String, Object> queryAttributes;
NoSQLQuery(Map<String, Object> queryAttributes) {
this.queryAttributes = queryAttributes;
};
public Map<String, Object> getQueryAttributes() {
return Collections.unmodifiableMap(queryAttributes);
}
@Override
public String toString() {
return "NoSQLQuery [" + queryAttributes + "]";
}
}

View file

@ -1,31 +0,0 @@
package org.keycloak.models.mongo.api.query;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class NoSQLQueryBuilder {
private Map<String, Object> queryAttributes = new HashMap<String, Object>();
protected NoSQLQueryBuilder() {};
public NoSQLQuery build() {
return new NoSQLQuery(queryAttributes);
}
public NoSQLQueryBuilder andCondition(String name, Object value) {
this.put(name, value);
return this;
}
public abstract NoSQLQueryBuilder inCondition(String name, List<?> values);
protected void put(String name, Object value) {
queryAttributes.put(name, value);
}
}

View file

@ -8,7 +8,13 @@ package org.keycloak.models.mongo.api.types;
*/
public interface Converter<T, S> {
S convertObject(T objectToConvert);
/**
* Convert object from one type to expected type
*
* @param converterContext Encapsulates reference to converted object and other things, which might be helpful in conversion
* @return converted object
*/
S convertObject(ConverterContext<T> converterContext);
Class<? extends T> getConverterObjectType();

View file

@ -0,0 +1,36 @@
package org.keycloak.models.mongo.api.types;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ConverterContext<T> {
// object to convert
private final T objectToConvert;
// expected return type, which could be useful information in some converters, so they are able to dynamically instantiate types
private final Class<?> expectedReturnType;
// in case that expected return type is generic type (like "List<String>"), then genericTypes could contain list of expected generic arguments
private final List<Class<?>> genericTypes;
public ConverterContext(T objectToConvert, Class<?> expectedReturnType, List<Class<?>> genericTypes) {
this.objectToConvert = objectToConvert;
this.expectedReturnType = expectedReturnType;
this.genericTypes = genericTypes;
}
public T getObjectToConvert() {
return objectToConvert;
}
public Class<?> getExpectedReturnType() {
return expectedReturnType;
}
public List<Class<?>> getGenericTypes() {
return genericTypes;
}
}

View file

@ -46,19 +46,22 @@ public class TypeConverter {
}
public <S> S convertDBObjectToApplicationObject(Object dbObject, Class<S> expectedApplicationObjectType) {
public Object convertDBObjectToApplicationObject(ConverterContext<Object> context) {
Object dbObject = context.getObjectToConvert();
Class<?> expectedApplicationObjectType = context.getExpectedReturnType();
Class<?> dbObjectType = dbObject.getClass();
Converter<Object, S> converter;
Converter<Object, Object> converter;
Map<Class<?>, Converter<?, ?>> appObjects = dbObjectConverters.get(dbObjectType);
if (appObjects == null) {
throw new IllegalArgumentException("Not found any converters for type " + dbObjectType);
} else {
if (appObjects.size() == 1) {
converter = (Converter<Object, S>)appObjects.values().iterator().next();
converter = (Converter<Object, Object>)appObjects.values().iterator().next();
} else {
// Try to find converter for requested application type
converter = (Converter<Object, S>)getAppConverterForType(expectedApplicationObjectType, appObjects);
converter = (Converter<Object, Object>)getAppConverterForType(context.getExpectedReturnType(), appObjects);
}
}
@ -70,7 +73,7 @@ public class TypeConverter {
" but we need type " + expectedApplicationObjectType);
} */
return converter.convertObject(dbObject);
return converter.convertObject(context);
}
@ -84,7 +87,7 @@ public class TypeConverter {
throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() +
" but we need type " + expectedDBObjectType);
}
return converter.convertObject(applicationObject);
return converter.convertObject(new ConverterContext(applicationObject, expectedDBObjectType, null));
}
// Try to find converter for given type or all it's supertypes

View file

@ -1,38 +0,0 @@
package org.keycloak.models.mongo.impl;
import com.mongodb.BasicDBObject;
import org.bson.types.ObjectId;
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoDBQueryBuilder extends NoSQLQueryBuilder {
protected MongoDBQueryBuilder() {};
@Override
public NoSQLQueryBuilder inCondition(String name, List<?> values) {
if (values == null) {
values = new LinkedList<Object>();
}
if ("_id".equals(name)) {
// we need to convert Strings to ObjectID
List<ObjectId> objIds = new ArrayList<ObjectId>();
for (Object object : values) {
ObjectId objectId = new ObjectId(object.toString());
objIds.add(objectId);
}
values = objIds;
}
BasicDBObject inObject = new BasicDBObject("$in", values);
put(name, inObject);
return this;
}
}

View file

@ -8,20 +8,21 @@ import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
import org.jboss.logging.Logger;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
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.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
import org.keycloak.models.mongo.api.types.TypeConverter;
import org.keycloak.models.mongo.impl.types.BasicDBListConverter;
import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter;
import org.keycloak.models.mongo.impl.types.BasicDBObjectToMapConverter;
import org.keycloak.models.mongo.impl.types.EnumToStringConverter;
import org.keycloak.models.mongo.impl.types.ListConverter;
import org.keycloak.models.mongo.impl.types.NoSQLObjectConverter;
import org.keycloak.models.mongo.impl.types.MapConverter;
import org.keycloak.models.mongo.impl.types.MongoEntityConverter;
import org.keycloak.models.mongo.impl.types.SimpleConverter;
import org.keycloak.models.mongo.impl.types.StringToEnumConverter;
import org.picketlink.common.properties.Property;
@ -30,6 +31,7 @@ import org.picketlink.common.properties.query.PropertyQueries;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -38,19 +40,19 @@ import java.util.concurrent.ConcurrentMap;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoDBImpl implements NoSQL {
public class MongoStoreImpl implements MongoStore {
private static final Class<?>[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class };
private static final Class<?>[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class, byte[].class };
private final DB database;
private static final Logger logger = Logger.getLogger(MongoDBImpl.class);
private static final Logger logger = Logger.getLogger(MongoStoreImpl.class);
private final TypeConverter typeConverter;
private ConcurrentMap<Class<? extends NoSQLObject>, ObjectInfo> objectInfoCache =
new ConcurrentHashMap<Class<? extends NoSQLObject>, ObjectInfo>();
private ConcurrentMap<Class<? extends MongoEntity>, ObjectInfo> objectInfoCache =
new ConcurrentHashMap<Class<? extends MongoEntity>, ObjectInfo>();
public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class<? extends NoSQLObject>[] managedDataTypes) {
public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<? extends MongoEntity>[] managedEntityTypes) {
this.database = database;
typeConverter = new TypeConverter();
@ -66,26 +68,45 @@ public class MongoDBImpl implements NoSQL {
typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class));
typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter));
typeConverter.addAppObjectConverter(new MapConverter(HashMap.class));
typeConverter.addAppObjectConverter(new MapConverter(Map.class));
typeConverter.addDBObjectConverter(new BasicDBObjectToMapConverter());
// Enum converters
typeConverter.addAppObjectConverter(new EnumToStringConverter());
typeConverter.addDBObjectConverter(new StringToEnumConverter());
for (Class<? extends NoSQLObject> type : managedDataTypes) {
for (Class<? extends MongoEntity> type : managedEntityTypes) {
getObjectInfo(type);
typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type));
typeConverter.addAppObjectConverter(new MongoEntityConverter(this, typeConverter, type));
typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type));
}
if (dropDatabaseOnStartup) {
if (clearCollectionsOnStartup) {
// dropDatabase();
clearManagedCollections(managedEntityTypes);
}
}
protected void dropDatabase() {
this.database.dropDatabase();
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) {
DBCollection dbCollection = getDBCollectionForType(clazz);
if (dbCollection != null) {
dbCollection.remove(new BasicDBObject());
logger.debug("Collection " + dbCollection.getName() + " cleared from " + this.database.getName());
}
}
}
@Override
public void saveObject(NoSQLObject object) {
Class<? extends NoSQLObject> clazz = object.getClass();
public void insertObject(MongoEntity object) {
Class<? extends MongoEntity> clazz = object.getClass();
// Find annotations for ID, for all the properties and for the name of the collection.
ObjectInfo objectInfo = getObjectInfo(clazz);
@ -95,36 +116,57 @@ public class MongoDBImpl implements NoSQL {
DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
// Decide if we should insert or update (based on presence of oid property in original object)
Property<String> oidProperty = objectInfo.getOidProperty();
String currentId = oidProperty == null ? null : oidProperty.getValue(object);
if (currentId == null) {
// Inserting object, which already has oid property set. So we need to set "_id"
if (currentId != null) {
dbObject.put("_id", getObjectId(currentId));
}
dbCollection.insert(dbObject);
// Add oid to value of given object
if (oidProperty != null) {
if (currentId == null && oidProperty != null) {
oidProperty.setValue(object, dbObject.getString("_id"));
}
}
@Override
public void updateObject(MongoEntity object) {
Class<? extends MongoEntity> clazz = object.getClass();
ObjectInfo objectInfo = getObjectInfo(clazz);
BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class);
DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
Property<String> oidProperty = objectInfo.getOidProperty();
String currentId = oidProperty == null ? null : oidProperty.getValue(object);
if (currentId == null) {
throw new IllegalStateException("Can't update object without id: " + object);
} else {
BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId));
BasicDBObject query = new BasicDBObject("_id", getObjectId(currentId));
dbCollection.update(query, dbObject);
}
}
@Override
public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
public <T extends MongoEntity> T loadObject(Class<T> type, String oid) {
DBCollection dbCollection = getDBCollectionForType(type);
BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid));
BasicDBObject idQuery = new BasicDBObject("_id", getObjectId(oid));
DBObject dbObject = dbCollection.findOne(idQuery);
return typeConverter.convertDBObjectToApplicationObject(dbObject, type);
if (dbObject == null) return null;
ConverterContext<Object> converterContext = new ConverterContext<Object>(dbObject, type, null);
return (T)typeConverter.convertDBObjectToApplicationObject(converterContext);
}
@Override
public <T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query) {
public <T extends MongoEntity> T loadSingleObject(Class<T> type, DBObject query) {
List<T> result = loadObjects(type, query);
if (result.size() > 1) {
throw new IllegalStateException("There are " + result.size() + " results for type=" + type + ", query=" + query + ". We expect just one");
@ -138,71 +180,64 @@ public class MongoDBImpl implements NoSQL {
@Override
public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query) {
public <T extends MongoEntity> List<T> loadObjects(Class<T> type, DBObject query) {
DBCollection dbCollection = getDBCollectionForType(type);
BasicDBObject dbQuery = getDBQueryFromQuery(query);
DBCursor cursor = dbCollection.find(dbQuery);
DBCursor cursor = dbCollection.find(query);
return convertCursor(type, cursor);
}
@Override
public void removeObject(NoSQLObject object) {
Class<? extends NoSQLObject> type = object.getClass();
public boolean removeObject(MongoEntity object) {
Class<? extends MongoEntity> type = object.getClass();
ObjectInfo objectInfo = getObjectInfo(type);
Property<String> idProperty = objectInfo.getOidProperty();
String oid = idProperty.getValue(object);
removeObject(type, oid);
return removeObject(type, oid);
}
@Override
public void removeObject(Class<? extends NoSQLObject> type, String oid) {
NoSQLObject found = loadObject(type, oid);
public boolean removeObject(Class<? extends MongoEntity> type, String oid) {
MongoEntity found = loadObject(type, oid);
if (found == null) {
logger.warn("Object of type: " + type + ", oid: " + oid + " doesn't exist in MongoDB. Skip removal");
return false;
} else {
DBCollection dbCollection = getDBCollectionForType(type);
BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid));
BasicDBObject dbQuery = new BasicDBObject("_id", getObjectId(oid));
dbCollection.remove(dbQuery);
logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB.");
found.afterRemove(this);
return true;
}
}
@Override
public void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query) {
List<? extends NoSQLObject> foundObjects = loadObjects(type, query);
public boolean removeObjects(Class<? extends MongoEntity> type, DBObject query) {
List<? extends MongoEntity> foundObjects = loadObjects(type, query);
if (foundObjects.size() == 0) {
logger.info("Not found any objects of type: " + type + ", query: " + query);
return false;
} else {
DBCollection dbCollection = getDBCollectionForType(type);
BasicDBObject dbQuery = getDBQueryFromQuery(query);
dbCollection.remove(dbQuery);
dbCollection.remove(query);
logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query);
for (NoSQLObject found : foundObjects) {
for (MongoEntity found : foundObjects) {
found.afterRemove(this);
}
return true;
}
}
@Override
public NoSQLQueryBuilder createQueryBuilder() {
return new MongoDBQueryBuilder();
}
@Override
public <S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush) {
Class<? extends NoSQLObject> type = object.getClass();
public <S> boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent) {
Class<? extends MongoEntity> type = object.getClass();
ObjectInfo objectInfo = getObjectInfo(type);
Property<String> oidProperty = getObjectInfo(type).getOidProperty();
@ -221,21 +256,28 @@ public class MongoDBImpl implements NoSQL {
list = new ArrayList<S>();
listProperty.setValue(object, list);
}
// Return if item is already in list
if (skipIfAlreadyPresent && list.contains(itemToPush)) {
return false;
}
list.add(itemToPush);
// Push item to DB. We always convert whole list, so it's not so optimal...
// Push item to DB. We always convert whole list, so it's not so optimal...TODO: use $push if possible
BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(list, BasicDBList.class);
BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
BasicDBObject query = new BasicDBObject("_id", getObjectId(oidProperty.getValue(object)));
BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList);
BasicDBObject setCommand = new BasicDBObject("$set", listObject);
getDBCollectionForType(type).update(query, setCommand);
return true;
}
@Override
public <S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull) {
Class<? extends NoSQLObject> type = object.getClass();
public <S> void pullItemFromList(MongoEntity object, String listPropertyName, S itemToPull) {
Class<? extends MongoEntity> type = object.getClass();
ObjectInfo objectInfo = getObjectInfo(type);
Property<String> oidProperty = getObjectInfo(type).getOidProperty();
@ -256,7 +298,7 @@ public class MongoDBImpl implements NoSQL {
// Pull item from DB
Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class);
BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
BasicDBObject query = new BasicDBObject("_id", getObjectId(oidProperty.getValue(object)));
BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull);
BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject);
getDBCollectionForType(type).update(query, pullCommand);
@ -272,14 +314,14 @@ public class MongoDBImpl implements NoSQL {
typeConverter.addDBObjectConverter(converter);
}
public ObjectInfo getObjectInfo(Class<? extends NoSQLObject> objectClass) {
public ObjectInfo getObjectInfo(Class<? extends MongoEntity> objectClass) {
ObjectInfo objectInfo = objectInfoCache.get(objectClass);
if (objectInfo == null) {
Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult();
Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoId.class)).getFirstResult();
List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList();
List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList();
NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class);
MongoCollection classAnnotation = objectClass.getAnnotation(MongoCollection.class);
String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName();
objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties);
@ -293,12 +335,13 @@ public class MongoDBImpl implements NoSQL {
return objectInfo;
}
private <T extends NoSQLObject> List<T> convertCursor(Class<T> type, DBCursor cursor) {
private <T extends MongoEntity> List<T> convertCursor(Class<T> type, DBCursor cursor) {
List<T> result = new ArrayList<T>();
try {
for (DBObject dbObject : cursor) {
T converted = typeConverter.convertDBObjectToApplicationObject(dbObject, type);
ConverterContext<Object> converterContext = new ConverterContext<Object>(dbObject, type, null);
T converted = (T)typeConverter.convertDBObjectToApplicationObject(converterContext);
result.add(converted);
}
} finally {
@ -308,17 +351,18 @@ public class MongoDBImpl implements NoSQL {
return result;
}
private DBCollection getDBCollectionForType(Class<? extends NoSQLObject> type) {
private DBCollection getDBCollectionForType(Class<? extends MongoEntity> type) {
ObjectInfo objectInfo = getObjectInfo(type);
return database.getCollection(objectInfo.getDbCollectionName());
String dbCollectionName = objectInfo.getDbCollectionName();
return dbCollectionName==null ? null : database.getCollection(objectInfo.getDbCollectionName());
}
private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) {
Map<String, Object> queryAttributes = query.getQueryAttributes();
BasicDBObject dbQuery = new BasicDBObject();
for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
dbQuery.append(queryAttr.getKey(), queryAttr.getValue());
// We allow ObjectId to be both "ObjectId" or "String".
private Object getObjectId(String idAsString) {
if (ObjectId.isValid(idAsString)) {
return new ObjectId(idAsString);
} else {
return idAsString;
}
return dbQuery;
}
}

View file

@ -1,6 +1,6 @@
package org.keycloak.models.mongo.impl;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.MongoEntity;
import org.picketlink.common.properties.Property;
import java.util.Collection;
@ -14,7 +14,7 @@ import java.util.Map;
*/
public class ObjectInfo {
private final Class<? extends NoSQLObject> objectClass;
private final Class<? extends MongoEntity> objectClass;
private final String dbCollectionName;
@ -22,7 +22,7 @@ public class ObjectInfo {
private final Map<String, Property<Object>> properties;
public ObjectInfo(Class<? extends NoSQLObject> objectClass, String dbCollectionName, Property<String> oidProperty, List<Property<Object>> properties) {
public ObjectInfo(Class<? extends MongoEntity> objectClass, String dbCollectionName, Property<String> oidProperty, List<Property<Object>> properties) {
this.objectClass = objectClass;
this.dbCollectionName = dbCollectionName;
this.oidProperty = oidProperty;
@ -34,7 +34,7 @@ public class ObjectInfo {
this.properties = Collections.unmodifiableMap(props);
}
public Class<? extends NoSQLObject> getObjectClass() {
public Class<? extends MongoEntity> getObjectClass() {
return objectClass;
}

View file

@ -1,16 +1,17 @@
package org.keycloak.models.mongo.impl.types;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
import org.keycloak.models.mongo.api.types.TypeConverter;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
public class BasicDBListConverter implements Converter<BasicDBList, List> {
private final TypeConverter typeConverter;
@ -19,16 +20,14 @@ public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
}
@Override
public ArrayList convertObject(BasicDBList dbList) {
public List convertObject(ConverterContext<BasicDBList> context) {
BasicDBList dbList = context.getObjectToConvert();
ArrayList<Object> appObjects = new ArrayList<Object>();
Class<?> expectedListElementType = null;
Class<?> expectedListElementType = context.getGenericTypes().get(0);
for (Object dbObject : dbList) {
if (expectedListElementType == null) {
expectedListElementType = findExpectedListElementType(dbObject);
}
appObjects.add(typeConverter.convertDBObjectToApplicationObject(dbObject, expectedListElementType));
ConverterContext<Object> newContext = new ConverterContext<Object>(dbObject, expectedListElementType, null);
appObjects.add(typeConverter.convertDBObjectToApplicationObject(newContext));
}
return appObjects;
}
@ -39,35 +38,7 @@ public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
}
@Override
public Class<ArrayList> getExpectedReturnType() {
return ArrayList.class;
}
private Class<?> findExpectedListElementType(Object dbObject) {
if (dbObject instanceof BasicDBObject) {
BasicDBObject basicDBObject = (BasicDBObject) dbObject;
String type = (String)basicDBObject.get(ListConverter.OBJECT_TYPE);
if (type == null) {
throw new IllegalStateException("Not found OBJECT_TYPE key inside object " + dbObject);
}
basicDBObject.remove(ListConverter.OBJECT_TYPE);
try {
return Class.forName(type);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
} else {
// Special case (if we have String like "org.keycloak.Gender###MALE" we expect that substring before ### is className
if (String.class.equals(dbObject.getClass())) {
String dbObjString = (String)dbObject;
if (dbObjString.contains(ClassCache.SPLIT)) {
String className = dbObjString.substring(0, dbObjString.indexOf(ClassCache.SPLIT));
return ClassCache.getInstance().getOrLoadClass(className);
}
}
return dbObject.getClass();
}
public Class<List> getExpectedReturnType() {
return List.class;
}
}

View file

@ -1,12 +1,17 @@
package org.keycloak.models.mongo.impl.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.mongodb.BasicDBObject;
import org.jboss.logging.Logger;
import org.keycloak.models.mongo.api.AttributedNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
import org.keycloak.models.mongo.api.types.TypeConverter;
import org.keycloak.models.mongo.impl.MongoDBImpl;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.impl.ObjectInfo;
import org.picketlink.common.properties.Property;
import org.picketlink.common.reflection.Types;
@ -14,31 +19,32 @@ import org.picketlink.common.reflection.Types;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBObjectConverter<S extends NoSQLObject> implements Converter<BasicDBObject, S> {
public class BasicDBObjectConverter<S extends MongoEntity> implements Converter<BasicDBObject, S> {
private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class);
private final MongoDBImpl mongoDBImpl;
private final MongoStoreImpl mongoStoreImpl;
private final TypeConverter typeConverter;
private final Class<S> expectedNoSQLObjectType;
private final Class<S> expectedObjectType;
public BasicDBObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<S> expectedNoSQLObjectType) {
this.mongoDBImpl = mongoDBImpl;
public BasicDBObjectConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class<S> expectedObjectType) {
this.mongoStoreImpl = mongoStoreImpl;
this.typeConverter = typeConverter;
this.expectedNoSQLObjectType = expectedNoSQLObjectType;
this.expectedObjectType = expectedObjectType;
}
@Override
public S convertObject(BasicDBObject dbObject) {
public S convertObject(ConverterContext<BasicDBObject> context) {
BasicDBObject dbObject = context.getObjectToConvert();
if (dbObject == null) {
return null;
}
ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType);
ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(expectedObjectType);
S object;
try {
object = expectedNoSQLObjectType.newInstance();
object = expectedObjectType.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -58,34 +64,50 @@ public class BasicDBObjectConverter<S extends NoSQLObject> implements Converter<
// It's declared property with @DBField annotation
setPropertyValue(object, value, property);
} else if (object instanceof AttributedNoSQLObject) {
// It's attributed object and property is not declared, so we will call setAttribute
((AttributedNoSQLObject)object).setAttribute(key, value.toString());
} else {
// Show warning if it's unknown
logger.warn("Property with key " + key + " not known for type " + expectedNoSQLObjectType);
logger.warn("Property with key " + key + " not known for type " + expectedObjectType);
}
}
return object;
}
private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) {
private void setPropertyValue(MongoEntity object, Object valueFromDB, Property property) {
if (valueFromDB == null) {
property.setValue(object, null);
return;
}
Class<?> expectedReturnType = property.getJavaClass();
ConverterContext<Object> context;
Type type = property.getBaseType();
// This can be the case when we have parameterized type (like "List<String>")
if (type instanceof ParameterizedType) {
ParameterizedType parameterized = (ParameterizedType) type;
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
List<Class<?>> genericTypes = new ArrayList<Class<?>>();
for (Type genericType : genericTypeArguments) {
genericTypes.add((Class<?>)genericType);
}
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
context = new ConverterContext<Object>(valueFromDB, expectedReturnType, genericTypes);
} else {
Class<?> expectedReturnType = (Class<?>)type;
// handle primitives
expectedReturnType = Types.boxedClass(expectedReturnType);
context = new ConverterContext<Object>(valueFromDB, expectedReturnType, null);
}
Object appObject = typeConverter.convertDBObjectToApplicationObject(context);
Object appObject = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedReturnType);
if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) {
property.setValue(object, appObject);
} else {
throw new IllegalStateException("Converted object " + appObject + " is not of type " + expectedReturnType +
throw new IllegalStateException("Converted object " + appObject + " is not of type " + context.getExpectedReturnType() +
". So can't be assigned as property " + property.getName() + " of " + object.getClass());
}
}
@ -97,6 +119,6 @@ public class BasicDBObjectConverter<S extends NoSQLObject> implements Converter<
@Override
public Class<S> getExpectedReturnType() {
return expectedNoSQLObjectType;
return expectedObjectType;
}
}

View file

@ -0,0 +1,44 @@
package org.keycloak.models.mongo.impl.types;
import java.util.HashMap;
import java.util.Map;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
/**
* For now, we support just convert to Map<String, String>
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BasicDBObjectToMapConverter implements Converter<BasicDBObject, Map> {
@Override
public Map convertObject(ConverterContext<BasicDBObject> context) {
BasicDBObject objectToConvert = context.getObjectToConvert();
HashMap<String, String> result = new HashMap<String, String>();
for (Map.Entry<String, Object> entry : objectToConvert.entrySet()) {
String key = entry.getKey();
String value = (String)entry.getValue();
if (key.contains(MapConverter.DOT_PLACEHOLDER)) {
key = key.replaceAll(MapConverter.DOT_PLACEHOLDER, ".");
}
result.put(key, value);
}
return result;
}
@Override
public Class<? extends BasicDBObject> getConverterObjectType() {
return BasicDBObject.class;
}
@Override
public Class<Map> getExpectedReturnType() {
return Map.class;
}
}

View file

@ -1,37 +0,0 @@
package org.keycloak.models.mongo.impl.types;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Helper class for caching of classNames to actual classes (Should help a bit to avoid expensive reflection calls)
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClassCache {
public static final String SPLIT = "###";
private static final ClassCache INSTANCE = new ClassCache();
private ConcurrentMap<String, Class<?>> cache = new ConcurrentHashMap<String, Class<?>>();
private ClassCache() {};
public static ClassCache getInstance() {
return INSTANCE;
}
public Class<?> getOrLoadClass(String className) {
Class<?> clazz = cache.get(className);
if (clazz == null) {
try {
clazz = Class.forName(className);
cache.putIfAbsent(className, clazz);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
}
return clazz;
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.mongo.impl.types;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -9,9 +10,10 @@ public class EnumToStringConverter implements Converter<Enum, String> {
// It will be saved in form of "org.keycloak.Gender#MALE" so it's possible to parse enumType out of it
@Override
public String convertObject(Enum objectToConvert) {
String className = objectToConvert.getClass().getName();
return className + ClassCache.SPLIT + objectToConvert.toString();
public String convertObject(ConverterContext<Enum> context) {
Enum objectToConvert = context.getObjectToConvert();
return objectToConvert.toString();
}
@Override

View file

@ -3,6 +3,7 @@ package org.keycloak.models.mongo.impl.types;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
import org.keycloak.models.mongo.api.types.TypeConverter;
import java.util.List;
@ -12,9 +13,6 @@ import java.util.List;
*/
public class ListConverter<T extends List> implements Converter<T, BasicDBList> {
// Key for ObjectType field, which points to actual Java type of element objects inside list
static final String OBJECT_TYPE = "OBJECT_TYPE";
private final TypeConverter typeConverter;
private final Class<T> listType;
@ -24,17 +22,13 @@ public class ListConverter<T extends List> implements Converter<T, BasicDBList>
}
@Override
public BasicDBList convertObject(T appObjectsList) {
public BasicDBList convertObject(ConverterContext<T> context) {
T appObjectsList = context.getObjectToConvert();
BasicDBList dbObjects = new BasicDBList();
for (Object appObject : appObjectsList) {
Object dbObject = typeConverter.convertApplicationObjectToDBObject(appObject, Object.class);
// We need to add OBJECT_TYPE key to object, so we can retrieve correct Java type of object during load of this list
if (dbObject instanceof BasicDBObject) {
BasicDBObject basicDBObject = (BasicDBObject)dbObject;
basicDBObject.put(OBJECT_TYPE, appObject.getClass().getName());
}
dbObjects.add(dbObject);
}
return dbObjects;

View file

@ -0,0 +1,52 @@
package org.keycloak.models.mongo.impl.types;
import java.util.Map;
import java.util.Set;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MapConverter<T extends Map> implements Converter<T, BasicDBObject> {
// Just some dummy way of encoding . character as it's not allowed by mongo in key fields
static final String DOT_PLACEHOLDER = "###";
private final Class<T> mapType;
public MapConverter(Class<T> mapType) {
this.mapType = mapType;
}
@Override
public BasicDBObject convertObject(ConverterContext<T> context) {
T objectToConvert = context.getObjectToConvert();
BasicDBObject dbObject = new BasicDBObject();
Set<Map.Entry> entries = objectToConvert.entrySet();
for (Map.Entry entry : entries) {
String key = (String)entry.getKey();
String value = (String)entry.getValue();
if (key.contains(".")) {
key = key.replaceAll("\\.", DOT_PLACEHOLDER);
}
dbObject.put(key, value);
}
return dbObject;
}
@Override
public Class<? extends T> getConverterObjectType() {
return mapType;
}
@Override
public Class<BasicDBObject> getExpectedReturnType() {
return BasicDBObject.class;
}
}

View file

@ -1,35 +1,36 @@
package org.keycloak.models.mongo.impl.types;
import com.mongodb.BasicDBObject;
import org.keycloak.models.mongo.api.AttributedNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
import org.keycloak.models.mongo.api.types.TypeConverter;
import org.keycloak.models.mongo.impl.MongoDBImpl;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.impl.ObjectInfo;
import org.picketlink.common.properties.Property;
import java.util.Collection;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T, BasicDBObject> {
public class MongoEntityConverter<T extends MongoEntity> implements Converter<T, BasicDBObject> {
private final MongoDBImpl mongoDBImpl;
private final MongoStoreImpl mongoStoreImpl;
private final TypeConverter typeConverter;
private final Class<T> expectedNoSQLObjectType;
public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<T> expectedNoSQLObjectType) {
this.mongoDBImpl = mongoDBImpl;
public MongoEntityConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class<T> expectedNoSQLObjectType) {
this.mongoStoreImpl = mongoStoreImpl;
this.typeConverter = typeConverter;
this.expectedNoSQLObjectType = expectedNoSQLObjectType;
}
@Override
public BasicDBObject convertObject(T applicationObject) {
ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(applicationObject.getClass());
public BasicDBObject convertObject(ConverterContext<T> context) {
T applicationObject = context.getObjectToConvert();
ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(applicationObject.getClass());
// Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped)
BasicDBObject dbObject = new BasicDBObject();
@ -42,15 +43,6 @@ public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T,
dbObject.put(propName, dbValue);
}
// Adding attributes
if (applicationObject instanceof AttributedNoSQLObject) {
AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)applicationObject;
Map<String, String> attributes = attributedObject.getAttributes();
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
dbObject.append(attribute.getKey(), attribute.getValue());
}
}
return dbObject;
}

View file

@ -1,8 +1,11 @@
package org.keycloak.models.mongo.impl.types;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
/**
* Just returns input
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SimpleConverter<T> implements Converter<T, T> {
@ -14,7 +17,8 @@ public class SimpleConverter<T> implements Converter<T, T> {
}
@Override
public T convertObject(T objectToConvert) {
public T convertObject(ConverterContext<T> context) {
T objectToConvert = context.getObjectToConvert();
return objectToConvert;
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.mongo.impl.types;
import org.keycloak.models.mongo.api.types.Converter;
import org.keycloak.models.mongo.api.types.ConverterContext;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -8,15 +9,10 @@ import org.keycloak.models.mongo.api.types.Converter;
public class StringToEnumConverter implements Converter<String, Enum> {
@Override
public Enum convertObject(String objectToConvert) {
int index = objectToConvert.indexOf(ClassCache.SPLIT);
if (index == -1) {
throw new IllegalStateException("Can't convert enum type with value " + objectToConvert);
}
public Enum convertObject(ConverterContext<String> context) {
String enumValue = context.getObjectToConvert();
String className = objectToConvert.substring(0, index);
String enumValue = objectToConvert.substring(index + 3);
Class<? extends Enum> clazz = (Class<? extends Enum>)ClassCache.getInstance().getOrLoadClass(className);
Class<? extends Enum> clazz = (Class<? extends Enum>)context.getExpectedReturnType();
return Enum.valueOf(clazz, enumValue);
}

View file

@ -2,6 +2,9 @@ package org.keycloak.models.mongo.keycloak;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelProvider;
import org.keycloak.models.mongo.keycloak.adapters.MongoKeycloakSessionFactory;
import org.keycloak.models.mongo.utils.MongoConfiguration;
import org.keycloak.models.mongo.utils.SystemPropertiesConfigurationProvider;
import java.lang.Override;
@ -18,16 +21,7 @@ public class MongoModelProvider implements ModelProvider {
@Override
public KeycloakSessionFactory createFactory() {
String host = PropertiesManager.getMongoHost();
int port = PropertiesManager.getMongoPort();
String dbName = PropertiesManager.getMongoDbName();
boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup();
// Create MongoDBSessionFactory via reflection now
try {
return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup);
} catch (Exception e) {
throw new RuntimeException(e);
}
MongoConfiguration config = SystemPropertiesConfigurationProvider.createConfiguration();
return new MongoKeycloakSessionFactory(config);
}
}

View file

@ -1,13 +1,15 @@
package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.keycloak.data.ApplicationData;
import org.keycloak.models.mongo.keycloak.data.RoleData;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
import org.keycloak.models.mongo.keycloak.entities.UserEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import java.util.ArrayList;
import java.util.HashSet;
@ -19,29 +21,38 @@ import java.util.Set;
*/
public class ApplicationAdapter implements ApplicationModel {
private final ApplicationData application;
private final NoSQL noSQL;
private final ApplicationEntity application;
private final MongoStore mongoStore;
private UserData resourceUser;
private UserAdapter resourceUser;
public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) {
this.application = applicationData;
this.noSQL = noSQL;
public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStore mongoStore) {
this(applicationEntity, null, mongoStore);
}
public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStore mongoStore) {
this.application = applicationEntity;
this.resourceUser = resourceUser;
this.mongoStore = mongoStore;
}
@Override
public void updateApplication() {
noSQL.saveObject(application);
mongoStore.updateObject(application);
}
@Override
public UserModel getApplicationUser() {
public UserAdapter getApplicationUser() {
// This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object
if (resourceUser == null) {
resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId());
UserEntity userEntity = mongoStore.loadObject(UserEntity.class, application.getResourceUserId());
if (userEntity == null) {
throw new IllegalStateException("User " + application.getResourceUserId() + " not found");
}
resourceUser = new UserAdapter(userEntity, mongoStore);
}
return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null;
return resourceUser;
}
@Override
@ -101,182 +112,90 @@ public class ApplicationAdapter implements ApplicationModel {
@Override
public RoleAdapter getRole(String name) {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("name", name)
.andCondition("applicationId", getId())
.build();
RoleData role = noSQL.loadSingleObject(RoleData.class, query);
DBObject query = new QueryBuilder()
.and("name").is(name)
.and("applicationId").is(getId())
.get();
RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query);
if (role == null) {
return null;
} else {
return new RoleAdapter(role, noSQL);
return new RoleAdapter(role, this, mongoStore);
}
}
@Override
public RoleModel getRoleById(String id) {
RoleData role = noSQL.loadObject(RoleData.class, id);
RoleEntity role = mongoStore.loadObject(RoleEntity.class, id);
if (role == null) {
return null;
} else {
return new RoleAdapter(role, noSQL);
return new RoleAdapter(role, this, mongoStore);
}
}
@Override
public void grantRole(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
noSQL.pushItemToList(userData, "roleIds", role.getId());
}
@Override
public boolean hasRole(UserModel user, String role) {
RoleModel roleModel = getRole(role);
return hasRole(user, roleModel);
}
@Override
public boolean hasRole(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
List<String> roleIds = userData.getRoleIds();
String roleId = role.getId();
if (roleIds != null) {
for (String currentId : roleIds) {
if (roleId.equals(currentId)) {
return true;
}
}
}
return false;
}
@Override
public RoleAdapter addRole(String name) {
if (getRole(name) != null) {
throw new IllegalArgumentException("Role " + name + " already exists");
RoleAdapter existing = getRole(name);
if (existing != null) {
return existing;
}
RoleData roleData = new RoleData();
roleData.setName(name);
roleData.setApplicationId(getId());
RoleEntity roleEntity = new RoleEntity();
roleEntity.setName(name);
roleEntity.setApplicationId(getId());
noSQL.saveObject(roleData);
return new RoleAdapter(roleData, noSQL);
mongoStore.insertObject(roleEntity);
return new RoleAdapter(roleEntity, this, mongoStore);
}
@Override
public List<RoleModel> getRoles() {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("applicationId", getId())
.build();
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
public boolean removeRoleById(String id) {
return mongoStore.removeObject(RoleEntity.class ,id);
}
List<RoleModel> result = new ArrayList<RoleModel>();
for (RoleData role : roles) {
result.add(new RoleAdapter(role, noSQL));
@Override
public Set<RoleModel> getRoles() {
DBObject query = new QueryBuilder()
.and("applicationId").is(getId())
.get();
List<RoleEntity> roles = mongoStore.loadObjects(RoleEntity.class, query);
Set<RoleModel> result = new HashSet<RoleModel>();
for (RoleEntity role : roles) {
result.add(new RoleAdapter(role, this, mongoStore));
}
return result;
}
// Static so that it can be used from RealmAdapter as well
static List<RoleData> getAllRolesOfUser(UserModel user, NoSQL noSQL) {
UserData userData = ((UserAdapter)user).getUser();
List<String> roleIds = userData.getRoleIds();
NoSQLQuery query = noSQL.createQueryBuilder()
.inCondition("_id", roleIds)
.build();
return noSQL.loadObjects(RoleData.class, query);
}
@Override
public Set<String> getRoleMappingValues(UserModel user) {
Set<String> result = new HashSet<String>();
List<RoleData> roles = getAllRolesOfUser(user, noSQL);
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
for (RoleData role : roles) {
public Set<RoleModel> getApplicationRoleMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore);
for (RoleEntity role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(role.getName());
result.add(new RoleAdapter(role, this, mongoStore));
}
}
return result;
}
@Override
public List<RoleModel> getRoleMappings(UserModel user) {
List<RoleModel> result = new ArrayList<RoleModel>();
List<RoleData> roles = getAllRolesOfUser(user, noSQL);
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
for (RoleData role : roles) {
public void addScope(RoleModel role) {
UserAdapter appUser = getApplicationUser();
mongoStore.pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true);
}
@Override
public Set<RoleModel> getApplicationScopeMappings(UserModel user) {
Set<RoleModel> result = new HashSet<RoleModel>();
List<RoleEntity> roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore);
for (RoleEntity role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(new RoleAdapter(role, noSQL));
}
}
return result;
}
@Override
public void deleteRoleMapping(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
noSQL.pullItemFromList(userData, "roleIds", role.getId());
}
@Override
public void addScopeMapping(UserModel agent, String roleName) {
RoleAdapter role = getRole(roleName);
if (role == null) {
throw new RuntimeException("Role not found");
}
addScopeMapping(agent, role);
}
@Override
public void addScopeMapping(UserModel agent, RoleModel role) {
UserData userData = ((UserAdapter)agent).getUser();
noSQL.pushItemToList(userData, "scopeIds", role.getId());
}
@Override
public void deleteScopeMapping(UserModel user, RoleModel role) {
UserData userData = ((UserAdapter)user).getUser();
noSQL.pullItemFromList(userData, "scopeIds", role.getId());
}
// Static so that it can be used from RealmAdapter as well
static List<RoleData> getAllScopesOfUser(UserModel user, NoSQL noSQL) {
UserData userData = ((UserAdapter)user).getUser();
List<String> roleIds = userData.getScopeIds();
NoSQLQuery query = noSQL.createQueryBuilder()
.inCondition("_id", roleIds)
.build();
return noSQL.loadObjects(RoleData.class, query);
}
@Override
public Set<String> getScopeMappingValues(UserModel agent) {
Set<String> result = new HashSet<String>();
List<RoleData> roles = getAllScopesOfUser(agent, noSQL);
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
for (RoleData role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(role.getName());
}
}
return result;
}
@Override
public List<RoleModel> getScopeMappings(UserModel agent) {
List<RoleModel> result = new ArrayList<RoleModel>();
List<RoleData> roles = getAllScopesOfUser(agent, noSQL);
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
for (RoleData role : roles) {
if (getId().equals(role.getApplicationId())) {
result.add(new RoleAdapter(role, noSQL));
result.add(new RoleAdapter(role, this, mongoStore));
}
}
return result;
@ -284,16 +203,45 @@ public class ApplicationAdapter implements ApplicationModel {
@Override
public List<String> getDefaultRoles() {
return null; //To change body of implemented methods use File | Settings | File Templates.
return application.getDefaultRoles();
}
@Override
public void addDefaultRole(String name) {
//To change body of implemented methods use File | Settings | File Templates.
RoleModel role = getRole(name);
if (role == null) {
addRole(name);
}
mongoStore.pushItemToList(application, "defaultRoles", name, true);
}
@Override
public void updateDefaultRoles(String[] defaultRoles) {
//To change body of implemented methods use File | Settings | File Templates.
List<String> roleNames = new ArrayList<String>();
for (String roleName : defaultRoles) {
RoleModel role = getRole(roleName);
if (role == null) {
addRole(roleName);
}
roleNames.add(roleName);
}
application.setDefaultRoles(roleNames);
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (!(o instanceof ApplicationAdapter)) return false;
ApplicationAdapter app = (ApplicationAdapter)o;
return app.getId().equals(getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -1,70 +0,0 @@
package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.impl.MongoDBImpl;
import org.keycloak.models.mongo.keycloak.data.ApplicationData;
import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
import org.keycloak.models.mongo.keycloak.data.RealmData;
import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
import org.keycloak.models.mongo.keycloak.data.RoleData;
import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
import java.net.UnknownHostException;
/**
* NoSQL implementation based on MongoDB
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoDBSessionFactory implements KeycloakSessionFactory {
protected static final Logger logger = Logger.getLogger(MongoDBSessionFactory.class);
private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
RealmData.class,
UserData.class,
RoleData.class,
RequiredCredentialData.class,
PasswordData.class,
OTPData.class,
SocialLinkData.class,
ApplicationData.class,
OAuthClientData.class
};
private final MongoClient mongoClient;
private final NoSQL mongoDB;
public MongoDBSessionFactory(String host, int port, String dbName, boolean dropDatabaseOnStartup) {
logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, dropDatabaseOnStartup));
try {
// TODO: authentication support
mongoClient = new MongoClient(host, port);
DB db = mongoClient.getDB(dbName);
mongoDB = new MongoDBImpl(db, dropDatabaseOnStartup, MANAGED_DATA_TYPES);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
@Override
public KeycloakSession createSession() {
return new NoSQLSession(mongoDB);
}
@Override
public void close() {
logger.info("Closing MongoDB client");
mongoClient.close();
}
}

View file

@ -0,0 +1,92 @@
package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoKeycloakSession implements KeycloakSession {
private static final MongoKeycloakTransaction PLACEHOLDER = new MongoKeycloakTransaction();
private final MongoStore mongoStore;
public MongoKeycloakSession(MongoStore mongoStore) {
this.mongoStore = mongoStore;
}
@Override
public KeycloakTransaction getTransaction() {
return PLACEHOLDER;
}
@Override
public void close() {
}
@Override
public RealmModel createRealm(String name) {
return createRealm(KeycloakModelUtils.generateId(), name);
}
@Override
public RealmModel createRealm(String id, String name) {
if (getRealm(id) != null) {
throw new IllegalStateException("Realm with id '" + id + "' already exists");
}
RealmEntity newRealm = new RealmEntity();
newRealm.setId(id);
newRealm.setName(name);
mongoStore.insertObject(newRealm);
RealmAdapter realm = new RealmAdapter(newRealm, mongoStore);
return realm;
}
@Override
public RealmModel getRealm(String id) {
RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, id);
return realmEntity != null ? new RealmAdapter(realmEntity, mongoStore) : null;
}
@Override
public List<RealmModel> getRealms(UserModel admin) {
DBObject query = new BasicDBObject();
List<RealmEntity> realms = mongoStore.loadObjects(RealmEntity.class, query);
List<RealmModel> results = new ArrayList<RealmModel>();
for (RealmEntity realmEntity : realms) {
results.add(new RealmAdapter(realmEntity, mongoStore));
}
return results;
}
@Override
public RealmModel getRealmByName(String name) {
DBObject query = new QueryBuilder()
.and("name").is(name)
.get();
RealmEntity realm = mongoStore.loadSingleObject(RealmEntity.class, query);
if (realm == null) return null;
return new RealmAdapter(realm, mongoStore);
}
@Override
public boolean removeRealm(String id) {
return mongoStore.removeObject(RealmEntity.class, id);
}
}

View file

@ -0,0 +1,83 @@
package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.DB;
import com.mongodb.MongoClient;
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.mongo.api.MongoStore;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
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.utils.EmbeddedMongo;
import org.keycloak.models.mongo.utils.MongoConfiguration;
import java.net.UnknownHostException;
/**
* KeycloakSessionFactory implementation based on MongoDB
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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,
RequiredCredentialEntity.class,
CredentialEntity.class,
SocialLinkEntity.class,
ApplicationEntity.class,
OAuthClientEntity.class
};
private final EmbeddedMongo embeddedMongo;
private final MongoClient mongoClient;
private final MongoStore mongoStore;
public MongoKeycloakSessionFactory(MongoConfiguration config) {
logger.info(String.format("Configuring MongoStore with: " + config));
if (config.isStartEmbedded()) {
embeddedMongo = new EmbeddedMongo();
embeddedMongo.startEmbeddedMongo(config.getPort());
} else {
embeddedMongo = null;
}
try {
// TODO: authentication support
mongoClient = new MongoClient(config.getHost(), config.getPort());
DB db = mongoClient.getDB(config.getDbName());
mongoStore = new MongoStoreImpl(db, config.isClearCollectionsOnStartup(), MANAGED_ENTITY_TYPES);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
@Override
public KeycloakSession createSession() {
return new MongoKeycloakSession(mongoStore);
}
@Override
public void close() {
logger.info("Closing MongoDB client");
mongoClient.close();
if (embeddedMongo != null) {
embeddedMongo.stopEmbeddedMongo();
}
}
}

View file

@ -5,7 +5,7 @@ import org.keycloak.models.KeycloakTransaction;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class NoSQLTransaction implements KeycloakTransaction {
public class MongoKeycloakTransaction implements KeycloakTransaction {
@Override
public void begin() {

View file

@ -1,86 +0,0 @@
package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.keycloak.data.RealmData;
import org.keycloak.models.utils.KeycloakSessionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class NoSQLSession implements KeycloakSession {
private static final NoSQLTransaction PLACEHOLDER = new NoSQLTransaction();
private final NoSQL noSQL;
public NoSQLSession(NoSQL noSQL) {
this.noSQL = noSQL;
}
@Override
public KeycloakTransaction getTransaction() {
return PLACEHOLDER;
}
@Override
public void close() {
}
@Override
public RealmModel createRealm(String name) {
return createRealm(KeycloakSessionUtils.generateId(), name);
}
@Override
public RealmModel createRealm(String id, String name) {
if (getRealm(id) != null) {
throw new IllegalStateException("Realm with id '" + id + "' already exists");
}
RealmData newRealm = new RealmData();
newRealm.setId(id);
newRealm.setName(name);
noSQL.saveObject(newRealm);
RealmAdapter realm = new RealmAdapter(newRealm, noSQL);
return realm;
}
@Override
public RealmModel getRealm(String id) {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("id", id)
.build();
RealmData realmData = noSQL.loadSingleObject(RealmData.class, query);
return realmData != null ? new RealmAdapter(realmData, noSQL) : null;
}
@Override
public List<RealmModel> getRealms(UserModel admin) {
String userId = ((UserAdapter)admin).getUser().getId();
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("realmAdmins", userId)
.build();
List<RealmData> realms = noSQL.loadObjects(RealmData.class, query);
List<RealmModel> results = new ArrayList<RealmModel>();
for (RealmData realmData : realms) {
results.add(new RealmAdapter(realmData, noSQL));
}
return results;
}
@Override
public void deleteRealm(RealmModel realm) {
String oid = ((RealmAdapter)realm).getOid();
noSQL.removeObject(RealmData.class, oid);
}
}

View file

@ -2,28 +2,27 @@ package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
import org.keycloak.models.mongo.keycloak.entities.UserEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OAuthClientAdapter implements OAuthClientModel {
private final OAuthClientData delegate;
private final OAuthClientEntity delegate;
private UserAdapter oauthAgent;
private final NoSQL noSQL;
private final MongoStore mongoStore;
public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) {
this.delegate = oauthClientData;
public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStore mongoStore) {
this.delegate = oauthClientEntity;
this.oauthAgent = oauthAgent;
this.noSQL = noSQL;
this.mongoStore = mongoStore;
}
public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) {
this.delegate = oauthClientData;
this.noSQL = noSQL;
public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStore mongoStore) {
this(oauthClientEntity, null, mongoStore);
}
@Override
@ -35,8 +34,8 @@ public class OAuthClientAdapter implements OAuthClientModel {
public UserModel getOAuthAgent() {
// This is not thread-safe. Assumption is that OAuthClientAdapter instance is per-client object
if (oauthAgent == null) {
UserData user = noSQL.loadObject(UserData.class, delegate.getOauthAgentId());
oauthAgent = user!=null ? new UserAdapter(user, noSQL) : null;
UserEntity user = mongoStore.loadObject(UserEntity.class, delegate.getOauthAgentId());
oauthAgent = user!=null ? new UserAdapter(user, mongoStore) : null;
}
return oauthAgent;
}

View file

@ -1,8 +1,20 @@
package org.keycloak.models.mongo.keycloak.adapters;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.keycloak.data.RoleData;
import org.keycloak.models.mongo.api.MongoStore;
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.utils.MongoModelUtils;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl)
@ -11,12 +23,23 @@ import org.keycloak.models.mongo.keycloak.data.RoleData;
*/
public class RoleAdapter implements RoleModel {
private final RoleData role;
private final NoSQL noSQL;
private final RoleEntity role;
private RoleContainerModel roleContainer;
private final MongoStore mongoStore;
public RoleAdapter(RoleData roleData, NoSQL noSQL) {
this.role = roleData;
this.noSQL = noSQL;
public RoleAdapter(RoleEntity roleEntity, MongoStore mongoStore) {
this(roleEntity, null, mongoStore);
}
public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStore mongoStore) {
this.role = roleEntity;
this.roleContainer = roleContainer;
this.mongoStore = mongoStore;
}
@Override
public String getId() {
return role.getId();
}
@Override
@ -24,6 +47,12 @@ public class RoleAdapter implements RoleModel {
return role.getName();
}
@Override
public void setName(String name) {
role.setName(name);
updateRole();
}
@Override
public String getDescription() {
return role.getDescription();
@ -32,21 +61,102 @@ public class RoleAdapter implements RoleModel {
@Override
public void setDescription(String description) {
role.setDescription(description);
noSQL.saveObject(role);
updateRole();
}
@Override
public String getId() {
return role.getId();
public boolean isComposite() {
return role.isComposite();
}
@Override
public void setName(String name) {
role.setName(name);
noSQL.saveObject(role);
public void setComposite(boolean flag) {
role.setComposite(flag);
updateRole();
}
public RoleData getRole() {
protected void updateRole() {
mongoStore.updateObject(role);
}
@Override
public void addCompositeRole(RoleModel childRole) {
mongoStore.pushItemToList(role, "compositeRoleIds", childRole.getId(), true);
}
@Override
public void removeCompositeRole(RoleModel childRole) {
mongoStore.pullItemFromList(role, "compositeRoleIds", childRole.getId());
}
@Override
public Set<RoleModel> getComposites() {
if (role.getCompositeRoleIds() == null || role.getCompositeRoleIds().isEmpty()) {
return Collections.EMPTY_SET;
}
DBObject query = new QueryBuilder()
.and("_id").in(MongoModelUtils.convertStringsToObjectIds(role.getCompositeRoleIds()))
.get();
List<RoleEntity> childRoles = mongoStore.loadObjects(RoleEntity.class, query);
Set<RoleModel> set = new HashSet<RoleModel>();
for (RoleEntity childRole : childRoles) {
set.add(new RoleAdapter(childRole, roleContainer, mongoStore));
}
return set;
}
@Override
public RoleContainerModel getContainer() {
if (roleContainer == null) {
// Compute it
if (role.getRealmId() != null) {
RealmEntity realm = mongoStore.loadObject(RealmEntity.class, role.getRealmId());
if (realm == null) {
throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists");
}
roleContainer = new RealmAdapter(realm, mongoStore);
} else if (role.getApplicationId() != null) {
ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, role.getApplicationId());
if (appEntity == null) {
throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists");
}
roleContainer = new ApplicationAdapter(appEntity, mongoStore);
} else {
throw new IllegalStateException("Both realmId and applicationId are null for role: " + this);
}
}
return roleContainer;
}
@Override
public boolean hasRole(RoleModel role) {
if (this.equals(role)) return true;
if (!isComposite()) return false;
Set<RoleModel> visited = new HashSet<RoleModel>();
return KeycloakModelUtils.searchFor(role, this, visited);
}
public RoleEntity getRole() {
return role;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleAdapter that = (RoleAdapter) o;
if (!getId().equals(that.getId())) return false;
return true;
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -1,10 +1,13 @@
package org.keycloak.models.mongo.keycloak.adapters;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
import org.keycloak.models.mongo.keycloak.entities.UserEntity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -17,12 +20,12 @@ import java.util.Set;
*/
public class UserAdapter implements UserModel {
private final UserData user;
private final NoSQL noSQL;
private final UserEntity user;
private final MongoStore mongoStore;
public UserAdapter(UserData userData, NoSQL noSQL) {
this.user = userData;
this.noSQL = noSQL;
public UserAdapter(UserEntity userEntity, MongoStore mongoStore) {
this.user = userEntity;
this.mongoStore = mongoStore;
}
@Override
@ -38,7 +41,7 @@ public class UserAdapter implements UserModel {
@Override
public void setEnabled(boolean enabled) {
user.setEnabled(enabled);
noSQL.saveObject(user);
updateUser();
}
@Override
@ -49,7 +52,7 @@ public class UserAdapter implements UserModel {
@Override
public void setFirstName(String firstName) {
user.setFirstName(firstName);
noSQL.saveObject(user);
updateUser();
}
@Override
@ -60,7 +63,7 @@ public class UserAdapter implements UserModel {
@Override
public void setLastName(String lastName) {
user.setLastName(lastName);
noSQL.saveObject(user);
updateUser();
}
@Override
@ -71,7 +74,7 @@ public class UserAdapter implements UserModel {
@Override
public void setEmail(String email) {
user.setEmail(email);
noSQL.saveObject(user);
updateUser();
}
@Override
@ -82,61 +85,112 @@ public class UserAdapter implements UserModel {
@Override
public void setEmailVerified(boolean verified) {
user.setEmailVerified(verified);
noSQL.saveObject(user);
updateUser();
}
@Override
public void setAttribute(String name, String value) {
user.setAttribute(name, value);
if (user.getAttributes() == null) {
user.setAttributes(new HashMap<String, String>());
}
user.getAttributes().put(name, value);
updateUser();
}
@Override
public void removeAttribute(String name) {
user.removeAttribute(name);
noSQL.saveObject(user);
if (user.getAttributes() == null) return;
user.getAttributes().remove(name);
updateUser();
}
@Override
public String getAttribute(String name) {
return user.getAttribute(name);
return user.getAttributes()==null ? null : user.getAttributes().get(name);
}
@Override
public Map<String, String> getAttributes() {
return user.getAttributes();
return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes());
}
public UserData getUser() {
public UserEntity getUser() {
return user;
}
@Override
public Set<RequiredAction> getRequiredActions() {
List<RequiredAction> actions = user.getRequiredActions();
public Set<String> getWebOrigins() {
Set<String> result = new HashSet<String>();
if (user.getWebOrigins() != null) {
result.addAll(user.getWebOrigins());
}
return result;
}
// Compatibility with picketlink impl
if (actions == null) {
return Collections.emptySet();
} else {
Set<RequiredAction> s = new HashSet<RequiredAction>();
for (RequiredAction a : actions) {
s.add(a);
@Override
public void setWebOrigins(Set<String> webOrigins) {
List<String> result = new ArrayList<String>();
result.addAll(webOrigins);
user.setWebOrigins(result);
updateUser();
}
return Collections.unmodifiableSet(s);
@Override
public void addWebOrigin(String webOrigin) {
mongoStore.pushItemToList(user, "webOrigins", webOrigin, true);
}
@Override
public void removeWebOrigin(String webOrigin) {
mongoStore.pullItemFromList(user, "webOrigins", webOrigin);
}
@Override
public Set<String> getRedirectUris() {
Set<String> result = new HashSet<String>();
if (user.getRedirectUris() != null) {
result.addAll(user.getRedirectUris());
}
return result;
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
List<String> result = new ArrayList<String>();
result.addAll(redirectUris);
user.setRedirectUris(result);
updateUser();
}
@Override
public void addRedirectUri(String redirectUri) {
mongoStore.pushItemToList(user, "redirectUris", redirectUri, true);
}
@Override
public void removeRedirectUri(String redirectUri) {
mongoStore.pullItemFromList(user, "redirectUris", redirectUri);
}
@Override
public Set<RequiredAction> getRequiredActions() {
Set<RequiredAction> result = new HashSet<RequiredAction>();
if (user.getRequiredActions() != null) {
result.addAll(user.getRequiredActions());
}
return result;
}
@Override
public void addRequiredAction(RequiredAction action) {
// Push action only if it's not already here
if (user.getRequiredActions() == null || !user.getRequiredActions().contains(action)) {
noSQL.pushItemToList(user, "requiredActions", action);
}
mongoStore.pushItemToList(user, "requiredActions", action, true);
}
@Override
public void removeRequiredAction(RequiredAction action) {
noSQL.pullItemFromList(user, "requiredActions", action);
mongoStore.pullItemFromList(user, "requiredActions", action);
}
@Override
@ -147,46 +201,10 @@ public class UserAdapter implements UserModel {
@Override
public void setTotp(boolean totp) {
user.setTotp(totp);
noSQL.saveObject(user);
updateUser();
}
@Override
public Set<String> getWebOrigins() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void setWebOrigins(Set<String> webOrigins) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void addWebOrigin(String webOrigin) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void removeWebOrigin(String webOrigin) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public Set<String> getRedirectUris() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void addRedirectUri(String redirectUri) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void removeRedirectUri(String redirectUri) {
//To change body of implemented methods use File | Settings | File Templates.
protected void updateUser() {
mongoStore.updateObject(user);
}
}

View file

@ -1,154 +0,0 @@
package org.keycloak.models.mongo.keycloak.credentials;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
import org.picketlink.idm.credential.Credentials;
import org.picketlink.idm.credential.encoder.PasswordEncoder;
import org.picketlink.idm.credential.encoder.SHAPasswordEncoder;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
/**
* Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler}
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PasswordCredentialHandler {
private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG";
/**
* <p>
* Stores a <b>stateless</b> instance of {@link org.picketlink.idm.credential.encoder.PasswordEncoder} that should be used to encode passwords.
* </p>
*/
public static final String PASSWORD_ENCODER = "PASSWORD_ENCODER";
private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);;
public PasswordCredentialHandler(Map<String, Object> options) {
setup(options);
}
private void setup(Map<String, Object> options) {
if (options != null) {
Object providedEncoder = options.get(PASSWORD_ENCODER);
if (providedEncoder != null) {
if (PasswordEncoder.class.isInstance(providedEncoder)) {
this.passwordEncoder = (PasswordEncoder) providedEncoder;
} else {
throw new IllegalArgumentException("The password encoder [" + providedEncoder
+ "] must be an instance of " + PasswordEncoder.class.getName());
}
}
}
}
public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate) {
Credentials.Status status = Credentials.Status.INVALID;
user = noSQL.loadObject(UserData.class, user.getId());
// If the user for the provided username cannot be found we fail validation
if (user != null) {
if (user.isEnabled()) {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("userId", user.getId())
.build();
PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query);
// If the stored hash is null we automatically fail validation
if (passwordData != null) {
// TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
if (!isCredentialExpired(passwordData.getExpiryDate())) {
boolean matches = this.passwordEncoder.verify(saltPassword(passwordToValidate, passwordData.getSalt()), passwordData.getEncodedHash());
if (matches) {
status = Credentials.Status.VALID;
}
} else {
status = Credentials.Status.EXPIRED;
}
}
} else {
status = Credentials.Status.ACCOUNT_DISABLED;
}
}
return status;
}
public void update(NoSQL noSQL, UserData user, String password,
Date effectiveDate, Date expiryDate) {
// Delete existing password of user
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("userId", user.getId())
.build();
noSQL.removeObjects(PasswordData.class, query);
PasswordData passwordData = new PasswordData();
String passwordSalt = generateSalt();
passwordData.setSalt(passwordSalt);
passwordData.setEncodedHash(this.passwordEncoder.encode(saltPassword(password, passwordSalt)));
if (effectiveDate != null) {
passwordData.setEffectiveDate(effectiveDate);
}
passwordData.setExpiryDate(expiryDate);
passwordData.setUserId(user.getId());
noSQL.saveObject(passwordData);
}
/**
* <p>
* Salt the give <code>rawPassword</code> with the specified <code>salt</code> value.
* </p>
*
* @param rawPassword
* @param salt
* @return
*/
private String saltPassword(String rawPassword, String salt) {
return salt + rawPassword;
}
/**
* <p>
* Generates a random string to be used as a salt for passwords.
* </p>
*
* @return
*/
private String generateSalt() {
// TODO: always returns same salt (See https://issues.jboss.org/browse/PLINK-258)
/*SecureRandom pseudoRandom = null;
try {
pseudoRandom = SecureRandom.getInstance(DEFAULT_SALT_ALGORITHM);
pseudoRandom.setSeed(1024);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error getting SecureRandom instance: " + DEFAULT_SALT_ALGORITHM, e);
}
return String.valueOf(pseudoRandom.nextLong());*/
return UUID.randomUUID().toString();
}
public static boolean isCredentialExpired(Date expiryDate) {
return expiryDate != null && new Date().compareTo(expiryDate) > 0;
}
}

View file

@ -1,135 +0,0 @@
package org.keycloak.models.mongo.keycloak.credentials;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.keycloak.data.UserData;
import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
import org.picketlink.idm.credential.Credentials;
import org.picketlink.idm.credential.util.TimeBasedOTP;
import java.util.Date;
import java.util.Map;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
import static org.picketlink.idm.credential.util.TimeBasedOTP.*;
/**
* Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler}
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class TOTPCredentialHandler extends PasswordCredentialHandler {
public static final String ALGORITHM = "ALGORITHM";
public static final String INTERVAL_SECONDS = "INTERVAL_SECONDS";
public static final String NUMBER_DIGITS = "NUMBER_DIGITS";
public static final String DELAY_WINDOW = "DELAY_WINDOW";
public static final String DEFAULT_DEVICE = "DEFAULT_DEVICE";
private TimeBasedOTP totp;
public TOTPCredentialHandler(Map<String, Object> options) {
super(options);
setup(options);
}
private void setup(Map<String, Object> options) {
String algorithm = getConfigurationProperty(options, ALGORITHM, DEFAULT_ALGORITHM);
String intervalSeconds = getConfigurationProperty(options, INTERVAL_SECONDS, "" + DEFAULT_INTERVAL_SECONDS);
String numberDigits = getConfigurationProperty(options, NUMBER_DIGITS, "" + DEFAULT_NUMBER_DIGITS);
String delayWindow = getConfigurationProperty(options, DELAY_WINDOW, "" + DEFAULT_DELAY_WINDOW);
this.totp = new TimeBasedOTP(algorithm, Integer.parseInt(numberDigits), Integer.valueOf(intervalSeconds), Integer.valueOf(delayWindow));
}
public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate, String token, String device) {
Credentials.Status status = super.validate(noSQL, user, passwordToValidate);
if (Credentials.Status.VALID != status) {
return status;
}
device = getDevice(device);
user = noSQL.loadObject(UserData.class, user.getId());
// If the user for the provided username cannot be found we fail validation
if (user != null) {
if (user.isEnabled()) {
// Try to find OTP based on userId and device (For now assume that this is unique combination)
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("userId", user.getId())
.andCondition("device", device)
.build();
OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
// If the stored OTP is null we automatically fail validation
if (otpData != null) {
// TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
if (!PasswordCredentialHandler.isCredentialExpired(otpData.getExpiryDate())) {
boolean isValid = this.totp.validate(token, otpData.getSecretKey().getBytes());
if (!isValid) {
status = Credentials.Status.INVALID;
}
} else {
status = Credentials.Status.EXPIRED;
}
} else {
status = Credentials.Status.UNVALIDATED;
}
} else {
status = Credentials.Status.ACCOUNT_DISABLED;
}
} else {
status = Credentials.Status.INVALID;
}
return status;
}
public void update(NoSQL noSQL, UserData user, String secret, String device, Date effectiveDate, Date expiryDate) {
device = getDevice(device);
// Try to look if user already has otp (Right now, supports just one OTP per user)
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("userId", user.getId())
.andCondition("device", device)
.build();
OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
if (otpData == null) {
otpData = new OTPData();
}
otpData.setSecretKey(secret);
otpData.setDevice(device);
if (effectiveDate != null) {
otpData.setEffectiveDate(effectiveDate);
}
otpData.setExpiryDate(expiryDate);
otpData.setUserId(user.getId());
noSQL.saveObject(otpData);
}
private String getDevice(String device) {
if (isNullOrEmpty(device)) {
device = DEFAULT_DEVICE;
}
return device;
}
private String getConfigurationProperty(Map<String, Object> options, String key, String defaultValue) {
Object value = options.get(key);
if (value != null) {
return String.valueOf(value);
}
return defaultValue;
}
}

View file

@ -1,62 +0,0 @@
package org.keycloak.models.mongo.keycloak.data;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.NoSQLObject;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "oauthClients")
public class OAuthClientData implements NoSQLObject {
private String id;
private String baseUrl;
private String oauthAgentId;
private String realmId;
@NoSQLId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@NoSQLField
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@NoSQLField
public String getOauthAgentId() {
return oauthAgentId;
}
public void setOauthAgentId(String oauthUserId) {
this.oauthAgentId = oauthUserId;
}
@NoSQLField
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
@Override
public void afterRemove(NoSQL noSQL) {
// Remove user of this oauthClient
noSQL.removeObject(UserData.class, oauthAgentId);
}
}

View file

@ -1,219 +0,0 @@
package org.keycloak.models.mongo.keycloak.data;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "realms")
public class RealmData implements NoSQLObject {
private String oid;
private String id;
private String name;
private boolean enabled;
private boolean sslNotRequired;
private boolean cookieLoginAllowed;
private boolean registrationAllowed;
private boolean verifyEmail;
private boolean resetPasswordAllowed;
private boolean social;
private boolean automaticRegistrationAfterSocialLogin;
private int tokenLifespan;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private String publicKeyPem;
private String privateKeyPem;
private List<String> defaultRoles;
private List<String> realmAdmins;
@NoSQLId
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
@NoSQLField
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@NoSQLField
public String getName() {
return name;
}
public void setName(String realmName) {
this.name = realmName;
}
@NoSQLField
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@NoSQLField
public boolean isSslNotRequired() {
return sslNotRequired;
}
public void setSslNotRequired(boolean sslNotRequired) {
this.sslNotRequired = sslNotRequired;
}
@NoSQLField
public boolean isCookieLoginAllowed() {
return cookieLoginAllowed;
}
public void setCookieLoginAllowed(boolean cookieLoginAllowed) {
this.cookieLoginAllowed = cookieLoginAllowed;
}
@NoSQLField
public boolean isRegistrationAllowed() {
return registrationAllowed;
}
public void setRegistrationAllowed(boolean registrationAllowed) {
this.registrationAllowed = registrationAllowed;
}
@NoSQLField
public boolean isVerifyEmail() {
return verifyEmail;
}
public void setVerifyEmail(boolean verifyEmail) {
this.verifyEmail = verifyEmail;
}
@NoSQLField
public boolean isResetPasswordAllowed() {
return resetPasswordAllowed;
}
public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
this.resetPasswordAllowed = resetPasswordAllowed;
}
@NoSQLField
public boolean isSocial() {
return social;
}
public void setSocial(boolean social) {
this.social = social;
}
@NoSQLField
public boolean isAutomaticRegistrationAfterSocialLogin() {
return automaticRegistrationAfterSocialLogin;
}
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
}
@NoSQLField
public int getTokenLifespan() {
return tokenLifespan;
}
public void setTokenLifespan(int tokenLifespan) {
this.tokenLifespan = tokenLifespan;
}
@NoSQLField
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
public void setAccessCodeLifespan(int accessCodeLifespan) {
this.accessCodeLifespan = accessCodeLifespan;
}
@NoSQLField
public int getAccessCodeLifespanUserAction() {
return accessCodeLifespanUserAction;
}
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
}
@NoSQLField
public String getPublicKeyPem() {
return publicKeyPem;
}
public void setPublicKeyPem(String publicKeyPem) {
this.publicKeyPem = publicKeyPem;
}
@NoSQLField
public String getPrivateKeyPem() {
return privateKeyPem;
}
public void setPrivateKeyPem(String privateKeyPem) {
this.privateKeyPem = privateKeyPem;
}
@NoSQLField
public List<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
@NoSQLField
public List<String> getRealmAdmins() {
return realmAdmins;
}
public void setRealmAdmins(List<String> realmAdmins) {
this.realmAdmins = realmAdmins;
}
@Override
public void afterRemove(NoSQL noSQL) {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("realmId", oid)
.build();
// Remove all users of this realm
noSQL.removeObjects(UserData.class, query);
// Remove all requiredCredentials of this realm
noSQL.removeObjects(RequiredCredentialData.class, query);
// Remove all roles of this realm
noSQL.removeObjects(RoleData.class, query);
// Remove all applications of this realm
noSQL.removeObjects(ApplicationData.class, query);
}
}

View file

@ -1,90 +0,0 @@
package org.keycloak.models.mongo.keycloak.data;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "requiredCredentials")
public class RequiredCredentialData extends AbstractNoSQLObject {
public static final int CLIENT_TYPE_USER = 1;
public static final int CLIENT_TYPE_RESOURCE = 2;
public static final int CLIENT_TYPE_OAUTH_RESOURCE = 3;
private String id;
private String type;
private boolean input;
private boolean secret;
private String formLabel;
private String realmId;
private int clientType;
@NoSQLId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@NoSQLField
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@NoSQLField
public boolean isInput() {
return input;
}
public void setInput(boolean input) {
this.input = input;
}
@NoSQLField
public boolean isSecret() {
return secret;
}
public void setSecret(boolean secret) {
this.secret = secret;
}
@NoSQLField
public String getFormLabel() {
return formLabel;
}
public void setFormLabel(String formLabel) {
this.formLabel = formLabel;
}
@NoSQLField
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
@NoSQLField
public int getClientType() {
return clientType;
}
public void setClientType(int clientType) {
this.clientType = clientType;
}
}

View file

@ -1,98 +0,0 @@
package org.keycloak.models.mongo.keycloak.data;
import org.jboss.logging.Logger;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "roles")
public class RoleData implements NoSQLObject {
private static final Logger logger = Logger.getLogger(RoleData.class);
private String id;
private String name;
private String description;
private String realmId;
private String applicationId;
@NoSQLId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@NoSQLField
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@NoSQLField
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@NoSQLField
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
@NoSQLField
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
@Override
public void afterRemove(NoSQL noSQL) {
// Remove this role from all users, which has it
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("roleIds", id)
.build();
List<UserData> users = noSQL.loadObjects(UserData.class, query);
for (UserData user : users) {
logger.info("Removing role " + getName() + " from user " + user.getLoginName());
noSQL.pullItemFromList(user, "roleIds", getId());
}
// Remove this scope from all users, which has it
query = noSQL.createQueryBuilder()
.andCondition("scopeIds", id)
.build();
users = noSQL.loadObjects(UserData.class, query);
for (UserData user : users) {
logger.info("Removing scope " + getName() + " from user " + user.getLoginName());
noSQL.pullItemFromList(user, "scopeIds", getId());
}
}
}

View file

@ -1,66 +0,0 @@
package org.keycloak.models.mongo.keycloak.data.credentials;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import java.util.Date;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "otpCredentials")
public class OTPData extends AbstractNoSQLObject {
private Date effectiveDate = new Date();
private Date expiryDate;
private String secretKey;
private String device;
private String userId;
@NoSQLField
public Date getEffectiveDate() {
return effectiveDate;
}
public void setEffectiveDate(Date effectiveDate) {
this.effectiveDate = effectiveDate;
}
@NoSQLField
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Date expiryDate) {
this.expiryDate = expiryDate;
}
@NoSQLField
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@NoSQLField
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
@NoSQLField
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}

View file

@ -1,66 +0,0 @@
package org.keycloak.models.mongo.keycloak.data.credentials;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import java.util.Date;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "passwordCredentials")
public class PasswordData extends AbstractNoSQLObject {
private Date effectiveDate = new Date();
private Date expiryDate;
private String encodedHash;
private String salt;
private String userId;
@NoSQLField
public Date getEffectiveDate() {
return effectiveDate;
}
public void setEffectiveDate(Date effectiveDate) {
this.effectiveDate = effectiveDate;
}
@NoSQLField
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Date expiryDate) {
this.expiryDate = expiryDate;
}
@NoSQLField
public String getEncodedHash() {
return encodedHash;
}
public void setEncodedHash(String encodedHash) {
this.encodedHash = encodedHash;
}
@NoSQLField
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
@NoSQLField
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}

View file

@ -1,17 +1,21 @@
package org.keycloak.models.mongo.keycloak.data;
package org.keycloak.models.mongo.keycloak.entities;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
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.MongoEntity;
import org.keycloak.models.mongo.api.MongoField;
import org.keycloak.models.mongo.api.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "applications")
public class ApplicationData implements NoSQLObject {
@MongoCollection(collectionName = "applications")
public class ApplicationEntity implements MongoEntity {
private String id;
private String name;
@ -23,7 +27,10 @@ public class ApplicationData implements NoSQLObject {
private String resourceUserId;
private String realmId;
@NoSQLId
// We are using names of defaultRoles (not ids)
private List<String> defaultRoles = new ArrayList<String>();
@MongoId
public String getId() {
return id;
}
@ -32,7 +39,7 @@ public class ApplicationData implements NoSQLObject {
this.id = id;
}
@NoSQLField
@MongoField
public String getName() {
return name;
}
@ -41,7 +48,7 @@ public class ApplicationData implements NoSQLObject {
this.name = name;
}
@NoSQLField
@MongoField
public boolean isEnabled() {
return enabled;
}
@ -50,7 +57,7 @@ public class ApplicationData implements NoSQLObject {
this.enabled = enabled;
}
@NoSQLField
@MongoField
public boolean isSurrogateAuthRequired() {
return surrogateAuthRequired;
}
@ -59,7 +66,7 @@ public class ApplicationData implements NoSQLObject {
this.surrogateAuthRequired = surrogateAuthRequired;
}
@NoSQLField
@MongoField
public String getManagementUrl() {
return managementUrl;
}
@ -68,7 +75,7 @@ public class ApplicationData implements NoSQLObject {
this.managementUrl = managementUrl;
}
@NoSQLField
@MongoField
public String getBaseUrl() {
return baseUrl;
}
@ -77,7 +84,7 @@ public class ApplicationData implements NoSQLObject {
this.baseUrl = baseUrl;
}
@NoSQLField
@MongoField
public String getResourceUserId() {
return resourceUserId;
}
@ -86,7 +93,7 @@ public class ApplicationData implements NoSQLObject {
this.resourceUserId = resourceUserId;
}
@NoSQLField
@MongoField
public String getRealmId() {
return realmId;
}
@ -95,15 +102,24 @@ public class ApplicationData implements NoSQLObject {
this.realmId = realmId;
}
@MongoField
public List<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}
@Override
public void afterRemove(NoSQL noSQL) {
public void afterRemove(MongoStore mongoStore) {
// Remove resourceUser of this application
noSQL.removeObject(UserData.class, resourceUserId);
mongoStore.removeObject(UserEntity.class, resourceUserId);
// Remove all roles, which belongs to this application
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("applicationId", id)
.build();
noSQL.removeObjects(RoleData.class, query);
DBObject query = new QueryBuilder()
.and("applicationId").is(id)
.get();
mongoStore.removeObjects(RoleEntity.class, query);
}
}

View file

@ -0,0 +1,51 @@
package org.keycloak.models.mongo.keycloak.entities;
import org.keycloak.models.mongo.api.AbstractMongoEntity;
import org.keycloak.models.mongo.api.MongoField;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class CredentialEntity extends AbstractMongoEntity {
private String type;
private String value;
private String device;
private byte[] salt;
@MongoField
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@MongoField
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@MongoField
public String getDevice() {
return device;
}
public void setDevice(String device) {
this.device = device;
}
@MongoField
public byte[] getSalt() {
return salt;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.models.mongo.keycloak.entities;
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.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "oauthClients")
public class OAuthClientEntity implements MongoEntity {
private String id;
private String name;
private String oauthAgentId;
private String realmId;
@MongoId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@MongoField
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@MongoField
public String getOauthAgentId() {
return oauthAgentId;
}
public void setOauthAgentId(String oauthUserId) {
this.oauthAgentId = oauthUserId;
}
@MongoField
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
@Override
public void afterRemove(MongoStore mongoStore) {
// Remove user of this oauthClient
mongoStore.removeObject(UserEntity.class, oauthAgentId);
}
}

View file

@ -0,0 +1,255 @@
package org.keycloak.models.mongo.keycloak.entities;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
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.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
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>
*/
@MongoCollection(collectionName = "realms")
public class RealmEntity implements MongoEntity {
private String id;
private String name;
private boolean enabled;
private boolean sslNotRequired;
private boolean registrationAllowed;
private boolean verifyEmail;
private boolean resetPasswordAllowed;
private boolean social;
private boolean updateProfileOnInitialSocialLogin;
private String passwordPolicy;
private int tokenLifespan;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private String publicKeyPem;
private String privateKeyPem;
// We are using names of defaultRoles (not ids)
private List<String> defaultRoles = new ArrayList<String>();
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
private List<RequiredCredentialEntity> requiredApplicationCredentials = new ArrayList<RequiredCredentialEntity>();
private List<RequiredCredentialEntity> requiredOAuthClientCredentials = new ArrayList<RequiredCredentialEntity>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
@MongoId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@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 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 String getPasswordPolicy() {
return passwordPolicy;
}
public void setPasswordPolicy(String passwordPolicy) {
this.passwordPolicy = passwordPolicy;
}
@MongoField
public int getTokenLifespan() {
return tokenLifespan;
}
public void setTokenLifespan(int tokenLifespan) {
this.tokenLifespan = tokenLifespan;
}
@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 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<RequiredCredentialEntity> getRequiredApplicationCredentials() {
return requiredApplicationCredentials;
}
public void setRequiredApplicationCredentials(List<RequiredCredentialEntity> requiredApplicationCredentials) {
this.requiredApplicationCredentials = requiredApplicationCredentials;
}
@MongoField
public List<RequiredCredentialEntity> getRequiredOAuthClientCredentials() {
return requiredOAuthClientCredentials;
}
public void setRequiredOAuthClientCredentials(List<RequiredCredentialEntity> requiredOAuthClientCredentials) {
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
}
@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;
}
@Override
public void afterRemove(MongoStore mongoStore) {
DBObject query = new QueryBuilder()
.and("realmId").is(id)
.get();
// Remove all users of this realm
mongoStore.removeObjects(UserEntity.class, query);
// Remove all roles of this realm
mongoStore.removeObjects(RoleEntity.class, query);
// Remove all applications of this realm
mongoStore.removeObjects(ApplicationEntity.class, query);
}
}

View file

@ -0,0 +1,51 @@
package org.keycloak.models.mongo.keycloak.entities;
import org.keycloak.models.mongo.api.AbstractMongoEntity;
import org.keycloak.models.mongo.api.MongoField;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RequiredCredentialEntity extends AbstractMongoEntity {
private String type;
private boolean input;
private boolean secret;
private String formLabel;
@MongoField
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@MongoField
public boolean isInput() {
return input;
}
public void setInput(boolean input) {
this.input = input;
}
@MongoField
public boolean isSecret() {
return secret;
}
public void setSecret(boolean secret) {
this.secret = secret;
}
@MongoField
public String getFormLabel() {
return formLabel;
}
public void setFormLabel(String formLabel) {
this.formLabel = formLabel;
}
}

View file

@ -0,0 +1,148 @@
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.MongoCollection;
import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.MongoField;
import org.keycloak.models.mongo.api.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@MongoCollection(collectionName = "roles")
public class RoleEntity implements MongoEntity {
private static final Logger logger = Logger.getLogger(RoleEntity.class);
private String id;
private String name;
private String description;
private boolean composite;
private List<String> compositeRoleIds;
private String realmId;
private String applicationId;
@MongoId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@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 boolean isComposite() {
return composite;
}
public void setComposite(boolean composite) {
this.composite = composite;
}
@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(MongoStore mongoStore) {
// Remove this role from all users, which has it
DBObject query = new QueryBuilder()
.and("roleIds").is(id)
.get();
List<UserEntity> users = mongoStore.loadObjects(UserEntity.class, query);
for (UserEntity user : users) {
logger.info("Removing role " + getName() + " from user " + user.getLoginName());
mongoStore.pullItemFromList(user, "roleIds", getId());
}
// Remove this scope from all users, which has it
query = new QueryBuilder()
.and("scopeIds").is(id)
.get();
users = mongoStore.loadObjects(UserEntity.class, query);
for (UserEntity user : users) {
logger.info("Removing scope " + getName() + " from user " + user.getLoginName());
mongoStore.pullItemFromList(user, "scopeIds", getId());
}
// Remove defaultRoles from realm
if (realmId != null) {
RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, realmId);
// Realm might be already removed at this point
if (realmEntity != null) {
mongoStore.pullItemFromList(realmEntity, "defaultRoles", getId());
}
}
// Remove defaultRoles from application
if (applicationId != null) {
ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, applicationId);
// Application might be already removed at this point
if (appEntity != null) {
mongoStore.pullItemFromList(appEntity, "defaultRoles", getId());
}
}
// Remove this role from others who has it as composite
query = new QueryBuilder()
.and("compositeRoleIds").is(getId())
.get();
List<RoleEntity> parentRoles = mongoStore.loadObjects(RoleEntity.class, query);
for (RoleEntity role : parentRoles) {
mongoStore.pullItemFromList(role, "compositeRoleIds", getId());
}
}
}

View file

@ -1,14 +1,14 @@
package org.keycloak.models.mongo.keycloak.data;
package org.keycloak.models.mongo.keycloak.entities;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.AbstractMongoEntity;
import org.keycloak.models.mongo.api.MongoCollection;
import org.keycloak.models.mongo.api.MongoField;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "socialLinks")
public class SocialLinkData extends AbstractNoSQLObject {
@MongoCollection(collectionName = "socialLinks")
public class SocialLinkEntity extends AbstractMongoEntity {
private String socialUsername;
private String socialProvider;
@ -17,7 +17,7 @@ public class SocialLinkData extends AbstractNoSQLObject {
// (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
private String realmId;
@NoSQLField
@MongoField
public String getSocialUsername() {
return socialUsername;
}
@ -26,7 +26,7 @@ public class SocialLinkData extends AbstractNoSQLObject {
this.socialUsername = socialUsername;
}
@NoSQLField
@MongoField
public String getSocialProvider() {
return socialProvider;
}
@ -35,7 +35,7 @@ public class SocialLinkData extends AbstractNoSQLObject {
this.socialProvider = socialProvider;
}
@NoSQLField
@MongoField
public String getUserId() {
return userId;
}
@ -44,7 +44,7 @@ public class SocialLinkData extends AbstractNoSQLObject {
this.userId = userId;
}
@NoSQLField
@MongoField
public String getRealmId() {
return realmId;
}

View file

@ -1,24 +1,24 @@
package org.keycloak.models.mongo.keycloak.data;
package org.keycloak.models.mongo.keycloak.entities;
import org.jboss.logging.Logger;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.AbstractAttributedNoSQLObject;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
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.MongoId;
import org.keycloak.models.mongo.api.MongoStore;
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>
*/
@NoSQLCollection(collectionName = "users")
public class UserData extends AbstractAttributedNoSQLObject {
private static final Logger logger = Logger.getLogger(UserData.class);
@MongoCollection(collectionName = "users")
public class UserEntity implements MongoEntity {
private String id;
private String loginName;
@ -33,9 +33,14 @@ public class UserData extends AbstractAttributedNoSQLObject {
private List<String> roleIds;
private List<String> scopeIds;
private List<UserModel.RequiredAction> requiredActions;
@NoSQLId
private Map<String, String> attributes;
private List<String> webOrigins;
private List<String> redirectUris;
private List<UserModel.RequiredAction> requiredActions;
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
@MongoId
public String getId() {
return id;
}
@ -44,7 +49,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.id = id;
}
@NoSQLField
@MongoField
public String getLoginName() {
return loginName;
}
@ -53,7 +58,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.loginName = loginName;
}
@NoSQLField
@MongoField
public String getFirstName() {
return firstName;
}
@ -62,7 +67,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.firstName = firstName;
}
@NoSQLField
@MongoField
public String getLastName() {
return lastName;
}
@ -71,7 +76,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.lastName = lastName;
}
@NoSQLField
@MongoField
public String getEmail() {
return email;
}
@ -80,7 +85,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.email = email;
}
@NoSQLField
@MongoField
public boolean isEmailVerified() {
return emailVerified;
}
@ -89,7 +94,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.emailVerified = emailVerified;
}
@NoSQLField
@MongoField
public boolean isEnabled() {
return enabled;
}
@ -98,7 +103,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.enabled = enabled;
}
@NoSQLField
@MongoField
public boolean isTotp() {
return totp;
}
@ -107,7 +112,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.totp = totp;
}
@NoSQLField
@MongoField
public String getRealmId() {
return realmId;
}
@ -116,7 +121,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.realmId = realmId;
}
@NoSQLField
@MongoField
public List<String> getRoleIds() {
return roleIds;
}
@ -125,7 +130,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.roleIds = roleIds;
}
@NoSQLField
@MongoField
public List<String> getScopeIds() {
return scopeIds;
}
@ -134,7 +139,34 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.scopeIds = scopeIds;
}
@NoSQLField
@MongoField
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
@MongoField
public List<String> getWebOrigins() {
return webOrigins;
}
public void setWebOrigins(List<String> webOrigins) {
this.webOrigins = webOrigins;
}
@MongoField
public List<String> getRedirectUris() {
return redirectUris;
}
public void setRedirectUris(List<String> redirectUris) {
this.redirectUris = redirectUris;
}
@MongoField
public List<UserModel.RequiredAction> getRequiredActions() {
return requiredActions;
}
@ -143,25 +175,22 @@ public class UserData extends AbstractAttributedNoSQLObject {
this.requiredActions = requiredActions;
}
@Override
public void afterRemove(NoSQL noSQL) {
NoSQLQuery query = noSQL.createQueryBuilder()
.andCondition("userId", id)
.build();
// Remove social links and passwords of this user
noSQL.removeObjects(SocialLinkData.class, query);
noSQL.removeObjects(PasswordData.class, query);
// Remove this user from all realms, which have him as an admin
NoSQLQuery realmQuery = noSQL.createQueryBuilder()
.andCondition("realmAdmins", id)
.build();
List<RealmData> realms = noSQL.loadObjects(RealmData.class, realmQuery);
for (RealmData realm : realms) {
logger.info("Removing admin user " + getLoginName() + " from realm " + realm.getId());
noSQL.pullItemFromList(realm, "realmAdmins", getId());
@MongoField
public List<CredentialEntity> getCredentials() {
return credentials;
}
public void setCredentials(List<CredentialEntity> credentials) {
this.credentials = credentials;
}
@Override
public void afterRemove(MongoStore mongoStore) {
DBObject query = new QueryBuilder()
.and("userId").is(id)
.get();
// Remove social links of this user
mongoStore.removeObjects(SocialLinkEntity.class, query);
}
}

View file

@ -0,0 +1,50 @@
package org.keycloak.models.mongo.utils;
import java.io.IOException;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class EmbeddedMongo {
protected static final Logger logger = Logger.getLogger(EmbeddedMongo.class);
private MongodExecutable mongodExe;
private MongodProcess mongod;
public void startEmbeddedMongo(int port) {
logger.info("Going to start embedded Mongo on port=" + port);
try {
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(port, Network.localhostIsIPv6()))
.build();
mongodExe = MongodStarter.getDefaultInstance().prepare(mongodConfig);
mongod = mongodExe.start();
} catch (IOException e) {
logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage());
throw new RuntimeException(e);
}
}
public void stopEmbeddedMongo() {
if (mongodExe != null) {
if (mongod != null) {
logger.info("Going to stop embedded MongoDB.");
mongod.stop();
}
mongodExe.stop();
}
}
}

View file

@ -0,0 +1,50 @@
package org.keycloak.models.mongo.utils;
/**
* Encapsulates all info about configuration of MongoDB instance
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoConfiguration {
private final String host;
private final int port;
private final String dbName;
private final boolean clearCollectionsOnStartup;
private final boolean startEmbedded;
public MongoConfiguration(String host, int port, String dbName, boolean clearCollectionsOnStartup, boolean startEmbedded) {
this.host = host;
this.port = port;
this.dbName = dbName;
this.clearCollectionsOnStartup = clearCollectionsOnStartup;
this.startEmbedded = startEmbedded;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getDbName() {
return dbName;
}
public boolean isClearCollectionsOnStartup() {
return clearCollectionsOnStartup;
}
public boolean isStartEmbedded() {
return startEmbedded;
}
@Override
public String toString() {
return String.format("MongoConfiguration: host: %s, port: %d, dbName: %s, clearCollectionsOnStartup: %b, startEmbedded: %b",
host, port, dbName, clearCollectionsOnStartup, startEmbedded);
}
}

View file

@ -0,0 +1,59 @@
package org.keycloak.models.mongo.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.bson.types.ObjectId;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.keycloak.adapters.UserAdapter;
import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
import org.keycloak.models.mongo.keycloak.entities.UserEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoModelUtils {
public static List<ObjectId> convertStringsToObjectIds(Collection<String> strings) {
List<ObjectId> result = new ArrayList<ObjectId>();
for (String id : strings) {
result.add(new ObjectId(id));
}
return result;
}
// Get everything including both application and realm roles
public static List<RoleEntity> getAllRolesOfUser(UserModel user, MongoStore mongoStore) {
UserEntity userEntity = ((UserAdapter)user).getUser();
List<String> roleIds = userEntity.getRoleIds();
if (roleIds == null || roleIds.isEmpty()) {
return Collections.EMPTY_LIST;
}
DBObject query = new QueryBuilder()
.and("_id").in(convertStringsToObjectIds(roleIds))
.get();
return mongoStore.loadObjects(RoleEntity.class, query);
}
// Get everything including both application and realm scopes
public static List<RoleEntity> getAllScopesOfUser(UserModel user, MongoStore mongoStore) {
UserEntity userEntity = ((UserAdapter)user).getUser();
List<String> scopeIds = userEntity.getScopeIds();
if (scopeIds == null || scopeIds.isEmpty()) {
return Collections.EMPTY_LIST;
}
DBObject query = new QueryBuilder()
.and("_id").in(convertStringsToObjectIds(scopeIds))
.get();
return mongoStore.loadObjects(RoleEntity.class, query);
}
}

View file

@ -0,0 +1,56 @@
package org.keycloak.models.mongo.utils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SystemPropertiesConfigurationProvider {
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_COLLECTIONS_ON_STARTUP = "keycloak.mongo.clearCollectionsOnStartup";
private static final String MONGO_START_EMBEDDED = "keycloak.mongo.startEmbedded";
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance
private static final int MONGO_DEFAULT_PORT = 27017;
// Port where embedded MongoDB instance will be started. Same port will be used by KeycloakApplication then
public static final int MONGO_DEFAULT_PORT_EMBEDDED = 27018;
public static String getMongoHost() {
return System.getProperty(MONGO_HOST, "localhost");
}
public static int getMongoPort() {
String portProp = System.getProperty(MONGO_PORT);
if (portProp != null) {
return Integer.parseInt(portProp);
} else {
// Default port is 27017 in case of non-embedded, and 27018 in case of embedded
return isStartEmbedded() ? MONGO_DEFAULT_PORT_EMBEDDED : MONGO_DEFAULT_PORT;
}
}
public static String getMongoDbName() {
return System.getProperty(MONGO_DB_NAME, "keycloak");
}
public static boolean isClearCollectionsOnStartup() {
return Boolean.parseBoolean(System.getProperty(MONGO_CLEAR_COLLECTIONS_ON_STARTUP, "true"));
}
public static boolean isStartEmbedded() {
return Boolean.parseBoolean(System.getProperty(MONGO_START_EMBEDDED, "false"));
}
// Create configuration based on system properties
public static MongoConfiguration createConfiguration() {
return new MongoConfiguration(
getMongoHost(),
getMongoPort(),
getMongoDbName(),
isClearCollectionsOnStartup(),
isStartEmbedded()
);
}
}

View file

@ -1,20 +1,20 @@
package org.keycloak.models.mongo.test;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.AbstractMongoEntity;
import org.keycloak.models.mongo.api.MongoField;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class Address extends AbstractNoSQLObject {
public class Address extends AbstractMongoEntity {
private String street;
private int number;
private List<String> flatNumbers;
@NoSQLField
@MongoField
public String getStreet() {
return street;
}
@ -23,7 +23,7 @@ public class Address extends AbstractNoSQLObject {
this.street = street;
}
@NoSQLField
@MongoField
public int getNumber() {
return number;
}
@ -32,7 +32,7 @@ public class Address extends AbstractNoSQLObject {
this.number = number;
}
@NoSQLField
@MongoField
public List<String> getFlatNumbers() {
return flatNumbers;
}

View file

@ -1,14 +1,16 @@
package org.keycloak.models.mongo.test;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.QueryBuilder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.keycloak.models.mongo.api.NoSQL;
import org.keycloak.models.mongo.api.NoSQLObject;
import org.keycloak.models.mongo.api.query.NoSQLQuery;
import org.keycloak.models.mongo.impl.MongoDBImpl;
import org.junit.Test;
import org.keycloak.models.mongo.api.MongoEntity;
import org.keycloak.models.mongo.api.MongoStore;
import org.keycloak.models.mongo.impl.MongoStoreImpl;
import java.net.UnknownHostException;
import java.util.ArrayList;
@ -20,13 +22,13 @@ import java.util.List;
*/
public class MongoDBModelTest {
private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
private static final Class<? extends MongoEntity>[] MANAGED_DATA_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
Person.class,
Address.class,
};
private MongoClient mongoClient;
private NoSQL mongoDB;
private MongoStore mongoDB;
@Before
public void before() throws Exception {
@ -35,7 +37,7 @@ public class MongoDBModelTest {
mongoClient = new MongoClient("localhost", 27017);
DB db = mongoClient.getDB("keycloakTest");
mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES);
mongoDB = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
@ -47,7 +49,7 @@ public class MongoDBModelTest {
mongoClient.close();
}
// @Test
@Test
public void mongoModelTest() throws Exception {
// Add some user
Person john = new Person();
@ -55,17 +57,17 @@ public class MongoDBModelTest {
john.setAge(25);
john.setGender(Person.Gender.MALE);
mongoDB.saveObject(john);
mongoDB.insertObject(john);
// Add another user
Person mary = new Person();
mary.setFirstName("mary");
mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"}));
mary.setKids(Arrays.asList("Peter", "Paul", "Wendy"));
Address addr1 = new Address();
addr1.setStreet("Elm");
addr1.setNumber(5);
addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"}));
addr1.setFlatNumbers(Arrays.asList("flat1", "flat2"));
Address addr2 = new Address();
List<Address> addresses = new ArrayList<Address>();
addresses.add(addr1);
@ -74,12 +76,13 @@ public class MongoDBModelTest {
mary.setAddresses(addresses);
mary.setMainAddress(addr1);
mary.setGender(Person.Gender.FEMALE);
mary.setGenders(Arrays.asList(new Person.Gender[] {Person.Gender.FEMALE}));
mongoDB.saveObject(mary);
mary.setGenders(Arrays.asList(Person.Gender.FEMALE));
Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size());
mongoDB.insertObject(mary);
NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build();
Assert.assertEquals(2, mongoDB.loadObjects(Person.class, new QueryBuilder().get()).size());
DBObject query = new QueryBuilder().and("addresses.flatNumbers").is("flat1").get();
List<Person> persons = mongoDB.loadObjects(Person.class, query);
Assert.assertEquals(1, persons.size());
mary = persons.get(0);
@ -89,13 +92,13 @@ public class MongoDBModelTest {
Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass());
// Test push/pull
mongoDB.pushItemToList(mary, "kids", "Pauline");
mongoDB.pushItemToList(mary, "kids", "Pauline", true);
mongoDB.pullItemFromList(mary, "kids", "Paul");
Address addr3 = new Address();
addr3.setNumber(6);
addr3.setStreet("Broadway");
mongoDB.pushItemToList(mary, "addresses", addr3);
mongoDB.pushItemToList(mary, "addresses", addr3, true);
mary = mongoDB.loadObject(Person.class, mary.getId());
Assert.assertEquals(3, mary.getKids().size());
@ -107,5 +110,24 @@ public class MongoDBModelTest {
Assert.assertEquals(5, mainAddress.getNumber());
Assert.assertEquals(Person.Gender.FEMALE, mary.getGender());
Assert.assertTrue(mary.getGenders().contains(Person.Gender.FEMALE));
// Some test of Map (attributes)
mary.addAttribute("attr1", "value1");
mary.addAttribute("attr2", "value2");
mary.addAttribute("attr.some3", "value3");
mongoDB.updateObject(mary);
mary = mongoDB.loadObject(Person.class, mary.getId());
Assert.assertEquals(3, mary.getAttributes().size());
mary.removeAttribute("attr2");
mary.removeAttribute("nonExisting");
mongoDB.updateObject(mary);
mary = mongoDB.loadObject(Person.class, mary.getId());
Assert.assertEquals(2, mary.getAttributes().size());
Assert.assertEquals("value1", mary.getAttributes().get("attr1"));
Assert.assertEquals("value3", mary.getAttributes().get("attr.some3"));
}
}

View file

@ -1,17 +1,19 @@
package org.keycloak.models.mongo.test;
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
import org.keycloak.models.mongo.api.NoSQLCollection;
import org.keycloak.models.mongo.api.NoSQLField;
import org.keycloak.models.mongo.api.NoSQLId;
import org.keycloak.models.mongo.api.AbstractMongoEntity;
import org.keycloak.models.mongo.api.MongoCollection;
import org.keycloak.models.mongo.api.MongoField;
import org.keycloak.models.mongo.api.MongoId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NoSQLCollection(collectionName = "persons")
public class Person extends AbstractNoSQLObject {
@MongoCollection(collectionName = "persons")
public class Person extends AbstractMongoEntity {
private String id;
private String firstName;
@ -21,9 +23,10 @@ public class Person extends AbstractNoSQLObject {
private Address mainAddress;
private Gender gender;
private List<Gender> genders;
private Map<String, String> attributes = new HashMap<String, String>();
@NoSQLId
@MongoId
public String getId() {
return id;
}
@ -32,7 +35,7 @@ public class Person extends AbstractNoSQLObject {
this.id = id;
}
@NoSQLField
@MongoField
public String getFirstName() {
return firstName;
}
@ -41,7 +44,7 @@ public class Person extends AbstractNoSQLObject {
this.firstName = firstName;
}
@NoSQLField
@MongoField
public int getAge() {
return age;
}
@ -50,7 +53,7 @@ public class Person extends AbstractNoSQLObject {
this.age = age;
}
@NoSQLField
@MongoField
public Gender getGender() {
return gender;
}
@ -59,7 +62,7 @@ public class Person extends AbstractNoSQLObject {
this.gender = gender;
}
@NoSQLField
@MongoField
public List<Gender> getGenders() {
return genders;
}
@ -68,7 +71,7 @@ public class Person extends AbstractNoSQLObject {
this.genders = genders;
}
@NoSQLField
@MongoField
public List<String> getKids() {
return kids;
}
@ -77,7 +80,7 @@ public class Person extends AbstractNoSQLObject {
this.kids = kids;
}
@NoSQLField
@MongoField
public List<Address> getAddresses() {
return addresses;
}
@ -86,7 +89,7 @@ public class Person extends AbstractNoSQLObject {
this.addresses = addresses;
}
@NoSQLField
@MongoField
public Address getMainAddress() {
return mainAddress;
}
@ -95,6 +98,23 @@ public class Person extends AbstractNoSQLObject {
this.mainAddress = mainAddress;
}
@MongoField
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
public void addAttribute(String key, String value) {
attributes.put(key, value);
}
public void removeAttribute(String key) {
attributes.remove(key);
}
public static enum Gender {
MALE, FEMALE
}

View file

@ -37,6 +37,6 @@
<module>api</module>
<!-- <module>picketlink</module> -->
<module>jpa</module>
<!-- <module>mongo</module> -->
<module>mongo</module>
</modules>
</project>

View file

@ -16,7 +16,7 @@
<resteasy.version>3.0.6.Final</resteasy.version>
<undertow.version>1.0.0.Beta30</undertow.version>
<picketlink.version>2.5.0.Beta6</picketlink.version>
<mongo.driver.version>2.11.2</mongo.driver.version>
<mongo.driver.version>2.11.3</mongo.driver.version>
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
<jboss-logging-tools.version>1.2.0.Beta1</jboss-logging-tools.version>
<hibernate.javax.persistence.version>1.0.1.Final</hibernate.javax.persistence.version>
@ -316,7 +316,7 @@
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.27</version>
<version>1.40</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>

View file

@ -72,11 +72,25 @@
</dependency>
-->
<!--<dependency>
<!-- TODO: remove -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-mongo</artifactId>
<version>${project.version}</version>
</dependency>-->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-common</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId>

View file

@ -8,6 +8,7 @@ import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.admin.AdminService;
import org.keycloak.services.utils.ModelProviderUtils;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;
@ -15,7 +16,6 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashSet;
import java.util.ServiceLoader;
import java.util.Set;
/**
@ -26,9 +26,6 @@ public class KeycloakApplication extends Application {
private static final Logger log = Logger.getLogger(KeycloakApplication.class);
private static final String MODEL_PROVIDER = "keycloak.model";
private static final String DEFAULT_MODEL_PROVIDER = "jpa";
protected Set<Object> singletons = new HashSet<Object>();
protected Set<Class<?>> classes = new HashSet<Class<?>>();
@ -73,28 +70,7 @@ public class KeycloakApplication extends Application {
public static KeycloakSessionFactory createSessionFactory() {
ServiceLoader<ModelProvider> providers = ServiceLoader.load(ModelProvider.class);
String configuredProvider = System.getProperty(MODEL_PROVIDER);
ModelProvider provider = null;
if (configuredProvider != null) {
for (ModelProvider p : providers) {
if (p.getId().equals(configuredProvider)) {
provider = p;
}
}
} else {
for (ModelProvider p : providers) {
if (provider == null) {
provider = p;
}
if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) {
provider = p;
break;
}
}
}
ModelProvider provider = ModelProviderUtils.getConfiguredModelProvider();
if (provider != null) {
log.debug("Model provider: " + provider.getId());

View file

@ -0,0 +1,50 @@
package org.keycloak.services.utils;
import java.util.ServiceLoader;
import org.keycloak.models.ModelProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ModelProviderUtils {
public static final String MODEL_PROVIDER = "keycloak.model";
public static final String DEFAULT_MODEL_PROVIDER = "jpa";
public static Iterable<ModelProvider> getRegisteredProviders() {
return ServiceLoader.load(ModelProvider.class);
}
public static ModelProvider getConfiguredModelProvider(Iterable<ModelProvider> providers) {
String configuredProvider = System.getProperty(MODEL_PROVIDER);
ModelProvider provider = null;
if (configuredProvider != null) {
for (ModelProvider p : providers) {
if (p.getId().equals(configuredProvider)) {
provider = p;
}
}
} else {
for (ModelProvider p : providers) {
if (provider == null) {
provider = p;
}
if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) {
provider = p;
break;
}
}
}
return provider;
}
public static ModelProvider getConfiguredModelProvider() {
return getConfiguredModelProvider(getRegisteredProviders());
}
}

View file

@ -1,88 +0,0 @@
package org.keycloak.services.utils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PropertiesManager {
private static final String SESSION_FACTORY = "keycloak.sessionFactory";
public static final String SESSION_FACTORY_PICKETLINK = "picketlink";
public static final String SESSION_FACTORY_MONGO = "mongo";
public static final String SESSION_FACTORY_JPA = "jpa";
private static final String MONGO_HOST = "keycloak.mongodb.host";
private static final String MONGO_PORT = "keycloak.mongodb.port";
private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
// Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
// Port where unit tests will start embedded MongoDB instance
public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
public static String getSessionFactoryType() {
return System.getProperty(SESSION_FACTORY, SESSION_FACTORY_JPA);
}
public static void setSessionFactoryType(String sessionFactoryType) {
System.setProperty(SESSION_FACTORY, sessionFactoryType);
}
public static void setDefaultSessionFactoryType() {
System.setProperty(SESSION_FACTORY, SESSION_FACTORY_JPA);
}
public static boolean isMongoSessionFactory() {
return getSessionFactoryType().equals(SESSION_FACTORY_MONGO);
}
public static boolean isPicketlinkSessionFactory() {
return getSessionFactoryType().equals(SESSION_FACTORY_PICKETLINK);
}
public static boolean isJpaSessionFactory() {
return getSessionFactoryType().equals(SESSION_FACTORY_JPA);
}
public static String getMongoHost() {
return System.getProperty(MONGO_HOST, "localhost");
}
public static void setMongoHost(String mongoHost) {
System.setProperty(MONGO_HOST, mongoHost);
}
public static int getMongoPort() {
return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
}
public static void setMongoPort(int mongoPort) {
System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
}
public static String getMongoDbName() {
return System.getProperty(MONGO_DB_NAME, "keycloak");
}
public static void setMongoDbName(String mongoMongoDbName) {
System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
}
public static boolean dropDatabaseOnStartup() {
return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
}
public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
}
public static boolean bootstrapEmbeddedMongoAtContextInit() {
return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
}
}

View file

@ -11,7 +11,6 @@ import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
import org.keycloak.test.common.AbstractKeycloakTest;
import org.keycloak.test.common.SessionFactoryTestContext;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
@ -25,10 +24,9 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest {
private RealmModel realm;
private UserModel user;
public AuthenticationManagerTest(SessionFactoryTestContext testContext) {
super(testContext);
public AuthenticationManagerTest(String providerId) {
super(providerId);
}
@Test
public void authForm() {
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
@ -126,7 +124,7 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest {
@Before
public void before() throws Exception {
super.before();
realm = getRealmManager().createRealm("Test");
realm = realmManager.createRealm("Test");
realm.setAccessCodeLifespan(100);
realm.setEnabled(true);
realm.setName("Test");

View file

@ -18,7 +18,6 @@ import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.OAuthClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.test.common.AbstractKeycloakTest;
import org.keycloak.test.common.SessionFactoryTestContext;
import java.util.ArrayList;
import java.util.Arrays;
@ -35,8 +34,8 @@ import java.util.StringTokenizer;
public class AdapterTest extends AbstractKeycloakTest {
private RealmModel realmModel;
public AdapterTest(SessionFactoryTestContext testContext) {
super(testContext);
public AdapterTest(String providerId) {
super(providerId);
}
@Test
@ -57,7 +56,7 @@ public class AdapterTest extends AbstractKeycloakTest {
@Test
public void test1CreateRealm() throws Exception {
realmModel = getRealmManager().createRealm("JUGGLER");
realmModel = realmManager.createRealm("JUGGLER");
realmModel.setAccessCodeLifespan(100);
realmModel.setAccessCodeLifespanUserAction(600);
realmModel.setEnabled(true);
@ -69,7 +68,7 @@ public class AdapterTest extends AbstractKeycloakTest {
realmModel.addDefaultRole("foo");
System.out.println(realmModel.getId());
realmModel = getRealmManager().getRealm(realmModel.getId());
realmModel = realmManager.getRealm(realmModel.getId());
Assert.assertNotNull(realmModel);
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
@ -85,7 +84,7 @@ public class AdapterTest extends AbstractKeycloakTest {
@Test
public void testRealmListing() throws Exception {
realmModel = getRealmManager().createRealm("JUGGLER");
realmModel = realmManager.createRealm("JUGGLER");
realmModel.setAccessCodeLifespan(100);
realmModel.setAccessCodeLifespanUserAction(600);
realmModel.setEnabled(true);
@ -97,7 +96,7 @@ public class AdapterTest extends AbstractKeycloakTest {
realmModel.addDefaultRole("foo");
System.out.println(realmModel.getId());
realmModel = getRealmManager().getRealm(realmModel.getId());
realmModel = realmManager.getRealm(realmModel.getId());
Assert.assertNotNull(realmModel);
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
@ -300,7 +299,7 @@ public class AdapterTest extends AbstractKeycloakTest {
user3.setEmail("knut@redhat.com");
}
RealmManager adapter = getRealmManager();
RealmManager adapter = realmManager;
{
List<UserModel> userModels = adapter.searchUsers("total junk query", realmModel);

View file

@ -1,19 +1,15 @@
package org.keycloak.test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.services.managers.ApplicationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.test.common.AbstractKeycloakTest;
import java.util.Iterator;
import java.util.List;
@ -21,24 +17,21 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ApplicationModelTest extends AbstractKeycloakServerTest {
private KeycloakSessionFactory factory;
private KeycloakSession identitySession;
private RealmManager manager;
public class ApplicationModelTest extends AbstractKeycloakTest {
private ApplicationModel application;
private RealmModel realm;
private ApplicationManager appManager;
public ApplicationModelTest(String providerId) {
super(providerId);
}
@Before
public void before() throws Exception {
factory = KeycloakApplication.createSessionFactory();
identitySession = factory.createSession();
identitySession.getTransaction().begin();
manager = new RealmManager(identitySession);
super.before();
appManager = new ApplicationManager(realmManager);
appManager = new ApplicationManager(manager);
realm = manager.createRealm("original");
realm = realmManager.createRealm("original");
application = realm.addApplication("application");
application.setBaseUrl("http://base");
application.setManagementUrl("http://management");
@ -57,16 +50,9 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
application.updateApplication();
}
@After
public void after() throws Exception {
identitySession.getTransaction().commit();
identitySession.close();
factory.close();
}
@Test
public void persist() {
RealmModel persisted = manager.getRealm(realm.getId());
RealmModel persisted = realmManager.getRealm(realm.getId());
assertEquals(application, persisted.getApplicationNameMap().get("app-name"));
}
@ -75,7 +61,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
public void json() {
ApplicationRepresentation representation = appManager.toRepresentation(application);
RealmModel realm = manager.createRealm("copy");
RealmModel realm = realmManager.createRealm("copy");
ApplicationModel copy = appManager.createApplication(realm, representation);
assertEquals(application, copy);

View file

@ -13,7 +13,6 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.test.common.AbstractKeycloakTest;
import org.keycloak.test.common.SessionFactoryTestContext;
import java.util.List;
import java.util.Set;
@ -25,13 +24,13 @@ import java.util.Set;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ImportTest extends AbstractKeycloakTest {
public ImportTest(SessionFactoryTestContext testContext) {
super(testContext);
public ImportTest(String providerId) {
super(providerId);
}
@Test
public void install() throws Exception {
RealmManager manager = getRealmManager();
RealmManager manager = realmManager;
RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm.json");
RealmModel realm = manager.createRealm("demo", rep.getRealm());
manager.importRealm(rep, realm);
@ -91,7 +90,7 @@ public class ImportTest extends AbstractKeycloakTest {
@Test
public void install2() throws Exception {
RealmManager manager = getRealmManager();
RealmManager manager = realmManager;
RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm-demo.json");
RealmModel realm = manager.createRealm("demo", rep.getRealm());
manager.importRealm(rep, realm);

View file

@ -1,46 +1,27 @@
package org.keycloak.test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.test.common.AbstractKeycloakTest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class ModelTest extends AbstractKeycloakServerTest {
private KeycloakSessionFactory factory;
private KeycloakSession identitySession;
private RealmManager manager;
public class ModelTest extends AbstractKeycloakTest {
@Before
public void before() throws Exception {
factory = KeycloakApplication.createSessionFactory();
identitySession = factory.createSession();
identitySession.getTransaction().begin();
manager = new RealmManager(identitySession);
}
@After
public void after() throws Exception {
identitySession.getTransaction().commit();
identitySession.close();
factory.close();
public ModelTest(String providerId) {
super(providerId);
}
@Test
public void importExportRealm() {
RealmModel realm = manager.createRealm("original");
RealmModel realm = realmManager.createRealm("original");
realm.setRegistrationAllowed(true);
realm.setResetPasswordAllowed(true);
realm.setSocial(true);
@ -62,10 +43,10 @@ public class ModelTest extends AbstractKeycloakServerTest {
HashMap<String, String> social = new HashMap<String,String>();
social.put("google.key", "1234");
social.put("google.secret", "5678");
realm.setSmtpConfig(social);
realm.setSocialConfig(social);
RealmModel peristed = manager.getRealm(realm.getId());
assertEquals(realm, peristed);
RealmModel persisted = realmManager.getRealm(realm.getId());
assertEquals(realm, persisted);
RealmModel copy = importExport(realm, "copy");
assertEquals(realm, copy);
@ -103,9 +84,9 @@ public class ModelTest extends AbstractKeycloakServerTest {
private RealmModel importExport(RealmModel src, String copyName) {
RealmRepresentation representation = ModelToRepresentation.toRepresentation(src);
RealmModel copy = manager.createRealm(copyName);
manager.importRealm(representation, copy);
return manager.getRealm(copy.getId());
RealmModel copy = realmManager.createRealm(copyName);
realmManager.importRealm(representation, copy);
return realmManager.getRealm(copy.getId());
}
}

View file

@ -1,17 +1,13 @@
package org.keycloak.test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.test.common.AbstractKeycloakTest;
import java.util.Iterator;
import java.util.List;
@ -19,29 +15,15 @@ import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class UserModelTest extends AbstractKeycloakServerTest {
private KeycloakSessionFactory factory;
private KeycloakSession identitySession;
private RealmManager manager;
public class UserModelTest extends AbstractKeycloakTest {
@Before
public void before() throws Exception {
factory = KeycloakApplication.createSessionFactory();
identitySession = factory.createSession();
identitySession.getTransaction().begin();
manager = new RealmManager(identitySession);
}
@After
public void after() throws Exception {
identitySession.getTransaction().commit();
identitySession.close();
factory.close();
public UserModelTest(String providerId) {
super(providerId);
}
@Test
public void persistUser() {
RealmModel realm = manager.createRealm("original");
RealmModel realm = realmManager.createRealm("original");
UserModel user = realm.addUser("user");
user.setFirstName("first-name");
user.setLastName("last-name");
@ -56,14 +38,14 @@ public class UserModelTest extends AbstractKeycloakServerTest {
user.addWebOrigin("origin-1");
user.addWebOrigin("origin-2");
UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
UserModel persisted = realmManager.getRealm(realm.getId()).getUser("user");
assertEquals(user, persisted);
}
@Test
public void webOriginSetTest() {
RealmModel realm = manager.createRealm("original");
RealmModel realm = realmManager.createRealm("original");
UserModel user = realm.addUser("user");
Assert.assertTrue(user.getWebOrigins().isEmpty());
@ -83,7 +65,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
@Test
public void testUserRequiredActions() throws Exception {
RealmModel realm = manager.createRealm("original");
RealmModel realm = realmManager.createRealm("original");
UserModel user = realm.addUser("user");
Assert.assertTrue(user.getRequiredActions().isEmpty());
@ -91,7 +73,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
String id = realm.getId();
commit();
realm = manager.getRealm(id);
realm = realmManager.getRealm(id);
user = realm.getUser("user");
Assert.assertEquals(1, user.getRequiredActions().size());
@ -127,7 +109,7 @@ public class UserModelTest extends AbstractKeycloakServerTest {
identitySession.close();
identitySession = factory.createSession();
identitySession.getTransaction().begin();
manager = new RealmManager(identitySession);
realmManager = new RealmManager(identitySession);
}
public static void assertEquals(UserModel expected, UserModel actual) {

View file

@ -1,5 +1,6 @@
package org.keycloak.test.common;
import org.jboss.resteasy.logging.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@ -8,11 +9,15 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelProvider;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.utils.ModelProviderUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ServiceLoader;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -20,58 +25,42 @@ import java.util.List;
@RunWith(Parameterized.class)
public abstract class AbstractKeycloakTest {
protected static final SessionFactoryTestContext[] TEST_CONTEXTS;
private static final Logger log = Logger.getLogger(AbstractKeycloakTest.class);
private final SessionFactoryTestContext testContext;
protected KeycloakSessionFactory factory;
protected KeycloakSession identitySession;
protected RealmManager realmManager;
// STATIC METHODS
static
{
// TODO: MongoDB disabled by default
TEST_CONTEXTS = new SessionFactoryTestContext[] {
//new PicketlinkSessionFactoryTestContext(),
new JpaSessionFactoryTestContext(),
// new MongoDBSessionFactoryTestContext()
};
}
@Parameterized.Parameters
public static Iterable<Object[]> parameters() {
Iterable<ModelProvider> modelProviders;
// We will run tests with all registered models if -Dkeycloak.model=all . Otherwise just with configured provider
String configuredProvider = System.getProperty(ModelProviderUtils.MODEL_PROVIDER);
if ("all".equalsIgnoreCase(configuredProvider)) {
modelProviders = ModelProviderUtils.getRegisteredProviders();
} else {
ModelProvider provider = ModelProviderUtils.getConfiguredModelProvider();
modelProviders = Arrays.asList(provider);
}
log.debug("Will use model providers: " + modelProviders);
List<Object[]> params = new ArrayList<Object[]>();
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
params.add(new Object[] {testContext});
for (ModelProvider provider : modelProviders) {
params.add(new Object[] { provider.getId() });
}
return params;
}
@BeforeClass
public static void baseBeforeClass() {
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
testContext.beforeTestClass();
}
}
@AfterClass
public static void baseAfterClass() {
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
testContext.afterTestClass();
}
}
// NON-STATIC METHODS
public AbstractKeycloakTest(SessionFactoryTestContext testContext) {
this.testContext = testContext;
public AbstractKeycloakTest(String providerId) {
System.setProperty(ModelProviderUtils.MODEL_PROVIDER, providerId);
}
@Before
public void before() throws Exception {
testContext.initEnvironment();
factory = KeycloakApplication.createSessionFactory();
identitySession = factory.createSession();
identitySession.getTransaction().begin();
@ -85,12 +74,4 @@ public abstract class AbstractKeycloakTest {
factory.close();
}
protected RealmManager getRealmManager() {
return realmManager;
}
protected KeycloakSession getIdentitySession() {
return identitySession;
}
}

View file

@ -1,24 +0,0 @@
package org.keycloak.test.common;
import org.keycloak.services.utils.PropertiesManager;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JpaSessionFactoryTestContext implements SessionFactoryTestContext {
@Override
public void beforeTestClass() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void afterTestClass() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void initEnvironment() {
PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_JPA);
}
}

View file

@ -1,24 +0,0 @@
package org.keycloak.test.common;
import org.keycloak.services.utils.PropertiesManager;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PicketlinkSessionFactoryTestContext implements SessionFactoryTestContext {
@Override
public void beforeTestClass() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void afterTestClass() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void initEnvironment() {
PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_PICKETLINK);
}
}

View file

@ -1,17 +0,0 @@
package org.keycloak.test.common;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface SessionFactoryTestContext {
void beforeTestClass();
void afterTestClass();
/**
* Init system properties (or other configuration) to ensure that KeycloakApplication.buildSessionFactory() will return correct
* instance of KeycloakSessionFactory for our test
*/
void initEnvironment();
}