From 0acc9e978a93e1127f548f3e1085adf5b56d6dfe Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 30 Aug 2013 17:38:54 +0200 Subject: [PATCH 01/14] Added first version of NoSQL api and MongoDBImpl implementation --- pom.xml | 11 +- services/pom.xml | 8 + .../models/nosql/adapters/NoSQLRealm.java | 49 +++++ .../models/nosql/adapters/NoSQLUser.java | 46 ++++ .../nosql/api/AttributedNoSQLObject.java | 17 ++ .../services/models/nosql/api/NoSQL.java | 26 +++ .../models/nosql/api/NoSQLCollection.java | 21 ++ .../services/models/nosql/api/NoSQLField.java | 22 ++ .../services/models/nosql/api/NoSQLId.java | 18 ++ .../models/nosql/api/NoSQLObject.java | 9 + .../models/nosql/impl/MongoDBImpl.java | 203 ++++++++++++++++++ .../models/nosql/impl/ObjectInfo.java | 53 +++++ .../services/models/nosql/impl/Test.java | 53 +++++ 13 files changed, 533 insertions(+), 3 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java diff --git a/pom.xml b/pom.xml index d08e744eeb60..99a90cd93023 100755 --- a/pom.xml +++ b/pom.xml @@ -236,9 +236,9 @@ com.icegreen greenmail 1.3.1b - + - + org.seleniumhq.selenium selenium-java @@ -248,7 +248,12 @@ org.seleniumhq.selenium selenium-chrome-driver 2.35.0 - + + + org.mongodb + mongo-java-driver + 2.11.2 + diff --git a/services/pom.xml b/services/pom.xml index 4d8075f8cc99..7c38f45478a2 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -144,6 +144,14 @@ jackson-xc provided + + org.picketlink + picketlink-common + + + org.mongodb + mongo-java-driver + junit junit diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java new file mode 100644 index 000000000000..c061f8b8c382 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java @@ -0,0 +1,49 @@ +package org.keycloak.services.models.nosql.adapters; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "realms") +public class NoSQLRealm implements NoSQLObject { + + private String oid; + private String prop1; + private Integer prop2; + + @NoSQLId + public String getOid() { + return oid; + } + + public void setOid(String oid) { + this.oid = oid; + } + + @NoSQLField(fieldName = "property1") + public String getProp1() { + return prop1; + } + + public void setProp1(String prop1) { + this.prop1 = prop1; + } + + @NoSQLField(fieldName = "property2") + public Integer getProp2() { + return prop2; + } + + public void setProp2(Integer prop2) { + this.prop2 = prop2; + } + + @Override + public String toString() { + return "NoSQLRealm [ oid=" + oid + ", prop1=" + prop1 + ", prop2=" + prop2 + "]"; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java new file mode 100644 index 000000000000..5c316fff4b32 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java @@ -0,0 +1,46 @@ +package org.keycloak.services.models.nosql.adapters; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "users") +public class NoSQLUser { + + @NoSQLId + private String oid; + + private String username; + + private String realmId; + + @NoSQLId + public String getOid() { + return oid; + } + + public void setOid(String oid) { + this.oid = oid; + } + + @NoSQLField + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @NoSQLField(fieldName = "realm_id") + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java new file mode 100644 index 000000000000..f750e82a8cc3 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java @@ -0,0 +1,17 @@ +package org.keycloak.services.models.nosql.api; + +import java.util.Map; + +/** + * @author Marek Posolda + */ +public interface AttributedNoSQLObject extends NoSQLObject { + + void setAttribute(String name, String value); + + void removeAttribute(String name); + + String getAttribute(String name); + + Map getAttributes(); +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java new file mode 100644 index 000000000000..dc16bb6ec188 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java @@ -0,0 +1,26 @@ +package org.keycloak.services.models.nosql.api; + +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public interface NoSQL { + + /** + * Insert object if it's oid is null. Otherwise update + */ + void saveObject(NoSQLObject object); + + T loadObject(Class type, String oid); + + List loadObjects(Class type, Map queryAttributes); + + // Object must have filled oid + void removeObject(NoSQLObject object); + + void removeObject(Class type, String oid); + + void removeObjects(Class type, Map queryAttributes); +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java new file mode 100644 index 000000000000..ff41188736c5 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java @@ -0,0 +1,21 @@ +package org.keycloak.services.models.nosql.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Marek Posolda + */ +@Target({TYPE}) +@Documented +@Retention(RUNTIME) +@Inherited +public @interface NoSQLCollection { + + String collectionName(); +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java new file mode 100644 index 000000000000..c3e0586d2f66 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java @@ -0,0 +1,22 @@ +package org.keycloak.services.models.nosql.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Marek Posolda + */ +@Target({METHOD, FIELD}) +@Documented +@Retention(RUNTIME) +public @interface NoSQLField { + + String fieldName() default ""; + + // TODO: add lazy loading? +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java new file mode 100644 index 000000000000..0cbca8542f8a --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java @@ -0,0 +1,18 @@ +package org.keycloak.services.models.nosql.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Marek Posolda + */ +@Target({METHOD, FIELD}) +@Documented +@Retention(RUNTIME) +public @interface NoSQLId { +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java new file mode 100644 index 000000000000..1f430b6f5a13 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java @@ -0,0 +1,9 @@ +package org.keycloak.services.models.nosql.api; + +/** + * Just marker interface + * + * @author Marek Posolda + */ +public interface NoSQLObject { +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java new file mode 100644 index 000000000000..bbdd84685c7f --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -0,0 +1,203 @@ +package org.keycloak.services.models.nosql.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import org.bson.types.ObjectId; +import org.jboss.resteasy.logging.Logger; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.picketlink.common.properties.Property; +import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; +import org.picketlink.common.properties.query.PropertyQueries; + +/** + * @author Marek Posolda + */ +public class MongoDBImpl implements NoSQL { + + private final DB database; + // private static final Logger logger = Logger.getLogger(MongoDBImpl.class); + + public MongoDBImpl(DB database) { + this.database = database; + } + + private ConcurrentMap, ObjectInfo> objectInfoCache = + new ConcurrentHashMap, ObjectInfo>(); + + + @Override + public void saveObject(NoSQLObject object) { + Class clazz = object.getClass(); + + // Find annotations for ID, for all the properties and for the name of the collection. + ObjectInfo objectInfo = getObjectInfo(clazz); + + // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped) + BasicDBObject dbObject = new BasicDBObject(); + List> props = objectInfo.getProperties(); + for (Property property : props) { + String propName = property.getName(); + Object propValue = property.getValue(object); + + + dbObject.append(propName, propValue); + + // Adding attributes + if (object instanceof AttributedNoSQLObject) { + AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object; + Map attributes = attributedObject.getAttributes(); + for (Map.Entry attribute : attributes.entrySet()) { + dbObject.append(attribute.getKey(), attribute.getValue()); + } + } + } + + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + // Decide if we should insert or update (based on presence of oid property in original object) + Property oidProperty = objectInfo.getOidProperty(); + String currentId = oidProperty.getValue(object); + if (currentId == null) { + dbCollection.insert(dbObject); + + // Add oid to value of given object + oidProperty.setValue(object, dbObject.getString("_id")); + } else { + BasicDBObject setCommand = new BasicDBObject("$set", dbObject); + BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId)); + dbCollection.update(query, setCommand); + } + } + + @Override + public T loadObject(Class type, String oid) { + ObjectInfo objectInfo = getObjectInfo(type); + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid)); + DBObject dbObject = dbCollection.findOne(idQuery); + + return convertObject(type, dbObject); + } + + @Override + public List loadObjects(Class type, Map queryAttributes) { + ObjectInfo objectInfo = getObjectInfo(type); + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + BasicDBObject query = new BasicDBObject(); + for (Map.Entry queryAttr : queryAttributes.entrySet()) { + query.append(queryAttr.getKey(), queryAttr.getValue()); + } + DBCursor cursor = dbCollection.find(query); + + return convertCursor(type, cursor); + } + + @Override + public void removeObject(NoSQLObject object) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void removeObject(Class type, String oid) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void removeObjects(Class type, Map queryAttributes) { + //To change body of implemented methods use File | Settings | File Templates. + } + + private ObjectInfo getObjectInfo(Class objectClass) { + ObjectInfo objectInfo = (ObjectInfo)objectInfoCache.get(objectClass); + if (objectInfo == null) { + Property idProperty = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult(); + if (idProperty == null) { + throw new IllegalStateException("Class " + objectClass + " doesn't have property with declared annotation " + NoSQLId.class); + } + + List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList(); + + NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class); + if (classAnnotation == null) { + throw new IllegalStateException("Class " + objectClass + " doesn't have annotation " + NoSQLCollection.class); + } + + String dbCollectionName = classAnnotation.collectionName(); + objectInfo = new ObjectInfo((Class)objectClass, dbCollectionName, idProperty, properties); + + ObjectInfo existing = objectInfoCache.putIfAbsent((Class)objectClass, objectInfo); + if (existing != null) { + objectInfo = existing; + } + } + + return objectInfo; + } + + + private T convertObject(Class type, DBObject dbObject) { + ObjectInfo objectInfo = getObjectInfo(type); + + T object; + try { + object = type.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + for (String key : dbObject.keySet()) { + Object value = dbObject.get(key); + Property property; + + if ("_id".equals(key)) { + // Current property is "id" + Property idProperty = objectInfo.getOidProperty(); + idProperty.setValue(object, value.toString()); + + } else if ((property = objectInfo.getPropertyByName(key)) != null) { + // It's declared property with @DBField annotation + property.setValue(object, value); + + } 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 + // TODO: logging + // logger.warn("Property with key " + key + " not known for type " + type); + System.err.println("Property with key " + key + " not known for type " + type); + } + } + + return object; + } + + private List convertCursor(Class type, DBCursor cursor) { + List result = new ArrayList(); + + for (DBObject dbObject : cursor) { + T converted = convertObject(type, dbObject); + result.add(converted); + } + + return result; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java new file mode 100644 index 000000000000..867ac12b62af --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java @@ -0,0 +1,53 @@ +package org.keycloak.services.models.nosql.impl; + +import java.util.List; + +import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.picketlink.common.properties.Property; + +/** + * @author Marek Posolda + */ +class ObjectInfo { + + private final Class objectClass; + + private final String dbCollectionName; + + private final Property oidProperty; + + private final List> properties; + + public ObjectInfo(Class objectClass, String dbCollectionName, Property oidProperty, List> properties) { + this.objectClass = objectClass; + this.dbCollectionName = dbCollectionName; + this.oidProperty = oidProperty; + this.properties = properties; + } + + public Class getObjectClass() { + return objectClass; + } + + public String getDbCollectionName() { + return dbCollectionName; + } + + public Property getOidProperty() { + return oidProperty; + } + + public List> getProperties() { + return properties; + } + + public Property getPropertyByName(String propertyName) { + for (Property property : properties) { + if (propertyName.equals(property.getName())) { + return property; + } + } + + return null; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java new file mode 100644 index 000000000000..375418ab41f5 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java @@ -0,0 +1,53 @@ +package org.keycloak.services.models.nosql.impl; + +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import org.keycloak.services.models.nosql.adapters.NoSQLRealm; + +/** + * TODO: delete + * + * @author Marek Posolda + */ +public class Test { + + public static void main(String[] args) throws UnknownHostException { + MongoClient mongoClient = new MongoClient( "localhost" , 27017 ); + DB javaDB = mongoClient.getDB("java"); + + MongoDBImpl test = new MongoDBImpl(javaDB); + NoSQLRealm realm = new NoSQLRealm(); + realm.setOid("522085fc31dab908ec31c0cb"); + realm.setProp1("something1"); + realm.setProp2(12); + test.saveObject(realm); + System.out.println(realm.getOid()); + + realm = test.loadObject(NoSQLRealm.class, "522085fc31dab908ec31c0cb"); + System.out.println("Loaded realm: " + realm); + + Map query = new HashMap(); + query.put("prop1", "sm"); + List queryResults = test.loadObjects(NoSQLRealm.class, query); + System.out.println("results1: " + queryResults); + + query.put("prop1", "something2"); + queryResults = test.loadObjects(NoSQLRealm.class, query); + System.out.println("results2: " + queryResults); + + query.put("prop2", 12); + queryResults = test.loadObjects(NoSQLRealm.class, query); + System.out.println("results3: " + queryResults); + + query.put("prop1", "something1"); + queryResults = test.loadObjects(NoSQLRealm.class, query); + System.out.println("results4: " + queryResults); + + mongoClient.close(); + } +} From 815e466d43655c08ebbb4cdad54d466272e6cba6 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 30 Aug 2013 19:23:50 +0200 Subject: [PATCH 02/14] Adding TypeConverters. AdapterTest.test1CreateRealm() is passing for MongoDB --- .../nosql/adapters/MongoDBSessionFactory.java | 43 ++ .../models/nosql/adapters/NoSQLRealm.java | 49 -- .../models/nosql/adapters/NoSQLSession.java | 67 +++ .../nosql/adapters/NoSQLTransaction.java | 39 ++ .../models/nosql/adapters/RealmAdapter.java | 492 ++++++++++++++++++ .../models/nosql/adapters/RoleAdapter.java | 49 ++ .../models/nosql/adapters/UserAdapter.java | 93 ++++ .../api/AbstractAttributedNoSQLObject.java | 37 ++ .../services/models/nosql/api/NoSQL.java | 6 +- .../services/models/nosql/api/NoSQLQuery.java | 34 ++ .../models/nosql/api/types/Converter.java | 18 + .../models/nosql/api/types/ConverterKey.java | 30 ++ .../models/nosql/api/types/TypeConverter.java | 43 ++ .../services/models/nosql/data/RealmData.java | 144 +++++ .../services/models/nosql/data/RoleData.java | 65 +++ .../services/models/nosql/data/UserData.java | 85 +++ .../models/nosql/impl/MongoDBImpl.java | 73 ++- .../services/models/nosql/impl/Test.java | 53 -- .../BasicDBListToStringArrayConverter.java | 41 ++ .../resources/KeycloakApplication.java | 10 +- 20 files changed, 1356 insertions(+), 115 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java new file mode 100644 index 000000000000..1c82f49f65d9 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java @@ -0,0 +1,43 @@ +package org.keycloak.services.models.nosql.adapters; + +import java.net.UnknownHostException; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.impl.MongoDBImpl; + +/** + * NoSQL implementation based on MongoDB + * + * @author Marek Posolda + */ +public class MongoDBSessionFactory implements KeycloakSessionFactory { + + private final MongoClient mongoClient; + private final NoSQL mongoDB; + + public MongoDBSessionFactory(String host, int port, String dbName) { + try { + // TODO: authentication support + mongoClient = new MongoClient(host, port); + + DB db = mongoClient.getDB(dbName); + mongoDB = new MongoDBImpl(db); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + @Override + public KeycloakSession createSession() { + return new NoSQLSession(mongoDB); + } + + @Override + public void close() { + mongoClient.close(); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java deleted file mode 100644 index c061f8b8c382..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLRealm.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.keycloak.services.models.nosql.adapters; - -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "realms") -public class NoSQLRealm implements NoSQLObject { - - private String oid; - private String prop1; - private Integer prop2; - - @NoSQLId - public String getOid() { - return oid; - } - - public void setOid(String oid) { - this.oid = oid; - } - - @NoSQLField(fieldName = "property1") - public String getProp1() { - return prop1; - } - - public void setProp1(String prop1) { - this.prop1 = prop1; - } - - @NoSQLField(fieldName = "property2") - public Integer getProp2() { - return prop2; - } - - public void setProp2(Integer prop2) { - this.prop2 = prop2; - } - - @Override - public String toString() { - return "NoSQLRealm [ oid=" + oid + ", prop1=" + prop1 + ", prop2=" + prop2 + "]"; - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java new file mode 100644 index 000000000000..10e5413a2e9b --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java @@ -0,0 +1,67 @@ +package org.keycloak.services.models.nosql.adapters; + +import java.util.List; + +import org.jboss.resteasy.spi.NotImplementedYetException; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakTransaction; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.data.RealmData; +import org.keycloak.services.models.nosql.api.NoSQL; + +/** + * @author Marek Posolda + */ +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() { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public RealmModel createRealm(String name) { + RealmData newRealm = new RealmData(); + newRealm.setName(name); + + noSQL.saveObject(newRealm); + + RealmAdapter realm = new RealmAdapter(newRealm, noSQL); + return realm; + } + + @Override + public RealmModel createRealm(String id, String name) { + // Ignore ID for now. It seems that it exists just for workaround picketlink + return createRealm(name); + } + + @Override + public RealmModel getRealm(String id) { + RealmData realmData = noSQL.loadObject(RealmData.class, id); + return new RealmAdapter(realmData, noSQL); + } + + @Override + public List getRealms(UserModel admin) { + throw new NotImplementedYetException(); + } + + @Override + public void deleteRealm(RealmModel realm) { + noSQL.removeObject(RealmData.class, realm.getId()); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java new file mode 100644 index 000000000000..60a1338970a0 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java @@ -0,0 +1,39 @@ +package org.keycloak.services.models.nosql.adapters; + +import org.keycloak.services.models.KeycloakTransaction; + +/** + * @author Marek Posolda + */ +public class NoSQLTransaction implements KeycloakTransaction { + + @Override + public void begin() { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void commit() { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void rollback() { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void setRollbackOnly() { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean getRollbackOnly() { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean isActive() { + return true; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java new file mode 100644 index 000000000000..58f73ef1ca92 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java @@ -0,0 +1,492 @@ +package org.keycloak.services.models.nosql.adapters; + +import java.io.IOException; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openssl.PEMWriter; +import org.jboss.resteasy.security.PemUtils; +import org.keycloak.services.models.ApplicationModel; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.RequiredCredentialModel; +import org.keycloak.services.models.RoleModel; +import org.keycloak.services.models.SocialLinkModel; +import org.keycloak.services.models.UserCredentialModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.NoSQLQuery; +import org.keycloak.services.models.nosql.data.RealmData; +import org.keycloak.services.models.nosql.data.RoleData; +import org.keycloak.services.models.nosql.data.UserData; + +/** + * @author Marek Posolda + */ +public class RealmAdapter implements RealmModel { + + private final RealmData realm; + private final NoSQL noSQL; + + protected volatile transient PublicKey publicKey; + protected volatile transient PrivateKey privateKey; + + public RealmAdapter(RealmData realmData, NoSQL noSQL) { + this.realm = realmData; + this.noSQL = noSQL; + } + + @Override + public String getId() { + return realm.getId(); + } + + @Override + public String getName() { + return realm.getName(); + } + + @Override + public void setName(String name) { + realm.setName(name); + updateRealm(); + } + + @Override + public boolean isEnabled() { + return realm.isEnabled(); + } + + @Override + public void setEnabled(boolean enabled) { + realm.setEnabled(enabled); + updateRealm(); + } + + @Override + public boolean isSocial() { + return realm.isSocial(); + } + + @Override + public void setSocial(boolean social) { + realm.setSocial(social); + updateRealm(); + } + + @Override + public boolean isAutomaticRegistrationAfterSocialLogin() { + return realm.isAutomaticRegistrationAfterSocialLogin(); + } + + @Override + public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) { + realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin); + updateRealm(); + } + + @Override + public boolean isSslNotRequired() { + return realm.isSslNotRequired(); + } + + @Override + public void setSslNotRequired(boolean sslNotRequired) { + realm.setSslNotRequired(sslNotRequired); + updateRealm(); + } + + @Override + public boolean isCookieLoginAllowed() { + return realm.isCookieLoginAllowed(); + } + + @Override + public void setCookieLoginAllowed(boolean cookieLoginAllowed) { + realm.setCookieLoginAllowed(cookieLoginAllowed); + updateRealm(); + } + + @Override + public boolean isRegistrationAllowed() { + return realm.isRegistrationAllowed(); + } + + @Override + public void setRegistrationAllowed(boolean registrationAllowed) { + realm.setRegistrationAllowed(registrationAllowed); + updateRealm(); + } + + @Override + public int getTokenLifespan() { + return realm.getTokenLifespan(); + } + + @Override + public void setTokenLifespan(int tokenLifespan) { + realm.setTokenLifespan(tokenLifespan); + updateRealm(); + } + + @Override + public int getAccessCodeLifespan() { + return realm.getAccessCodeLifespan(); + } + + @Override + public void setAccessCodeLifespan(int accessCodeLifespan) { + realm.setAccessCodeLifespan(accessCodeLifespan); + updateRealm(); + } + + @Override + public String getPublicKeyPem() { + return realm.getPublicKeyPem(); + } + + @Override + public void setPublicKeyPem(String publicKeyPem) { + realm.setPublicKeyPem(publicKeyPem); + this.publicKey = null; + updateRealm(); + } + + @Override + public String getPrivateKeyPem() { + return realm.getPrivateKeyPem(); + } + + @Override + public void setPrivateKeyPem(String privateKeyPem) { + realm.setPrivateKeyPem(privateKeyPem); + this.privateKey = null; + updateRealm(); + } + + @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); + } + } + 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)); + } + + @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); + } + } + 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)); + } + + @Override + public List getRequiredCredentials() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addRequiredCredential(String cred) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean validatePassword(UserModel user, String password) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean validateTOTP(UserModel user, String password, String token) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void updateCredential(UserModel user, UserCredentialModel cred) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public UserModel getUser(String name) { + NoSQLQuery query = NoSQLQuery.create().put("loginName", name).put("realmId", getId()); + UserData user = noSQL.loadSingleObject(UserData.class, query); + + if (user == null) { + return null; + } else { + return new UserAdapter(user, noSQL); + } + } + + @Override + public UserModel addUser(String username) { + if (getUser(username) != null) { + throw new IllegalArgumentException("User " + username + " already exists"); + } + + UserData userData = new UserData(); + userData.setLoginName(username); + userData.setEnabled(true); + userData.setRealmId(getId()); + + noSQL.saveObject(userData); + return new UserAdapter(userData, noSQL); + } + + @Override + public RoleAdapter getRole(String name) { + NoSQLQuery query = NoSQLQuery.create().put("name", name).put("realmId", getId()); + RoleData role = noSQL.loadSingleObject(RoleData.class, query); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public RoleModel addRole(String name) { + if (getRole(name) != null) { + throw new IllegalArgumentException("Role " + name + " already exists"); + } + + RoleData roleData = new RoleData(); + roleData.setName(name); + roleData.setRealmId(getId()); + + noSQL.saveObject(roleData); + return new RoleAdapter(roleData, noSQL); + } + + @Override + public List getRoles() { + NoSQLQuery query = NoSQLQuery.create().put("realmId", getId()); + List roles = noSQL.loadObjects(RoleData.class, query); + + List result = new ArrayList(); + for (RoleData role : roles) { + result.add(new RoleAdapter(role, noSQL)); + } + + return result; + } + + @Override + public List getDefaultRoles() { + List defaultRoleModels = new ArrayList(); + if (realm.getDefaultRoles() != null) { + for (String name : realm.getDefaultRoles()) { + RoleAdapter role = getRole(name); + if (role != null) { + defaultRoleModels.add(role); + } + } + } + return defaultRoleModels; + } + + @Override + public void addDefaultRole(String name) { + if (getRole(name) == null) { + addRole(name); + } + + String[] defaultRoles = realm.getDefaultRoles(); + if (defaultRoles == null) { + defaultRoles = new String[1]; + } else { + defaultRoles = Arrays.copyOf(defaultRoles, defaultRoles.length + 1); + } + defaultRoles[defaultRoles.length - 1] = name; + + realm.setDefaultRoles(defaultRoles); + updateRealm(); + } + + @Override + public void updateDefaultRoles(String[] defaultRoles) { + for (String name : defaultRoles) { + if (getRole(name) == null) { + addRole(name); + } + } + + realm.setDefaultRoles(defaultRoles); + updateRealm(); + } + + @Override + public Map getResourceNameMap() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public List getApplications() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public ApplicationModel addApplication(String name) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean hasRole(UserModel user, RoleModel role) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void grantRole(UserModel user, RoleModel role) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public Set getRoleMappings(UserModel user) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addScope(UserModel agent, String roleName) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public Set getScope(UserModel agent) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean isRealmAdmin(UserModel agent) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addRealmAdmin(UserModel agent) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public RoleModel getRoleById(String id) { + RoleData role = noSQL.loadObject(RoleData.class, id); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public List getRequiredApplicationCredentials() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public List getRequiredOAuthClientCredentials() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean hasRole(UserModel user, String role) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public ApplicationModel getApplicationById(String id) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addRequiredOAuthClientCredential(String type) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addRequiredResourceCredential(String type) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void updateRequiredCredentials(Set creds) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void updateRequiredOAuthClientCredentials(Set creds) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void updateRequiredApplicationCredentials(Set creds) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public UserModel getUserBySocialLink(SocialLinkModel socialLink) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public Set getSocialLinks(UserModel user) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void addSocialLink(UserModel user, SocialLinkModel socialLink) { + //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { + //To change body of implemented methods use File | Settings | File Templates. + } + + protected void updateRealm() { + noSQL.saveObject(realm); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java new file mode 100644 index 000000000000..edde2e40bd06 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java @@ -0,0 +1,49 @@ +package org.keycloak.services.models.nosql.adapters; + +import org.keycloak.services.models.RoleModel; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.data.RoleData; +import org.keycloak.services.models.nosql.data.UserData; + +/** + * Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) + * + * @author Marek Posolda + */ +public class RoleAdapter implements RoleModel { + + private final RoleData role; + private final NoSQL noSQL; + + public RoleAdapter(RoleData roleData, NoSQL noSQL) { + this.role = roleData; + this.noSQL = noSQL; + } + + @Override + public String getName() { + return role.getName(); + } + + @Override + public String getDescription() { + return role.getDescription(); + } + + @Override + public void setDescription(String description) { + role.setDescription(description); + noSQL.saveObject(role); + } + + @Override + public String getId() { + return role.getId(); + } + + @Override + public void setName(String name) { + role.setName(name); + noSQL.saveObject(role); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java new file mode 100644 index 000000000000..781ba266c8f2 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java @@ -0,0 +1,93 @@ +package org.keycloak.services.models.nosql.adapters; + +import java.util.Map; + +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.data.UserData; + +/** + * Wrapper around UserData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) + * + * @author Marek Posolda + */ +public class UserAdapter implements UserModel { + + private final UserData user; + private final NoSQL noSQL; + + public UserAdapter(UserData userData, NoSQL noSQL) { + this.user = userData; + this.noSQL = noSQL; + } + + @Override + public String getLoginName() { + return user.getLoginName(); + } + + @Override + public boolean isEnabled() { + return user.isEnabled(); + } + + @Override + public void setEnabled(boolean enabled) { + user.setEnabled(enabled); + noSQL.saveObject(user); + } + + @Override + public String getFirstName() { + return user.getFirstName(); + } + + @Override + public void setFirstName(String firstName) { + user.setFirstName(firstName); + noSQL.saveObject(user); + } + + @Override + public String getLastName() { + return user.getLastName(); + } + + @Override + public void setLastName(String lastName) { + user.setLastName(lastName); + noSQL.saveObject(user); + } + + @Override + public String getEmail() { + return user.getEmail(); + } + + @Override + public void setEmail(String email) { + user.setEmail(email); + noSQL.saveObject(user); + } + + @Override + public void setAttribute(String name, String value) { + user.setAttribute(name, value); + } + + @Override + public void removeAttribute(String name) { + user.removeAttribute(name); + noSQL.saveObject(user); + } + + @Override + public String getAttribute(String name) { + return user.getAttribute(name); + } + + @Override + public Map getAttributes() { + return user.getAttributes(); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java new file mode 100644 index 000000000000..efdddee67c7a --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java @@ -0,0 +1,37 @@ +package org.keycloak.services.models.nosql.api; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public abstract class AbstractAttributedNoSQLObject implements AttributedNoSQLObject { + + // Simple hashMap for now (no thread-safe) + private Map attributes = new HashMap(); + + @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 getAttributes() { + return Collections.unmodifiableMap(attributes); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java index dc16bb6ec188..9da8166286ed 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java @@ -15,12 +15,14 @@ public interface NoSQL { T loadObject(Class type, String oid); - List loadObjects(Class type, Map queryAttributes); + T loadSingleObject(Class type, NoSQLQuery query); + + List loadObjects(Class type, NoSQLQuery query); // Object must have filled oid void removeObject(NoSQLObject object); void removeObject(Class type, String oid); - void removeObjects(Class type, Map queryAttributes); + void removeObjects(Class type, NoSQLQuery query); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java new file mode 100644 index 000000000000..d26064400c92 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java @@ -0,0 +1,34 @@ +package org.keycloak.services.models.nosql.api; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class NoSQLQuery { + + private Map queryAttributes = new HashMap(); + + private NoSQLQuery() {}; + + public static NoSQLQuery create() { + return new NoSQLQuery(); + } + + public NoSQLQuery put(String name, Object value) { + queryAttributes.put(name, value); + return this; + } + + public Map getQueryAttributes() { + return Collections.unmodifiableMap(queryAttributes); + } + + @Override + public String toString() { + return "NoSQLQuery [" + queryAttributes + "]"; + } + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java new file mode 100644 index 000000000000..221db2b7a93c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java @@ -0,0 +1,18 @@ +package org.keycloak.services.models.nosql.api.types; + +/** + * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application. + * Various converters should be registered in TypeConverter, which is main entry point to be used by application + * + * @author Marek Posolda + */ +public interface Converter { + + T convertDBObjectToApplicationObject(S dbObject); + + S convertApplicationObjectToDBObject(T applicationObject); + + Class getApplicationObjectType(); + + Class getDBObjectType(); +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java new file mode 100644 index 000000000000..f4b6fe0cdf05 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java @@ -0,0 +1,30 @@ +package org.keycloak.services.models.nosql.api.types; + +/** + * @author Marek Posolda + */ +class ConverterKey { + + private final Class applicationObjectType; + private final Class dbObjectType; + + public ConverterKey(Class applicationObjectType, Class dbObjectType) { + this.applicationObjectType = applicationObjectType; + this.dbObjectType = dbObjectType; + } + + @Override + public int hashCode() { + return applicationObjectType.hashCode() * 13 + dbObjectType.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !obj.getClass().equals(this.getClass())) { + return false; + } + + ConverterKey tc = (ConverterKey)obj; + return tc.applicationObjectType.equals(this.applicationObjectType) && tc.dbObjectType.equals(this.dbObjectType); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java new file mode 100644 index 000000000000..7bdb384625c1 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java @@ -0,0 +1,43 @@ +package org.keycloak.services.models.nosql.api.types; + +import java.util.HashMap; +import java.util.Map; + +/** + * Registry of converters, which allow to convert application object to database objects. TypeConverter is main entry point to be used by application. + * Application can create instance of TypeConverter and then register required Converter objects. + * + * @author Marek Posolda + */ +public class TypeConverter { + + private Map> converterRegistry = new HashMap>(); + + public void addConverter(Converter converter) { + ConverterKey converterKey = new ConverterKey(converter.getApplicationObjectType(), converter.getDBObjectType()); + converterRegistry.put(converterKey, converter); + } + + public T convertDBObjectToApplicationObject(S dbObject, Class expectedApplicationObjectType, Class expectedDBObjectType) { + Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); + return converter.convertDBObjectToApplicationObject(dbObject); + } + + public S convertApplicationObjectToDBObject(T applicationobject, Class expectedApplicationObjectType, Class expectedDBObjectType) { + Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); + return converter.convertApplicationObjectToDBObject(applicationobject); + } + + private Converter getConverter( Class expectedApplicationObjectType, Class expectedDBObjectType) { + ConverterKey key = new ConverterKey(expectedApplicationObjectType, expectedDBObjectType); + Converter converter = (Converter)converterRegistry.get(key); + + if (converter == null) { + throw new IllegalStateException("Can't found converter for expectedApplicationObject=" + expectedApplicationObjectType + ", expectedDBObjectType=" + expectedDBObjectType); + } + + return converter; + } + + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java new file mode 100644 index 000000000000..8e6a663630d8 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java @@ -0,0 +1,144 @@ +package org.keycloak.services.models.nosql.data; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "realms") +public class RealmData implements NoSQLObject { + + private String id; + private String name; + private boolean enabled; + private boolean sslNotRequired; + private boolean cookieLoginAllowed; + private boolean registrationAllowed; + private boolean social; + private boolean automaticRegistrationAfterSocialLogin; + private int tokenLifespan; + private int accessCodeLifespan; + private String publicKeyPem; + private String privateKeyPem; + private String[] defaultRoles; + + @NoSQLId + 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 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 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 String[] getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(String[] defaultRoles) { + this.defaultRoles = defaultRoles; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java b/services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java new file mode 100644 index 000000000000..7d4ba4b3c2ca --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java @@ -0,0 +1,65 @@ +package org.keycloak.services.models.nosql.data; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "roles") +public class RoleData implements NoSQLObject { + + 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; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java new file mode 100644 index 000000000000..70aab2a06eb0 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java @@ -0,0 +1,85 @@ +package org.keycloak.services.models.nosql.data; + +import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "users") +public class UserData extends AbstractAttributedNoSQLObject { + + private String id; + private String loginName; + private String firstName; + private String lastName; + private String email; + private boolean enabled; + + private String realmId; + + @NoSQLId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @NoSQLField + public String getLoginName() { + return loginName; + } + + public void setLoginName(String loginName) { + this.loginName = loginName; + } + + @NoSQLField + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + @NoSQLField + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @NoSQLField + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @NoSQLField + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @NoSQLField + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index bbdd84685c7f..7d61b85f7daa 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -13,6 +13,7 @@ import com.mongodb.DBObject; import org.bson.types.ObjectId; import org.jboss.resteasy.logging.Logger; +import org.jboss.resteasy.spi.NotImplementedYetException; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQL; @@ -20,9 +21,14 @@ import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLQuery; +import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.services.models.nosql.api.types.TypeConverter; +import org.keycloak.services.models.nosql.impl.types.BasicDBListToStringArrayConverter; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; +import org.picketlink.common.reflection.Types; /** * @author Marek Posolda @@ -32,14 +38,20 @@ public class MongoDBImpl implements NoSQL { private final DB database; // private static final Logger logger = Logger.getLogger(MongoDBImpl.class); + private final TypeConverter typeConverter; + public MongoDBImpl(DB database) { this.database = database; + + typeConverter = new TypeConverter(); + typeConverter.addConverter(new BasicDBListToStringArrayConverter()); } private ConcurrentMap, ObjectInfo> objectInfoCache = new ConcurrentHashMap, ObjectInfo>(); + @Override public void saveObject(NoSQLObject object) { Class clazz = object.getClass(); @@ -96,32 +108,62 @@ public T loadObject(Class type, String oid) { } @Override - public List loadObjects(Class type, Map queryAttributes) { + public T loadSingleObject(Class type, NoSQLQuery query) { + List 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"); + } else if (result.size() == 1) { + return result.get(0); + } else { + // 0 results + return null; + } + } + + @Override + public List loadObjects(Class type, NoSQLQuery query) { + Map queryAttributes = query.getQueryAttributes(); + ObjectInfo objectInfo = getObjectInfo(type); DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); - BasicDBObject query = new BasicDBObject(); + BasicDBObject dbQuery = new BasicDBObject(); for (Map.Entry queryAttr : queryAttributes.entrySet()) { - query.append(queryAttr.getKey(), queryAttr.getValue()); + dbQuery.append(queryAttr.getKey(), queryAttr.getValue()); } - DBCursor cursor = dbCollection.find(query); + DBCursor cursor = dbCollection.find(dbQuery); return convertCursor(type, cursor); } @Override public void removeObject(NoSQLObject object) { - //To change body of implemented methods use File | Settings | File Templates. + Class type = object.getClass(); + ObjectInfo objectInfo = getObjectInfo(type); + + Property idProperty = objectInfo.getOidProperty(); + String oid = idProperty.getValue(object); + + removeObject(type, oid); } @Override public void removeObject(Class type, String oid) { - //To change body of implemented methods use File | Settings | File Templates. + ObjectInfo objectInfo = getObjectInfo(type); + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + BasicDBObject query = new BasicDBObject("_id", new ObjectId(oid)); + dbCollection.remove(query); } @Override - public void removeObjects(Class type, Map queryAttributes) { - //To change body of implemented methods use File | Settings | File Templates. + public void removeObjects(Class type, NoSQLQuery query) { + throw new NotImplementedYetException(); + } + + // Possibility to add converters + public void addConverter(Converter converter) { + typeConverter.addConverter(converter); } private ObjectInfo getObjectInfo(Class objectClass) { @@ -173,7 +215,20 @@ private T convertObject(Class type, DBObject dbObject } else if ((property = objectInfo.getPropertyByName(key)) != null) { // It's declared property with @DBField annotation - property.setValue(object, value); + Class expectedType = property.getJavaClass(); + Class actualType = value != null ? value.getClass() : expectedType; + + // handle primitives + expectedType = Types.boxedClass(expectedType); + actualType = Types.boxedClass(actualType); + + if (actualType.isAssignableFrom(expectedType)) { + property.setValue(object, value); + } else { + // we need to convert + Object convertedValue = typeConverter.convertDBObjectToApplicationObject(value, expectedType, actualType); + property.setValue(object, convertedValue); + } } else if (object instanceof AttributedNoSQLObject) { // It's attributed object and property is not declared, so we will call setAttribute diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java deleted file mode 100644 index 375418ab41f5..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/Test.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.keycloak.services.models.nosql.impl; - -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.mongodb.DB; -import com.mongodb.MongoClient; -import org.keycloak.services.models.nosql.adapters.NoSQLRealm; - -/** - * TODO: delete - * - * @author Marek Posolda - */ -public class Test { - - public static void main(String[] args) throws UnknownHostException { - MongoClient mongoClient = new MongoClient( "localhost" , 27017 ); - DB javaDB = mongoClient.getDB("java"); - - MongoDBImpl test = new MongoDBImpl(javaDB); - NoSQLRealm realm = new NoSQLRealm(); - realm.setOid("522085fc31dab908ec31c0cb"); - realm.setProp1("something1"); - realm.setProp2(12); - test.saveObject(realm); - System.out.println(realm.getOid()); - - realm = test.loadObject(NoSQLRealm.class, "522085fc31dab908ec31c0cb"); - System.out.println("Loaded realm: " + realm); - - Map query = new HashMap(); - query.put("prop1", "sm"); - List queryResults = test.loadObjects(NoSQLRealm.class, query); - System.out.println("results1: " + queryResults); - - query.put("prop1", "something2"); - queryResults = test.loadObjects(NoSQLRealm.class, query); - System.out.println("results2: " + queryResults); - - query.put("prop2", 12); - queryResults = test.loadObjects(NoSQLRealm.class, query); - System.out.println("results3: " + queryResults); - - query.put("prop1", "something1"); - queryResults = test.loadObjects(NoSQLRealm.class, query); - System.out.println("results4: " + queryResults); - - mongoClient.close(); - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java new file mode 100644 index 000000000000..0f9d5d59172c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java @@ -0,0 +1,41 @@ +package org.keycloak.services.models.nosql.impl.types; + +import com.mongodb.BasicDBList; +import org.keycloak.services.models.nosql.api.types.Converter; + +/** + * Convert BasicDBList to String[] and viceversa (T needs to be declared as Object as Array is not possible here :/ ) + * + * @author Marek Posolda + */ +public class BasicDBListToStringArrayConverter implements Converter { + + private static final String[] PLACEHOLDER = new String[] {}; + + @Override + public Object convertDBObjectToApplicationObject(BasicDBList dbObject) { + return dbObject.toArray(PLACEHOLDER); + } + + @Override + public BasicDBList convertApplicationObjectToDBObject(Object applicationObject) { + BasicDBList list = new BasicDBList(); + + String[] array = (String[])applicationObject; + for (String key : array) { + list.add(key); + } + + return list; + } + + @Override + public Class getApplicationObjectType() { + return PLACEHOLDER.getClass(); + } + + @Override + public Class getDBObjectType() { + return BasicDBList.class; + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 49855b9af73e..089ab3998f94 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -7,6 +7,11 @@ import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory; import org.keycloak.models.picketlink.mappings.ApplicationEntity; import org.keycloak.models.picketlink.mappings.RealmEntity; +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.models.nosql.adapters.MongoDBSessionFactory; +import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; +import org.keycloak.services.models.picketlink.mappings.ApplicationEntity; +import org.keycloak.services.models.picketlink.mappings.RealmEntity; import org.keycloak.social.SocialRequestManager; import org.picketlink.idm.PartitionManager; import org.picketlink.idm.config.IdentityConfigurationBuilder; @@ -54,8 +59,9 @@ protected KeycloakSessionFactory createSessionFactory() { } public static KeycloakSessionFactory buildSessionFactory() { - EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store"); - return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager()); + // EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store"); + // return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager()); + return new MongoDBSessionFactory("localhost", 27017, "keycloak"); } public KeycloakSessionFactory getFactory() { From 5b8908c8225c1ea785acc439ec737a28b7009e91 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 2 Sep 2013 11:43:43 +0200 Subject: [PATCH 03/14] Added NoSQLQueryBuilder API. Support for persistence of all objects. All unit tests are passing and UI is working with MongoDB --- .../nosql/adapters/MongoDBSessionFactory.java | 43 - .../models/nosql/adapters/NoSQLSession.java | 67 -- .../models/nosql/adapters/NoSQLUser.java | 46 -- .../models/nosql/adapters/RealmAdapter.java | 492 ------------ .../services/models/nosql/api/NoSQL.java | 3 +- .../nosql/api/{ => query}/NoSQLQuery.java | 17 +- .../nosql/api/query/NoSQLQueryBuilder.java | 38 + .../models/nosql/impl/MongoDBImpl.java | 65 +- .../nosql/impl/MongoDBQueryBuilder.java | 33 + .../services/models/nosql/impl/Utils.java | 56 ++ .../keycloak/adapters/ApplicationAdapter.java | 199 +++++ .../adapters/MongoDBSessionFactory.java | 74 ++ .../nosql/keycloak/adapters/NoSQLSession.java | 85 ++ .../adapters/NoSQLTransaction.java | 2 +- .../nosql/keycloak/adapters/RealmAdapter.java | 751 ++++++++++++++++++ .../{ => keycloak}/adapters/RoleAdapter.java | 9 +- .../{ => keycloak}/adapters/UserAdapter.java | 8 +- .../PasswordCredentialHandler.java | 155 ++++ .../credentials/TOTPCredentialHandler.java | 11 + .../nosql/keycloak/data/ApplicationData.java | 85 ++ .../nosql/{ => keycloak}/data/RealmData.java | 30 +- .../keycloak/data/RequiredCredentialData.java | 90 +++ .../nosql/{ => keycloak}/data/RoleData.java | 2 +- .../nosql/keycloak/data/SocialLinkData.java | 54 ++ .../nosql/{ => keycloak}/data/UserData.java | 23 +- .../data/credentials/PasswordData.java | 77 ++ .../resources/KeycloakApplication.java | 7 +- 27 files changed, 1823 insertions(+), 699 deletions(-) delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java rename services/src/main/java/org/keycloak/services/models/nosql/api/{ => query}/NoSQLQuery.java (53%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/adapters/NoSQLTransaction.java (94%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/adapters/RoleAdapter.java (85%) rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/adapters/UserAdapter.java (91%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/data/RealmData.java (84%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/data/RoleData.java (95%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java rename services/src/main/java/org/keycloak/services/models/nosql/{ => keycloak}/data/UserData.java (79%) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java deleted file mode 100644 index 1c82f49f65d9..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/MongoDBSessionFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.keycloak.services.models.nosql.adapters; - -import java.net.UnknownHostException; - -import com.mongodb.DB; -import com.mongodb.MongoClient; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakSessionFactory; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.impl.MongoDBImpl; - -/** - * NoSQL implementation based on MongoDB - * - * @author Marek Posolda - */ -public class MongoDBSessionFactory implements KeycloakSessionFactory { - - private final MongoClient mongoClient; - private final NoSQL mongoDB; - - public MongoDBSessionFactory(String host, int port, String dbName) { - try { - // TODO: authentication support - mongoClient = new MongoClient(host, port); - - DB db = mongoClient.getDB(dbName); - mongoDB = new MongoDBImpl(db); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } - - @Override - public KeycloakSession createSession() { - return new NoSQLSession(mongoDB); - } - - @Override - public void close() { - mongoClient.close(); - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java deleted file mode 100644 index 10e5413a2e9b..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLSession.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.keycloak.services.models.nosql.adapters; - -import java.util.List; - -import org.jboss.resteasy.spi.NotImplementedYetException; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakTransaction; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.data.RealmData; -import org.keycloak.services.models.nosql.api.NoSQL; - -/** - * @author Marek Posolda - */ -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() { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public RealmModel createRealm(String name) { - RealmData newRealm = new RealmData(); - newRealm.setName(name); - - noSQL.saveObject(newRealm); - - RealmAdapter realm = new RealmAdapter(newRealm, noSQL); - return realm; - } - - @Override - public RealmModel createRealm(String id, String name) { - // Ignore ID for now. It seems that it exists just for workaround picketlink - return createRealm(name); - } - - @Override - public RealmModel getRealm(String id) { - RealmData realmData = noSQL.loadObject(RealmData.class, id); - return new RealmAdapter(realmData, noSQL); - } - - @Override - public List getRealms(UserModel admin) { - throw new NotImplementedYetException(); - } - - @Override - public void deleteRealm(RealmModel realm) { - noSQL.removeObject(RealmData.class, realm.getId()); - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java deleted file mode 100644 index 5c316fff4b32..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLUser.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.keycloak.services.models.nosql.adapters; - -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "users") -public class NoSQLUser { - - @NoSQLId - private String oid; - - private String username; - - private String realmId; - - @NoSQLId - public String getOid() { - return oid; - } - - public void setOid(String oid) { - this.oid = oid; - } - - @NoSQLField - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - @NoSQLField(fieldName = "realm_id") - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java deleted file mode 100644 index 58f73ef1ca92..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RealmAdapter.java +++ /dev/null @@ -1,492 +0,0 @@ -package org.keycloak.services.models.nosql.adapters; - -import java.io.IOException; -import java.io.StringWriter; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.bouncycastle.openssl.PEMWriter; -import org.jboss.resteasy.security.PemUtils; -import org.keycloak.services.models.ApplicationModel; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.RequiredCredentialModel; -import org.keycloak.services.models.RoleModel; -import org.keycloak.services.models.SocialLinkModel; -import org.keycloak.services.models.UserCredentialModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLQuery; -import org.keycloak.services.models.nosql.data.RealmData; -import org.keycloak.services.models.nosql.data.RoleData; -import org.keycloak.services.models.nosql.data.UserData; - -/** - * @author Marek Posolda - */ -public class RealmAdapter implements RealmModel { - - private final RealmData realm; - private final NoSQL noSQL; - - protected volatile transient PublicKey publicKey; - protected volatile transient PrivateKey privateKey; - - public RealmAdapter(RealmData realmData, NoSQL noSQL) { - this.realm = realmData; - this.noSQL = noSQL; - } - - @Override - public String getId() { - return realm.getId(); - } - - @Override - public String getName() { - return realm.getName(); - } - - @Override - public void setName(String name) { - realm.setName(name); - updateRealm(); - } - - @Override - public boolean isEnabled() { - return realm.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - realm.setEnabled(enabled); - updateRealm(); - } - - @Override - public boolean isSocial() { - return realm.isSocial(); - } - - @Override - public void setSocial(boolean social) { - realm.setSocial(social); - updateRealm(); - } - - @Override - public boolean isAutomaticRegistrationAfterSocialLogin() { - return realm.isAutomaticRegistrationAfterSocialLogin(); - } - - @Override - public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) { - realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin); - updateRealm(); - } - - @Override - public boolean isSslNotRequired() { - return realm.isSslNotRequired(); - } - - @Override - public void setSslNotRequired(boolean sslNotRequired) { - realm.setSslNotRequired(sslNotRequired); - updateRealm(); - } - - @Override - public boolean isCookieLoginAllowed() { - return realm.isCookieLoginAllowed(); - } - - @Override - public void setCookieLoginAllowed(boolean cookieLoginAllowed) { - realm.setCookieLoginAllowed(cookieLoginAllowed); - updateRealm(); - } - - @Override - public boolean isRegistrationAllowed() { - return realm.isRegistrationAllowed(); - } - - @Override - public void setRegistrationAllowed(boolean registrationAllowed) { - realm.setRegistrationAllowed(registrationAllowed); - updateRealm(); - } - - @Override - public int getTokenLifespan() { - return realm.getTokenLifespan(); - } - - @Override - public void setTokenLifespan(int tokenLifespan) { - realm.setTokenLifespan(tokenLifespan); - updateRealm(); - } - - @Override - public int getAccessCodeLifespan() { - return realm.getAccessCodeLifespan(); - } - - @Override - public void setAccessCodeLifespan(int accessCodeLifespan) { - realm.setAccessCodeLifespan(accessCodeLifespan); - updateRealm(); - } - - @Override - public String getPublicKeyPem() { - return realm.getPublicKeyPem(); - } - - @Override - public void setPublicKeyPem(String publicKeyPem) { - realm.setPublicKeyPem(publicKeyPem); - this.publicKey = null; - updateRealm(); - } - - @Override - public String getPrivateKeyPem() { - return realm.getPrivateKeyPem(); - } - - @Override - public void setPrivateKeyPem(String privateKeyPem) { - realm.setPrivateKeyPem(privateKeyPem); - this.privateKey = null; - updateRealm(); - } - - @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); - } - } - 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)); - } - - @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); - } - } - 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)); - } - - @Override - public List getRequiredCredentials() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addRequiredCredential(String cred) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean validatePassword(UserModel user, String password) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean validateTOTP(UserModel user, String password, String token) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void updateCredential(UserModel user, UserCredentialModel cred) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public UserModel getUser(String name) { - NoSQLQuery query = NoSQLQuery.create().put("loginName", name).put("realmId", getId()); - UserData user = noSQL.loadSingleObject(UserData.class, query); - - if (user == null) { - return null; - } else { - return new UserAdapter(user, noSQL); - } - } - - @Override - public UserModel addUser(String username) { - if (getUser(username) != null) { - throw new IllegalArgumentException("User " + username + " already exists"); - } - - UserData userData = new UserData(); - userData.setLoginName(username); - userData.setEnabled(true); - userData.setRealmId(getId()); - - noSQL.saveObject(userData); - return new UserAdapter(userData, noSQL); - } - - @Override - public RoleAdapter getRole(String name) { - NoSQLQuery query = NoSQLQuery.create().put("name", name).put("realmId", getId()); - RoleData role = noSQL.loadSingleObject(RoleData.class, query); - if (role == null) { - return null; - } else { - return new RoleAdapter(role, noSQL); - } - } - - @Override - public RoleModel addRole(String name) { - if (getRole(name) != null) { - throw new IllegalArgumentException("Role " + name + " already exists"); - } - - RoleData roleData = new RoleData(); - roleData.setName(name); - roleData.setRealmId(getId()); - - noSQL.saveObject(roleData); - return new RoleAdapter(roleData, noSQL); - } - - @Override - public List getRoles() { - NoSQLQuery query = NoSQLQuery.create().put("realmId", getId()); - List roles = noSQL.loadObjects(RoleData.class, query); - - List result = new ArrayList(); - for (RoleData role : roles) { - result.add(new RoleAdapter(role, noSQL)); - } - - return result; - } - - @Override - public List getDefaultRoles() { - List defaultRoleModels = new ArrayList(); - if (realm.getDefaultRoles() != null) { - for (String name : realm.getDefaultRoles()) { - RoleAdapter role = getRole(name); - if (role != null) { - defaultRoleModels.add(role); - } - } - } - return defaultRoleModels; - } - - @Override - public void addDefaultRole(String name) { - if (getRole(name) == null) { - addRole(name); - } - - String[] defaultRoles = realm.getDefaultRoles(); - if (defaultRoles == null) { - defaultRoles = new String[1]; - } else { - defaultRoles = Arrays.copyOf(defaultRoles, defaultRoles.length + 1); - } - defaultRoles[defaultRoles.length - 1] = name; - - realm.setDefaultRoles(defaultRoles); - updateRealm(); - } - - @Override - public void updateDefaultRoles(String[] defaultRoles) { - for (String name : defaultRoles) { - if (getRole(name) == null) { - addRole(name); - } - } - - realm.setDefaultRoles(defaultRoles); - updateRealm(); - } - - @Override - public Map getResourceNameMap() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public List getApplications() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ApplicationModel addApplication(String name) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean hasRole(UserModel user, RoleModel role) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void grantRole(UserModel user, RoleModel role) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public Set getRoleMappings(UserModel user) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addScope(UserModel agent, String roleName) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public Set getScope(UserModel agent) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean isRealmAdmin(UserModel agent) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addRealmAdmin(UserModel agent) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public RoleModel getRoleById(String id) { - RoleData role = noSQL.loadObject(RoleData.class, id); - if (role == null) { - return null; - } else { - return new RoleAdapter(role, noSQL); - } - } - - @Override - public List getRequiredApplicationCredentials() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public List getRequiredOAuthClientCredentials() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean hasRole(UserModel user, String role) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ApplicationModel getApplicationById(String id) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addRequiredOAuthClientCredential(String type) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addRequiredResourceCredential(String type) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void updateRequiredCredentials(Set creds) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void updateRequiredOAuthClientCredentials(Set creds) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void updateRequiredApplicationCredentials(Set creds) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public UserModel getUserBySocialLink(SocialLinkModel socialLink) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public Set getSocialLinks(UserModel user) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addSocialLink(UserModel user, SocialLinkModel socialLink) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { - //To change body of implemented methods use File | Settings | File Templates. - } - - protected void updateRealm() { - noSQL.saveObject(realm); - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java index 9da8166286ed..c3081b05a931 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java @@ -1,7 +1,8 @@ package org.keycloak.services.models.nosql.api; import java.util.List; -import java.util.Map; + +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java similarity index 53% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java rename to services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java index d26064400c92..90be20142066 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLQuery.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.services.models.nosql.api.query; import java.util.Collections; import java.util.HashMap; @@ -9,18 +9,11 @@ */ public class NoSQLQuery { - private Map queryAttributes = new HashMap(); + private final Map queryAttributes; - private NoSQLQuery() {}; - - public static NoSQLQuery create() { - return new NoSQLQuery(); - } - - public NoSQLQuery put(String name, Object value) { - queryAttributes.put(name, value); - return this; - } + NoSQLQuery(Map queryAttributes) { + this.queryAttributes = queryAttributes; + }; public Map getQueryAttributes() { return Collections.unmodifiableMap(queryAttributes); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java new file mode 100644 index 000000000000..aa4eb48e522f --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java @@ -0,0 +1,38 @@ +package org.keycloak.services.models.nosql.api.query; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public abstract class NoSQLQueryBuilder { + + private Map queryAttributes = new HashMap(); + + protected NoSQLQueryBuilder() {}; + + public static NoSQLQueryBuilder create(Class builderClass) { + try { + return builderClass.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + 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, Object[] values); + + protected void put(String name, Object value) { + queryAttributes.put(name, value); + } + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index 7d61b85f7daa..fd6efa7627f9 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -12,16 +12,13 @@ import com.mongodb.DBCursor; import com.mongodb.DBObject; import org.bson.types.ObjectId; -import org.jboss.resteasy.logging.Logger; -import org.jboss.resteasy.spi.NotImplementedYetException; -import org.keycloak.services.managers.RealmManager; import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; import org.keycloak.services.models.nosql.api.types.Converter; import org.keycloak.services.models.nosql.api.types.TypeConverter; import org.keycloak.services.models.nosql.impl.types.BasicDBListToStringArrayConverter; @@ -68,14 +65,14 @@ public void saveObject(NoSQLObject object) { dbObject.append(propName, propValue); + } - // Adding attributes - if (object instanceof AttributedNoSQLObject) { - AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object; - Map attributes = attributedObject.getAttributes(); - for (Map.Entry attribute : attributes.entrySet()) { - dbObject.append(attribute.getKey(), attribute.getValue()); - } + // Adding attributes + if (object instanceof AttributedNoSQLObject) { + AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object; + Map attributes = attributedObject.getAttributes(); + for (Map.Entry attribute : attributes.entrySet()) { + dbObject.append(attribute.getKey(), attribute.getValue()); } } @@ -98,8 +95,7 @@ public void saveObject(NoSQLObject object) { @Override public T loadObject(Class type, String oid) { - ObjectInfo objectInfo = getObjectInfo(type); - DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + DBCollection dbCollection = getDBCollectionForType(type); BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid)); DBObject dbObject = dbCollection.findOne(idQuery); @@ -122,15 +118,9 @@ public T loadSingleObject(Class type, NoSQLQuery quer @Override public List loadObjects(Class type, NoSQLQuery query) { - Map queryAttributes = query.getQueryAttributes(); - - ObjectInfo objectInfo = getObjectInfo(type); - DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + DBCollection dbCollection = getDBCollectionForType(type); + BasicDBObject dbQuery = getDBQueryFromQuery(query); - BasicDBObject dbQuery = new BasicDBObject(); - for (Map.Entry queryAttr : queryAttributes.entrySet()) { - dbQuery.append(queryAttr.getKey(), queryAttr.getValue()); - } DBCursor cursor = dbCollection.find(dbQuery); return convertCursor(type, cursor); @@ -149,19 +139,21 @@ public void removeObject(NoSQLObject object) { @Override public void removeObject(Class type, String oid) { - ObjectInfo objectInfo = getObjectInfo(type); - DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject query = new BasicDBObject("_id", new ObjectId(oid)); - dbCollection.remove(query); + BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid)); + dbCollection.remove(dbQuery); } @Override public void removeObjects(Class type, NoSQLQuery query) { - throw new NotImplementedYetException(); + DBCollection dbCollection = getDBCollectionForType(type); + BasicDBObject dbQuery = getDBQueryFromQuery(query); + + dbCollection.remove(dbQuery); } - // Possibility to add converters + // Possibility to add user-defined converters public void addConverter(Converter converter) { typeConverter.addConverter(converter); } @@ -171,6 +163,7 @@ private ObjectInfo getObjectInfo(Class objectClass if (objectInfo == null) { Property idProperty = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult(); if (idProperty == null) { + // TODO: should be allowed to have NoSQLObject classes without declared NoSQLId annotation? throw new IllegalStateException("Class " + objectClass + " doesn't have property with declared annotation " + NoSQLId.class); } @@ -195,6 +188,10 @@ private ObjectInfo getObjectInfo(Class objectClass private T convertObject(Class type, DBObject dbObject) { + if (dbObject == null) { + return null; + } + ObjectInfo objectInfo = getObjectInfo(type); T object; @@ -255,4 +252,18 @@ private List convertCursor(Class type, DBCursor cu return result; } + + private DBCollection getDBCollectionForType(Class type) { + ObjectInfo objectInfo = getObjectInfo(type); + return database.getCollection(objectInfo.getDbCollectionName()); + } + + private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) { + Map queryAttributes = query.getQueryAttributes(); + BasicDBObject dbQuery = new BasicDBObject(); + for (Map.Entry queryAttr : queryAttributes.entrySet()) { + dbQuery.append(queryAttr.getKey(), queryAttr.getValue()); + } + return dbQuery; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java new file mode 100644 index 000000000000..b3af7324eb2d --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java @@ -0,0 +1,33 @@ +package org.keycloak.services.models.nosql.impl; + +import com.mongodb.BasicDBObject; +import org.bson.types.ObjectId; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; + +/** + * @author Marek Posolda + */ +public class MongoDBQueryBuilder extends NoSQLQueryBuilder { + + @Override + public NoSQLQueryBuilder inCondition(String name, Object[] values) { + if (values == null) { + values = new Object[0]; + } + + if ("_id".equals(name)) { + // we need to convert Strings to ObjectID + ObjectId[] objIds = new ObjectId[values.length]; + for (int i=0 ; iMarek Posolda + */ +public class Utils { + + private Utils() {}; + + /** + * Add item to the end of array + * + * @param inputArray could be null. In this case, method will return array of length 1 with added item + * @param item must be not-null + * @return array with added item to the end + */ + public static T[] addItemToArray(T[] inputArray, T item) { + if (item == null) { + throw new IllegalArgumentException("item must be non-null"); + } + + T[] outputArray; + if (inputArray == null) { + outputArray = (T[])Array.newInstance(item.getClass(), 1); + } else { + outputArray = Arrays.copyOf(inputArray, inputArray.length + 1); + } + outputArray[outputArray.length - 1] = item; + return outputArray; + } + + /** + * Return true if array contains specified item + * @param array could be null (In this case method always return false) + * @param item can't be null + * @return + */ + public static boolean contains(Object[] array, Object item) { + if (item == null) { + throw new IllegalArgumentException("item must be non-null"); + } + + if (array != null) { + for (Object current : array) { + if (item.equals(current)) { + return true; + } + } + } + return false; + } + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java new file mode 100644 index 000000000000..fc8a6e177f56 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java @@ -0,0 +1,199 @@ +package org.keycloak.services.models.nosql.keycloak.adapters; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.keycloak.services.models.ApplicationModel; +import org.keycloak.services.models.RoleModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.impl.Utils; +import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; +import org.keycloak.services.models.nosql.keycloak.data.RoleData; +import org.keycloak.services.models.nosql.keycloak.data.UserData; + +/** + * @author Marek Posolda + */ +public class ApplicationAdapter implements ApplicationModel { + + private final ApplicationData application; + private final NoSQL noSQL; + + private UserData resourceUser; + + public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) { + this.application = applicationData; + this.noSQL = noSQL; + } + + @Override + public void updateResource() { + noSQL.saveObject(application); + } + + @Override + public UserModel getResourceUser() { + // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object + if (resourceUser == null) { + resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId()); + } + + return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null; + } + + @Override + public String getId() { + return application.getId(); + } + + @Override + public String getName() { + return application.getName(); + } + + @Override + public void setName(String name) { + application.setName(name); + } + + @Override + public boolean isEnabled() { + return application.isEnabled(); + } + + @Override + public void setEnabled(boolean enabled) { + application.setEnabled(enabled); + } + + @Override + public boolean isSurrogateAuthRequired() { + return application.isSurrogateAuthRequired(); + } + + @Override + public void setSurrogateAuthRequired(boolean surrogateAuthRequired) { + application.setSurrogateAuthRequired(surrogateAuthRequired); + } + + @Override + public String getManagementUrl() { + return application.getManagementUrl(); + } + + @Override + public void setManagementUrl(String url) { + application.setManagementUrl(url); + } + + @Override + public RoleAdapter getRole(String name) { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("name", name) + .andCondition("applicationId", getId()) + .build(); + RoleData role = noSQL.loadSingleObject(RoleData.class, query); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public RoleAdapter addRole(String name) { + if (getRole(name) != null) { + throw new IllegalArgumentException("Role " + name + " already exists"); + } + + RoleData roleData = new RoleData(); + roleData.setName(name); + roleData.setApplicationId(getId()); + + noSQL.saveObject(roleData); + return new RoleAdapter(roleData, noSQL); + } + + @Override + public List getRoles() { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("applicationId", getId()) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + + List result = new ArrayList(); + for (RoleData role : roles) { + result.add(new RoleAdapter(role, noSQL)); + } + + return result; + } + + @Override + public Set getRoleMappings(UserModel user) { + UserData userData = ((UserAdapter)user).getUser(); + String[] roleIds = userData.getRoleIds(); + + Set result = new HashSet(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .inCondition("_id", roleIds) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + // TODO: Maybe improve to have roles and scopes in separate table? As actually 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 void addScope(UserModel agent, String roleName) { + RoleAdapter role = getRole(roleName); + if (role == null) { + throw new RuntimeException("Role not found"); + } + + addScope(agent, role); + } + + @Override + public void addScope(UserModel agent, RoleModel role) { + UserData userData = ((UserAdapter)agent).getUser(); + RoleData roleData = ((RoleAdapter)role).getRole(); + + String[] scopeIds = userData.getScopeIds(); + scopeIds = Utils.addItemToArray(scopeIds, roleData.getId()); + userData.setScopeIds(scopeIds); + + noSQL.saveObject(userData); + } + + @Override + public Set getScope(UserModel agent) { + UserData userData = ((UserAdapter)agent).getUser(); + String[] scopeIds = userData.getScopeIds(); + + Set result = new HashSet(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .inCondition("_id", scopeIds) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + // TODO: Maybe improve to have roles and scopes in separate table? As actually 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; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java new file mode 100644 index 000000000000..a0ff43589eb4 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java @@ -0,0 +1,74 @@ +package org.keycloak.services.models.nosql.keycloak.adapters; + +import java.net.UnknownHostException; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; +import org.keycloak.services.models.nosql.keycloak.data.RealmData; +import org.keycloak.services.models.nosql.keycloak.data.RequiredCredentialData; +import org.keycloak.services.models.nosql.keycloak.data.RoleData; +import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; +import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.services.models.nosql.impl.MongoDBImpl; +import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; + +/** + * NoSQL implementation based on MongoDB + * + * @author Marek Posolda + */ +public class MongoDBSessionFactory implements KeycloakSessionFactory { + + private static final Class[] MANAGED_DATA_TYPES = { + RealmData.class, + UserData.class, + RoleData.class, + RequiredCredentialData.class, + PasswordData.class, + SocialLinkData.class, + ApplicationData.class + }; + + private final MongoClient mongoClient; + private final NoSQL mongoDB; + + public MongoDBSessionFactory(String host, int port, String dbName, boolean removeAllObjectsAtStartup) { + try { + // TODO: authentication support + mongoClient = new MongoClient(host, port); + + DB db = mongoClient.getDB(dbName); + mongoDB = new MongoDBImpl(db); + + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + + if (removeAllObjectsAtStartup) { + NoSQLQuery emptyQuery = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class).build(); + for (Class type : MANAGED_DATA_TYPES) { + mongoDB.removeObjects((Class)type, emptyQuery); + } + // TODO: logging + System.out.println("All objects successfully removed from DB"); + } + } + + @Override + public KeycloakSession createSession() { + return new NoSQLSession(mongoDB); + } + + @Override + public void close() { + mongoClient.close(); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java new file mode 100644 index 000000000000..9e2619ec2358 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java @@ -0,0 +1,85 @@ +package org.keycloak.services.models.nosql.keycloak.adapters; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.resteasy.spi.NotImplementedYetException; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakTransaction; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.data.RealmData; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; + +/** + * @author Marek Posolda + */ +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(PicketlinkKeycloakSession.generateId(), name); + } + + @Override + public RealmModel createRealm(String id, String name) { + 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 = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("id", id) + .build(); + RealmData realmData = noSQL.loadSingleObject(RealmData.class, query); + return realmData != null ? new RealmAdapter(realmData, noSQL) : null; + } + + @Override + public List getRealms(UserModel admin) { + String userId = ((UserAdapter)admin).getUser().getId(); + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("realmAdmins", userId) + .build(); + List realms = noSQL.loadObjects(RealmData.class, query); + + List results = new ArrayList(); + 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); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java similarity index 94% rename from services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java index 60a1338970a0..2842852d6bfa 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/NoSQLTransaction.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.adapters; +package org.keycloak.services.models.nosql.keycloak.adapters; import org.keycloak.services.models.KeycloakTransaction; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java new file mode 100644 index 000000000000..5181c42f7213 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -0,0 +1,751 @@ +package org.keycloak.services.models.nosql.keycloak.adapters; + +import java.io.IOException; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bouncycastle.openssl.PEMWriter; +import org.jboss.resteasy.security.PemUtils; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.models.ApplicationModel; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.RequiredCredentialModel; +import org.keycloak.services.models.RoleModel; +import org.keycloak.services.models.SocialLinkModel; +import org.keycloak.services.models.UserCredentialModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.credentials.PasswordCredentialHandler; +import org.keycloak.services.models.nosql.keycloak.credentials.TOTPCredentialHandler; +import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; +import org.keycloak.services.models.nosql.keycloak.data.RealmData; +import org.keycloak.services.models.nosql.keycloak.data.RequiredCredentialData; +import org.keycloak.services.models.nosql.keycloak.data.RoleData; +import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; +import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.impl.Utils; +import org.keycloak.services.models.picketlink.relationships.ResourceRelationship; +import org.picketlink.idm.credential.Credentials; +import org.picketlink.idm.query.RelationshipQuery; + +/** + * @author Marek Posolda + */ +public class RealmAdapter implements RealmModel { + + private final RealmData realm; + private final NoSQL noSQL; + + protected volatile transient PublicKey publicKey; + protected volatile transient PrivateKey privateKey; + + // TODO: likely shouldn't be static. And setup is not called ATM, which means that it's not possible to configure stuff like PasswordEncoder etc. + private static PasswordCredentialHandler passwordCredentialHandler = new PasswordCredentialHandler(); + private static TOTPCredentialHandler totpCredentialHandler = new TOTPCredentialHandler(); + + public RealmAdapter(RealmData realmData, NoSQL noSQL) { + this.realm = realmData; + this.noSQL = noSQL; + } + + protected String getOid() { + return realm.getOid(); + } + + @Override + public String getId() { + return realm.getId(); + } + + @Override + public String getName() { + return realm.getName(); + } + + @Override + public void setName(String name) { + realm.setName(name); + updateRealm(); + } + + @Override + public boolean isEnabled() { + return realm.isEnabled(); + } + + @Override + public void setEnabled(boolean enabled) { + realm.setEnabled(enabled); + updateRealm(); + } + + @Override + public boolean isSocial() { + return realm.isSocial(); + } + + @Override + public void setSocial(boolean social) { + realm.setSocial(social); + updateRealm(); + } + + @Override + public boolean isAutomaticRegistrationAfterSocialLogin() { + return realm.isAutomaticRegistrationAfterSocialLogin(); + } + + @Override + public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) { + realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin); + updateRealm(); + } + + @Override + public boolean isSslNotRequired() { + return realm.isSslNotRequired(); + } + + @Override + public void setSslNotRequired(boolean sslNotRequired) { + realm.setSslNotRequired(sslNotRequired); + updateRealm(); + } + + @Override + public boolean isCookieLoginAllowed() { + return realm.isCookieLoginAllowed(); + } + + @Override + public void setCookieLoginAllowed(boolean cookieLoginAllowed) { + realm.setCookieLoginAllowed(cookieLoginAllowed); + updateRealm(); + } + + @Override + public boolean isRegistrationAllowed() { + return realm.isRegistrationAllowed(); + } + + @Override + public void setRegistrationAllowed(boolean registrationAllowed) { + realm.setRegistrationAllowed(registrationAllowed); + updateRealm(); + } + + @Override + public int getTokenLifespan() { + return realm.getTokenLifespan(); + } + + @Override + public void setTokenLifespan(int tokenLifespan) { + realm.setTokenLifespan(tokenLifespan); + updateRealm(); + } + + @Override + public int getAccessCodeLifespan() { + return realm.getAccessCodeLifespan(); + } + + @Override + public void setAccessCodeLifespan(int accessCodeLifespan) { + realm.setAccessCodeLifespan(accessCodeLifespan); + updateRealm(); + } + + @Override + public String getPublicKeyPem() { + return realm.getPublicKeyPem(); + } + + @Override + public void setPublicKeyPem(String publicKeyPem) { + realm.setPublicKeyPem(publicKeyPem); + this.publicKey = null; + updateRealm(); + } + + @Override + public String getPrivateKeyPem() { + return realm.getPrivateKeyPem(); + } + + @Override + public void setPrivateKeyPem(String privateKeyPem) { + realm.setPrivateKeyPem(privateKeyPem); + this.privateKey = null; + updateRealm(); + } + + @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); + } + } + 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)); + } + + @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); + } + } + 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)); + } + + @Override + public UserAdapter getUser(String name) { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("loginName", name) + .andCondition("realmId", getOid()) + .build(); + UserData user = noSQL.loadSingleObject(UserData.class, query); + + if (user == null) { + return null; + } else { + return new UserAdapter(user, noSQL); + } + } + + @Override + public UserAdapter addUser(String username) { + if (getUser(username) != null) { + throw new IllegalArgumentException("User " + username + " already exists"); + } + + UserData userData = new UserData(); + userData.setLoginName(username); + userData.setEnabled(true); + userData.setRealmId(getOid()); + + noSQL.saveObject(userData); + return new UserAdapter(userData, noSQL); + } + + @Override + public RoleAdapter getRole(String name) { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("name", name) + .andCondition("realmId", getOid()) + .build(); + RoleData role = noSQL.loadSingleObject(RoleData.class, query); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public RoleModel addRole(String name) { + if (getRole(name) != null) { + throw new IllegalArgumentException("Role " + name + " already exists"); + } + + RoleData roleData = new RoleData(); + roleData.setName(name); + roleData.setRealmId(getOid()); + + noSQL.saveObject(roleData); + return new RoleAdapter(roleData, noSQL); + } + + @Override + public List getRoles() { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("realmId", getOid()) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + + List result = new ArrayList(); + for (RoleData role : roles) { + result.add(new RoleAdapter(role, noSQL)); + } + + return result; + } + + @Override + public List getDefaultRoles() { + String[] defaultRoles = realm.getDefaultRoles(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .inCondition("_id", defaultRoles) + .build(); + List defaultRolesData = noSQL.loadObjects(RoleData.class, query); + + List defaultRoleModels = new ArrayList(); + for (RoleData roleData : defaultRolesData) { + defaultRoleModels.add(new RoleAdapter(roleData, noSQL)); + } + return defaultRoleModels; + } + + @Override + public void addDefaultRole(String name) { + RoleModel role = getRole(name); + if (role == null) { + role = addRole(name); + } + + String[] defaultRoles = realm.getDefaultRoles(); + String[] roleIds = Utils.addItemToArray(defaultRoles, role.getId()); + + realm.setDefaultRoles(roleIds); + updateRealm(); + } + + @Override + public void updateDefaultRoles(String[] defaultRoles) { + // defaultRoles is array with names of roles. So we need to convert to array of ids + String[] roleIds = new String[defaultRoles.length]; + for (int i=0 ; i getResourceNameMap() { + Map resourceMap = new HashMap(); + for (ApplicationModel resource : getApplications()) { + resourceMap.put(resource.getName(), resource); + } + return resourceMap; + } + + @Override + public List getApplications() { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("realmId", getOid()) + .build(); + List appDatas = noSQL.loadObjects(ApplicationData.class, query); + + List result = new ArrayList(); + for (ApplicationData appData : appDatas) { + result.add(new ApplicationAdapter(appData, noSQL)); + } + return result; + } + + @Override + public ApplicationModel addApplication(String name) { + UserAdapter resourceUser = addUser(name); + + ApplicationData appData = new ApplicationData(); + appData.setName(name); + appData.setRealmId(getOid()); + appData.setResourceUserId(resourceUser.getUser().getId()); + noSQL.saveObject(appData); + + ApplicationModel resource = new ApplicationAdapter(appData, noSQL); + resource.addRole("*"); + resource.addScope(resourceUser, "*"); + return resource; + } + + @Override + public boolean hasRole(UserModel user, RoleModel role) { + UserData userData = ((UserAdapter)user).getUser(); + + 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 void grantRole(UserModel user, RoleModel role) { + UserData userData = ((UserAdapter)user).getUser(); + RoleData roleData = ((RoleAdapter)role).getRole(); + + String[] roleIds = userData.getRoleIds(); + roleIds = Utils.addItemToArray(roleIds, roleData.getId()); + userData.setRoleIds(roleIds); + + noSQL.saveObject(userData); + } + + @Override + public Set getRoleMappings(UserModel user) { + UserData userData = ((UserAdapter)user).getUser(); + String[] roleIds = userData.getRoleIds(); + + Set result = new HashSet(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .inCondition("_id", roleIds) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + for (RoleData role : roles) { + if (getOid().equals(role.getRealmId())) { + result.add(role.getName()); + } + } + return result; + } + + @Override + public void addScope(UserModel agent, String roleName) { + UserData userData = ((UserAdapter)agent).getUser(); + RoleAdapter role = getRole(roleName); + if (role == null) { + throw new RuntimeException("Role not found"); + } + RoleData roleData = role.getRole(); + + String[] scopeIds = userData.getScopeIds(); + scopeIds = Utils.addItemToArray(scopeIds, roleData.getId()); + userData.setScopeIds(scopeIds); + + noSQL.saveObject(userData); + } + + @Override + public Set getScope(UserModel agent) { + UserData userData = ((UserAdapter)agent).getUser(); + String[] scopeIds = userData.getScopeIds(); + + Set result = new HashSet(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .inCondition("_id", scopeIds) + .build(); + List roles = noSQL.loadObjects(RoleData.class, query); + // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + for (RoleData role : roles) { + if (getOid().equals(role.getRealmId())) { + result.add(role.getName()); + } + } + return result; + } + + @Override + public boolean isRealmAdmin(UserModel agent) { + String[] realmAdmins = realm.getRealmAdmins(); + String userId = ((UserAdapter)agent).getUser().getId(); + return Utils.contains(realmAdmins, userId); + } + + @Override + public void addRealmAdmin(UserModel agent) { + UserData userData = ((UserAdapter)agent).getUser(); + + String[] currentAdmins = realm.getRealmAdmins(); + String[] newAdmins = Utils.addItemToArray(currentAdmins, userData.getId()); + + realm.setRealmAdmins(newAdmins); + updateRealm(); + } + + @Override + public RoleModel getRoleById(String id) { + RoleData role = noSQL.loadObject(RoleData.class, id); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public boolean hasRole(UserModel user, String role) { + RoleModel roleModel = getRole(role); + return hasRole(user, roleModel); + } + + @Override + public void addRequiredCredential(String cred) { + RequiredCredentialModel credentialModel = initRequiredCredentialModel(cred); + addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_USER); + } + + @Override + public void addRequiredResourceCredential(String type) { + RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); + addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_RESOURCE); + } + + @Override + public void addRequiredOAuthClientCredential(String type) { + RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); + addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); + } + + protected void addRequiredCredential(RequiredCredentialModel credentialModel, int clientType) { + RequiredCredentialData credData = new RequiredCredentialData(); + credData.setType(credentialModel.getType()); + credData.setFormLabel(credentialModel.getFormLabel()); + credData.setInput(credentialModel.isInput()); + credData.setSecret(credentialModel.isSecret()); + + credData.setRealmId(getOid()); + credData.setClientType(clientType); + + noSQL.saveObject(credData); + } + + @Override + public void updateRequiredCredentials(Set creds) { + List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_USER); + updateRequiredCredentials(creds, credsData); + } + + @Override + public void updateRequiredApplicationCredentials(Set creds) { + List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_RESOURCE); + updateRequiredCredentials(creds, credsData); + } + + @Override + public void updateRequiredOAuthClientCredentials(Set creds) { + List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); + updateRequiredCredentials(creds, credsData); + } + + protected void updateRequiredCredentials(Set creds, List credsData) { + Set already = new HashSet(); + for (RequiredCredentialData data : credsData) { + if (!creds.contains(data.getType())) { + noSQL.removeObject(data); + } else { + already.add(data.getType()); + } + } + for (String cred : creds) { + // TODO + System.out.println("updating cred: " + cred); + // logger.info("updating cred: " + cred); + if (!already.contains(cred)) { + addRequiredCredential(cred); + } + } + } + + @Override + public List getRequiredCredentials() { + return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_USER); + } + + @Override + public List getRequiredApplicationCredentials() { + return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_RESOURCE); + } + + @Override + public List getRequiredOAuthClientCredentials() { + return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); + } + + protected List getRequiredCredentials(int credentialType) { + List credsData = getRequiredCredentialsData(credentialType); + + List result = new ArrayList(); + for (RequiredCredentialData data : credsData) { + RequiredCredentialModel model = new RequiredCredentialModel(); + model.setFormLabel(data.getFormLabel()); + model.setInput(data.isInput()); + model.setSecret(data.isSecret()); + model.setType(data.getType()); + + result.add(model); + } + return result; + } + + protected List getRequiredCredentialsData(int credentialType) { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("realmId", getOid()) + .andCondition("clientType", credentialType) + .build(); + return noSQL.loadObjects(RequiredCredentialData.class, query); + } + + @Override + public boolean validatePassword(UserModel user, String password) { + Credentials.Status status = passwordCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password); + return status == Credentials.Status.VALID; + } + + @Override + public boolean validateTOTP(UserModel user, String password, String token) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void updateCredential(UserModel user, UserCredentialModel cred) { + if (cred.getType().equals(CredentialRepresentation.PASSWORD)) { + passwordCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), null, null); + } else if (cred.getType().equals(CredentialRepresentation.TOTP)) { + // TODO +// TOTPCredential totp = new TOTPCredential(cred.getValue()); +// totp.setDevice(cred.getDevice()); +// idm.updateCredential(((UserAdapter)user).getUser(), totp); + } else if (cred.getType().equals(CredentialRepresentation.CLIENT_CERT)) { + // TODO +// X509Certificate cert = null; +// try { +// cert = org.keycloak.PemUtils.decodeCertificate(cred.getValue()); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// X509CertificateCredentials creds = new X509CertificateCredentials(cert); +// idm.updateCredential(((UserAdapter)user).getUser(), creds); + } + } + + @Override + public UserModel getUserBySocialLink(SocialLinkModel socialLink) { + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("socialProvider", socialLink.getSocialProvider()) + .andCondition("socialUsername", socialLink.getSocialUsername()) + .build(); + SocialLinkData socialLinkData = noSQL.loadSingleObject(SocialLinkData.class, query); + + if (socialLinkData == null) { + return null; + } else { + UserData userData = noSQL.loadObject(UserData.class, socialLinkData.getUserId()); + // TODO: Add some checking if userData exists and programmatically remove binding if it doesn't? (There are more similar places where this should be handled) + return new UserAdapter(userData, noSQL); + } + } + + @Override + public Set getSocialLinks(UserModel user) { + UserData userData = ((UserAdapter)user).getUser(); + String userId = userData.getId(); + + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("userId", userId) + .build(); + List dbSocialLinks = noSQL.loadObjects(SocialLinkData.class, query); + + Set result = new HashSet(); + for (SocialLinkData socialLinkData : dbSocialLinks) { + SocialLinkModel model = new SocialLinkModel(socialLinkData.getSocialProvider(), socialLinkData.getSocialUsername()); + result.add(model); + } + return result; + } + + @Override + public void addSocialLink(UserModel user, SocialLinkModel socialLink) { + UserData userData = ((UserAdapter)user).getUser(); + SocialLinkData socialLinkData = new SocialLinkData(); + socialLinkData.setSocialProvider(socialLink.getSocialProvider()); + socialLinkData.setSocialUsername(socialLink.getSocialUsername()); + socialLinkData.setUserId(userData.getId()); + + noSQL.saveObject(socialLinkData); + } + + @Override + public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { + UserData userData = ((UserAdapter)user).getUser(); + String userId = userData.getId(); + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("socialProvider", socialLink.getSocialProvider()) + .andCondition("socialUsername", socialLink.getSocialUsername()) + .andCondition("userId", userId) + .build(); + noSQL.removeObjects(SocialLinkData.class, query); + } + + protected void updateRealm() { + noSQL.saveObject(realm); + } + + protected RequiredCredentialModel initRequiredCredentialModel(String type) { + RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type); + if (model == null) { + throw new RuntimeException("Unknown credential type " + type); + } + return model; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java similarity index 85% rename from services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java index edde2e40bd06..9c3f1394b09b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/RoleAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java @@ -1,9 +1,8 @@ -package org.keycloak.services.models.nosql.adapters; +package org.keycloak.services.models.nosql.keycloak.adapters; import org.keycloak.services.models.RoleModel; import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.data.RoleData; -import org.keycloak.services.models.nosql.data.UserData; +import org.keycloak.services.models.nosql.keycloak.data.RoleData; /** * Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) @@ -46,4 +45,8 @@ public void setName(String name) { role.setName(name); noSQL.saveObject(role); } + + public RoleData getRole() { + return role; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java similarity index 91% rename from services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java index 781ba266c8f2..e9085688b742 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/adapters/UserAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java @@ -1,10 +1,10 @@ -package org.keycloak.services.models.nosql.adapters; +package org.keycloak.services.models.nosql.keycloak.adapters; import java.util.Map; import org.keycloak.services.models.UserModel; import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.data.UserData; +import org.keycloak.services.models.nosql.keycloak.data.UserData; /** * Wrapper around UserData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) @@ -90,4 +90,8 @@ public String getAttribute(String name) { public Map getAttributes() { return user.getAttributes(); } + + public UserData getUser() { + return user; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java new file mode 100644 index 000000000000..32a84d7469ef --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java @@ -0,0 +1,155 @@ +package org.keycloak.services.models.nosql.keycloak.credentials; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; +import org.picketlink.idm.credential.Credentials; +import org.picketlink.idm.credential.encoder.PasswordEncoder; +import org.picketlink.idm.credential.encoder.SHAPasswordEncoder; + +/** + * Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler} + * + * @author Marek Posolda + */ +public class PasswordCredentialHandler { + + private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG"; + + /** + *

+ * Stores a stateless instance of {@link org.picketlink.idm.credential.encoder.PasswordEncoder} that should be used to encode passwords. + *

+ */ + public static final String PASSWORD_ENCODER = "PASSWORD_ENCODER"; + + private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);; + + public void setup(Map 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 = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .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) { + 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) { + + // Try to look if user already has password + NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + .andCondition("userId", user.getId()) + .build(); + + PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query); + if (passwordData == null) { + 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); + } + + /** + *

+ * Salt the give rawPassword with the specified salt value. + *

+ * + * @param rawPassword + * @param salt + * @return + */ + private String saltPassword(String rawPassword, String salt) { + return salt + rawPassword; + } + + /** + *

+ * Generates a random string to be used as a salt for passwords. + *

+ * + * @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; + } + + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java new file mode 100644 index 000000000000..a5dc8d2e4491 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java @@ -0,0 +1,11 @@ +package org.keycloak.services.models.nosql.keycloak.credentials; + +/** + * Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler} + * + * @author Marek Posolda + */ +public class TOTPCredentialHandler extends PasswordCredentialHandler { + + // TODO: implement +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java new file mode 100644 index 000000000000..7035f9ae2904 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java @@ -0,0 +1,85 @@ +package org.keycloak.services.models.nosql.keycloak.data; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "applications") +public class ApplicationData implements NoSQLObject { + + private String id; + private String name; + private boolean enabled; + private boolean surrogateAuthRequired; + private String managementUrl; + + private String resourceUserId; + private String realmId; + + @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 boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @NoSQLField + public boolean isSurrogateAuthRequired() { + return surrogateAuthRequired; + } + + public void setSurrogateAuthRequired(boolean surrogateAuthRequired) { + this.surrogateAuthRequired = surrogateAuthRequired; + } + + @NoSQLField + public String getManagementUrl() { + return managementUrl; + } + + public void setManagementUrl(String managementUrl) { + this.managementUrl = managementUrl; + } + + @NoSQLField + public String getResourceUserId() { + return resourceUserId; + } + + public void setResourceUserId(String resourceUserId) { + this.resourceUserId = resourceUserId; + } + + @NoSQLField + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java similarity index 84% rename from services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java index 8e6a663630d8..a550c6a4b22b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/data/RealmData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java @@ -1,4 +1,8 @@ -package org.keycloak.services.models.nosql.data; +package org.keycloak.services.models.nosql.keycloak.data; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; @@ -11,6 +15,8 @@ @NoSQLCollection(collectionName = "realms") public class RealmData implements NoSQLObject { + private String oid; + private String id; private String name; private boolean enabled; @@ -23,9 +29,21 @@ public class RealmData implements NoSQLObject { private int accessCodeLifespan; private String publicKeyPem; private String privateKeyPem; + private String[] defaultRoles; + private String[] realmAdmins; @NoSQLId + public String getOid() { + return oid; + } + + public void setOid(String oid) { + this.oid = oid; + } + + // TODO: Is ID really needed? It seems that it exists just to workaround picketlink... + @NoSQLField public String getId() { return id; } @@ -141,4 +159,14 @@ public String[] getDefaultRoles() { public void setDefaultRoles(String[] defaultRoles) { this.defaultRoles = defaultRoles; } + + @NoSQLField + public String[] getRealmAdmins() { + return realmAdmins; + } + + public void setRealmAdmins(String[] realmAdmins) { + this.realmAdmins = realmAdmins; + } + } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java new file mode 100644 index 000000000000..d20b595b6a80 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java @@ -0,0 +1,90 @@ +package org.keycloak.services.models.nosql.keycloak.data; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "requiredCredentials") +public class RequiredCredentialData implements NoSQLObject { + + 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; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java similarity index 95% rename from services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java index 7d4ba4b3c2ca..9e00ef19fd9e 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/data/RoleData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.data; +package org.keycloak.services.models.nosql.keycloak.data; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java new file mode 100644 index 000000000000..0ed96a416e92 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java @@ -0,0 +1,54 @@ +package org.keycloak.services.models.nosql.keycloak.data; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "socialLinks") +public class SocialLinkData implements NoSQLObject { + + private String id; + private String socialUsername; + private String socialProvider; + private String userId; + + @NoSQLId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @NoSQLField + public String getSocialUsername() { + return socialUsername; + } + + public void setSocialUsername(String socialUsername) { + this.socialUsername = socialUsername; + } + + @NoSQLField + public String getSocialProvider() { + return socialProvider; + } + + public void setSocialProvider(String socialProvider) { + this.socialProvider = socialProvider; + } + + @NoSQLField + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java similarity index 79% rename from services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java rename to services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java index 70aab2a06eb0..cde0b4294036 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/data/UserData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.data; +package org.keycloak.services.models.nosql.keycloak.data; import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQLCollection; @@ -20,6 +20,9 @@ public class UserData extends AbstractAttributedNoSQLObject { private String realmId; + private String[] roleIds; + private String[] scopeIds; + @NoSQLId public String getId() { return id; @@ -82,4 +85,22 @@ public String getRealmId() { public void setRealmId(String realmId) { this.realmId = realmId; } + + @NoSQLField + public String[] getRoleIds() { + return roleIds; + } + + public void setRoleIds(String[] roleIds) { + this.roleIds = roleIds; + } + + @NoSQLField + public String[] getScopeIds() { + return scopeIds; + } + + public void setScopeIds(String[] scopeIds) { + this.scopeIds = scopeIds; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java new file mode 100644 index 000000000000..a5ffad495bc2 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java @@ -0,0 +1,77 @@ +package org.keycloak.services.models.nosql.keycloak.data.credentials; + +import java.util.Date; + +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.NoSQLObject; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "passwordCredentials") +public class PasswordData implements NoSQLObject { + + private String id; + private Date effectiveDate = new Date(); + private Date expiryDate; + private String encodedHash; + private String salt; + + private String userId; + + @NoSQLId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @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; + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 089ab3998f94..dc8d7111eb51 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -8,8 +8,9 @@ import org.keycloak.models.picketlink.mappings.ApplicationEntity; import org.keycloak.models.picketlink.mappings.RealmEntity; import org.keycloak.services.models.KeycloakSessionFactory; -import org.keycloak.services.models.nosql.adapters.MongoDBSessionFactory; +import org.keycloak.services.models.nosql.keycloak.adapters.MongoDBSessionFactory; import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; +import org.keycloak.services.models.picketlink.PicketlinkKeycloakSessionFactory; import org.keycloak.services.models.picketlink.mappings.ApplicationEntity; import org.keycloak.services.models.picketlink.mappings.RealmEntity; import org.keycloak.social.SocialRequestManager; @@ -24,6 +25,8 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.servlet.ServletContext; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; import java.util.HashSet; @@ -61,7 +64,7 @@ protected KeycloakSessionFactory createSessionFactory() { public static KeycloakSessionFactory buildSessionFactory() { // EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store"); // return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager()); - return new MongoDBSessionFactory("localhost", 27017, "keycloak"); + return new MongoDBSessionFactory("localhost", 27017, "keycloak", true); } public KeycloakSessionFactory getFactory() { From be48672ba601ed115e3e81c232170df609ffbed3 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 6 Sep 2013 11:36:30 +0200 Subject: [PATCH 04/14] Added system properties to support switch between picketlink and mongo. Support for Mongo data objects without ID or @DBCollection --- .../api/AbstractAttributedNoSQLObject.java | 2 +- .../models/nosql/api/AbstractNoSQLObject.java | 12 ++ .../services/models/nosql/api/NoSQL.java | 3 + .../services/models/nosql/api/NoSQLField.java | 4 +- .../models/nosql/api/NoSQLObject.java | 9 +- .../nosql/api/query/NoSQLQueryBuilder.java | 8 - .../models/nosql/api/types/ConverterKey.java | 1 + .../models/nosql/api/types/TypeConverter.java | 12 +- .../models/nosql/impl/MongoDBImpl.java | 185 +++++++----------- .../nosql/impl/MongoDBQueryBuilder.java | 2 + .../models/nosql/impl/ObjectInfo.java | 8 +- .../services/models/nosql/impl/Utils.java | 19 ++ .../BasicDBListToStringArrayConverter.java | 2 +- .../impl/types/NoSQLObjectConverter.java | 133 +++++++++++++ .../keycloak/adapters/ApplicationAdapter.java | 8 +- .../adapters/MongoDBSessionFactory.java | 18 +- .../nosql/keycloak/adapters/NoSQLSession.java | 4 +- .../nosql/keycloak/adapters/RealmAdapter.java | 22 +-- .../PasswordCredentialHandler.java | 4 +- .../nosql/keycloak/data/ApplicationData.java | 14 ++ .../models/nosql/keycloak/data/RealmData.java | 20 ++ .../keycloak/data/RequiredCredentialData.java | 3 +- .../models/nosql/keycloak/data/RoleData.java | 40 ++++ .../nosql/keycloak/data/SocialLinkData.java | 13 +- .../models/nosql/keycloak/data/UserData.java | 14 ++ .../data/credentials/PasswordData.java | 13 +- .../resources/KeycloakApplication.java | 22 ++- 27 files changed, 408 insertions(+), 187 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java index efdddee67c7a..a3a6ddb1bfb3 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java @@ -7,7 +7,7 @@ /** * @author Marek Posolda */ -public abstract class AbstractAttributedNoSQLObject implements AttributedNoSQLObject { +public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject implements AttributedNoSQLObject { // Simple hashMap for now (no thread-safe) private Map attributes = new HashMap(); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java new file mode 100644 index 000000000000..692839bc0061 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java @@ -0,0 +1,12 @@ +package org.keycloak.services.models.nosql.api; + +/** + * @author Marek Posolda + */ +public abstract class AbstractNoSQLObject implements NoSQLObject { + + @Override + public void afterRemove(NoSQL noSQL) { + // Empty by default + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java index c3081b05a931..1b28fd1e338b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java @@ -3,6 +3,7 @@ import java.util.List; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; /** * @author Marek Posolda @@ -26,4 +27,6 @@ public interface NoSQL { void removeObject(Class type, String oid); void removeObjects(Class type, NoSQLQuery query); + + NoSQLQueryBuilder createQueryBuilder(); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java index c3e0586d2f66..8c5d2a2940f2 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java @@ -16,7 +16,5 @@ @Retention(RUNTIME) public @interface NoSQLField { - String fieldName() default ""; - - // TODO: add lazy loading? + // TODO: fieldName add lazy loading? } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java index 1f430b6f5a13..7d31b687001d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java @@ -1,9 +1,16 @@ package org.keycloak.services.models.nosql.api; /** - * Just marker interface + * Base interface for object, which is persisted in NoSQL database * * @author Marek Posolda */ public interface NoSQLObject { + + /** + * Lifecycle callback, which is called after removal of this object from NoSQL database. + * It may be useful for triggering removal of wired objects. + */ + void afterRemove(NoSQL noSQL); + } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java index aa4eb48e522f..341a115120f8 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java @@ -12,14 +12,6 @@ public abstract class NoSQLQueryBuilder { protected NoSQLQueryBuilder() {}; - public static NoSQLQueryBuilder create(Class builderClass) { - try { - return builderClass.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - public NoSQLQuery build() { return new NoSQLQuery(queryAttributes); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java index f4b6fe0cdf05..68fc3cb950ef 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java @@ -27,4 +27,5 @@ public boolean equals(Object obj) { ConverterKey tc = (ConverterKey)obj; return tc.applicationObjectType.equals(this.applicationObjectType) && tc.dbObjectType.equals(this.dbObjectType); } + } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java index 7bdb384625c1..3368e4f390a5 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java @@ -11,6 +11,7 @@ */ public class TypeConverter { + // TODO: Thread-safety support (maybe...) private Map> converterRegistry = new HashMap>(); public void addConverter(Converter converter) { @@ -18,14 +19,19 @@ public void addConverter(Converter converter) { converterRegistry.put(converterKey, converter); } - public T convertDBObjectToApplicationObject(S dbObject, Class expectedApplicationObjectType, Class expectedDBObjectType) { + public T convertDBObjectToApplicationObject(S dbObject, Class expectedApplicationObjectType) { + // TODO: Not type safe as it expects that S type of converter must exactly match type of dbObject. Converter lookup should be more flexible + Class expectedDBObjectType = (Class)dbObject.getClass(); Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); return converter.convertDBObjectToApplicationObject(dbObject); } - public S convertApplicationObjectToDBObject(T applicationobject, Class expectedApplicationObjectType, Class expectedDBObjectType) { + public S convertApplicationObjectToDBObject(T applicationObject, Class expectedDBObjectType) { + // TODO: Not type safe as it expects that T type of converter must exactly match type of applicationObject. Converter lookup should be more flexible + Class expectedApplicationObjectType = (Class)applicationObject.getClass(); Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); - return converter.convertApplicationObjectToDBObject(applicationobject); + + return converter.convertApplicationObjectToDBObject(applicationObject); } private Converter getConverter( Class expectedApplicationObjectType, Class expectedDBObjectType) { diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index fd6efa7627f9..0385aeef0cb4 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -12,20 +12,21 @@ import com.mongodb.DBCursor; import com.mongodb.DBObject; import org.bson.types.ObjectId; -import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; +import org.jboss.resteasy.logging.Logger; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; import org.keycloak.services.models.nosql.api.types.Converter; import org.keycloak.services.models.nosql.api.types.TypeConverter; import org.keycloak.services.models.nosql.impl.types.BasicDBListToStringArrayConverter; +import org.keycloak.services.models.nosql.impl.types.NoSQLObjectConverter; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; -import org.picketlink.common.reflection.Types; /** * @author Marek Posolda @@ -33,59 +34,61 @@ public class MongoDBImpl implements NoSQL { private final DB database; - // private static final Logger logger = Logger.getLogger(MongoDBImpl.class); + private static final Logger logger = Logger.getLogger(MongoDBImpl.class); private final TypeConverter typeConverter; + private ConcurrentMap, ObjectInfo> objectInfoCache = + new ConcurrentHashMap, ObjectInfo>(); - public MongoDBImpl(DB database) { + public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class[] managedDataTypes) { this.database = database; typeConverter = new TypeConverter(); typeConverter.addConverter(new BasicDBListToStringArrayConverter()); - } - - private ConcurrentMap, ObjectInfo> objectInfoCache = - new ConcurrentHashMap, ObjectInfo>(); - + for (Class type : managedDataTypes) { + typeConverter.addConverter(new NoSQLObjectConverter(this, typeConverter, type)); + getObjectInfo(type); + } + if (removeAllObjectsAtStartup) { + for (Class type : managedDataTypes) { + ObjectInfo objectInfo = getObjectInfo(type); + String collectionName = objectInfo.getDbCollectionName(); + if (collectionName != null) { + logger.debug("Removing all objects of type " + type); + + DBCollection dbCollection = this.database.getCollection(collectionName); + dbCollection.remove(new BasicDBObject()); + } else { + logger.debug("Skip removing objects of type " + type + " as it doesn't have it's own collection"); + } + } + logger.info("All objects successfully removed from MongoDB"); + } + } @Override public void saveObject(NoSQLObject object) { - Class clazz = object.getClass(); + Class clazz = object.getClass(); // Find annotations for ID, for all the properties and for the name of the collection. ObjectInfo objectInfo = getObjectInfo(clazz); // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped) - BasicDBObject dbObject = new BasicDBObject(); - List> props = objectInfo.getProperties(); - for (Property property : props) { - String propName = property.getName(); - Object propValue = property.getValue(object); - - - dbObject.append(propName, propValue); - } - - // Adding attributes - if (object instanceof AttributedNoSQLObject) { - AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object; - Map attributes = attributedObject.getAttributes(); - for (Map.Entry attribute : attributes.entrySet()) { - dbObject.append(attribute.getKey(), attribute.getValue()); - } - } + BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class); DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); // Decide if we should insert or update (based on presence of oid property in original object) Property oidProperty = objectInfo.getOidProperty(); - String currentId = oidProperty.getValue(object); + String currentId = oidProperty == null ? null : oidProperty.getValue(object); if (currentId == null) { dbCollection.insert(dbObject); // Add oid to value of given object - oidProperty.setValue(object, dbObject.getString("_id")); + if (oidProperty != null) { + oidProperty.setValue(object, dbObject.getString("_id")); + } } else { BasicDBObject setCommand = new BasicDBObject("$set", dbObject); BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId)); @@ -100,7 +103,7 @@ public T loadObject(Class type, String oid) { BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid)); DBObject dbObject = dbCollection.findOne(idQuery); - return convertObject(type, dbObject); + return typeConverter.convertDBObjectToApplicationObject(dbObject, type); } @Override @@ -129,7 +132,7 @@ public List loadObjects(Class type, NoSQLQuery que @Override public void removeObject(NoSQLObject object) { Class type = object.getClass(); - ObjectInfo objectInfo = getObjectInfo(type); + ObjectInfo objectInfo = getObjectInfo(type); Property idProperty = objectInfo.getOidProperty(); String oid = idProperty.getValue(object); @@ -139,18 +142,39 @@ public void removeObject(NoSQLObject object) { @Override public void removeObject(Class type, String oid) { - DBCollection dbCollection = getDBCollectionForType(type); + NoSQLObject found = loadObject(type, oid); + if (found == null) { + logger.warn("Object of type: " + type + ", oid: " + oid + " doesn't exist in MongoDB. Skip removal"); + } else { + DBCollection dbCollection = getDBCollectionForType(type); + BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid)); + dbCollection.remove(dbQuery); + logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB."); - BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid)); - dbCollection.remove(dbQuery); + found.afterRemove(this); + } } @Override public void removeObjects(Class type, NoSQLQuery query) { - DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject dbQuery = getDBQueryFromQuery(query); + List foundObjects = loadObjects(type, query); + if (foundObjects.size() == 0) { + logger.info("Not found any objects of type: " + type + ", query: " + query); + } else { + DBCollection dbCollection = getDBCollectionForType(type); + BasicDBObject dbQuery = getDBQueryFromQuery(query); + dbCollection.remove(dbQuery); + logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query); + + for (NoSQLObject found : foundObjects) { + found.afterRemove(this); + } + } + } - dbCollection.remove(dbQuery); + @Override + public NoSQLQueryBuilder createQueryBuilder() { + return new MongoDBQueryBuilder(); } // Possibility to add user-defined converters @@ -158,26 +182,19 @@ public void addConverter(Converter converter) { typeConverter.addConverter(converter); } - private ObjectInfo getObjectInfo(Class objectClass) { - ObjectInfo objectInfo = (ObjectInfo)objectInfoCache.get(objectClass); + public ObjectInfo getObjectInfo(Class objectClass) { + ObjectInfo objectInfo = objectInfoCache.get(objectClass); if (objectInfo == null) { Property idProperty = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult(); - if (idProperty == null) { - // TODO: should be allowed to have NoSQLObject classes without declared NoSQLId annotation? - throw new IllegalStateException("Class " + objectClass + " doesn't have property with declared annotation " + NoSQLId.class); - } List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList(); NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class); - if (classAnnotation == null) { - throw new IllegalStateException("Class " + objectClass + " doesn't have annotation " + NoSQLCollection.class); - } - String dbCollectionName = classAnnotation.collectionName(); - objectInfo = new ObjectInfo((Class)objectClass, dbCollectionName, idProperty, properties); + String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName(); + objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties); - ObjectInfo existing = objectInfoCache.putIfAbsent((Class)objectClass, objectInfo); + ObjectInfo existing = objectInfoCache.putIfAbsent(objectClass, objectInfo); if (existing != null) { objectInfo = existing; } @@ -186,75 +203,23 @@ private ObjectInfo getObjectInfo(Class objectClass return objectInfo; } - - private T convertObject(Class type, DBObject dbObject) { - if (dbObject == null) { - return null; - } - - ObjectInfo objectInfo = getObjectInfo(type); - - T object; - try { - object = type.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - for (String key : dbObject.keySet()) { - Object value = dbObject.get(key); - Property property; - - if ("_id".equals(key)) { - // Current property is "id" - Property idProperty = objectInfo.getOidProperty(); - idProperty.setValue(object, value.toString()); - - } else if ((property = objectInfo.getPropertyByName(key)) != null) { - // It's declared property with @DBField annotation - Class expectedType = property.getJavaClass(); - Class actualType = value != null ? value.getClass() : expectedType; - - // handle primitives - expectedType = Types.boxedClass(expectedType); - actualType = Types.boxedClass(actualType); - - if (actualType.isAssignableFrom(expectedType)) { - property.setValue(object, value); - } else { - // we need to convert - Object convertedValue = typeConverter.convertDBObjectToApplicationObject(value, expectedType, actualType); - property.setValue(object, convertedValue); - } - - } 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 - // TODO: logging - // logger.warn("Property with key " + key + " not known for type " + type); - System.err.println("Property with key " + key + " not known for type " + type); - } - } - - return object; - } - private List convertCursor(Class type, DBCursor cursor) { List result = new ArrayList(); - for (DBObject dbObject : cursor) { - T converted = convertObject(type, dbObject); - result.add(converted); + try { + for (DBObject dbObject : cursor) { + T converted = typeConverter.convertDBObjectToApplicationObject(dbObject, type); + result.add(converted); + } + } finally { + cursor.close(); } return result; } private DBCollection getDBCollectionForType(Class type) { - ObjectInfo objectInfo = getObjectInfo(type); + ObjectInfo objectInfo = getObjectInfo(type); return database.getCollection(objectInfo.getDbCollectionName()); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java index b3af7324eb2d..94b6196dbc94 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java @@ -9,6 +9,8 @@ */ public class MongoDBQueryBuilder extends NoSQLQueryBuilder { + protected MongoDBQueryBuilder() {}; + @Override public NoSQLQueryBuilder inCondition(String name, Object[] values) { if (values == null) { diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java index 867ac12b62af..27f782c7e29f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java @@ -8,9 +8,9 @@ /** * @author Marek Posolda */ -class ObjectInfo { +public class ObjectInfo { - private final Class objectClass; + private final Class objectClass; private final String dbCollectionName; @@ -18,14 +18,14 @@ class ObjectInfo { private final List> properties; - public ObjectInfo(Class objectClass, String dbCollectionName, Property oidProperty, List> properties) { + public ObjectInfo(Class objectClass, String dbCollectionName, Property oidProperty, List> properties) { this.objectClass = objectClass; this.dbCollectionName = dbCollectionName; this.oidProperty = oidProperty; this.properties = properties; } - public Class getObjectClass() { + public Class getObjectClass() { return objectClass; } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java index 1398e278b8ea..26ab30a87c10 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java @@ -53,4 +53,23 @@ public static boolean contains(Object[] array, Object item) { return false; } + public static T[] removeItemFromArray(T[] inputArray, T item) { + if (item == null) { + throw new IllegalArgumentException("item must be non-null"); + } + + if (inputArray == null) { + return inputArray; + } else { + T[] outputArray = (T[])Array.newInstance(item.getClass(), inputArray.length - 1); + int counter = 0; + for (T object : inputArray) { + if (!item.equals(object)) { + outputArray[counter++] = object; + } + } + return outputArray; + } + } + } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java index 0f9d5d59172c..0c3f1767439d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java @@ -13,7 +13,7 @@ public class BasicDBListToStringArrayConverter implements ConverterMarek Posolda + */ +public class NoSQLObjectConverter implements Converter { + + private final MongoDBImpl mongoDBImpl; + private final TypeConverter typeConverter; + private final Class expectedNoSQLObjectType; + + public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { + this.mongoDBImpl = mongoDBImpl; + this.typeConverter = typeConverter; + this.expectedNoSQLObjectType = expectedNoSQLObjectType; + } + + @Override + public T convertDBObjectToApplicationObject(BasicDBObject dbObject) { + if (dbObject == null) { + return null; + } + + ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType); + + T object; + try { + object = expectedNoSQLObjectType.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + for (String key : dbObject.keySet()) { + Object value = dbObject.get(key); + Property property; + + if ("_id".equals(key)) { + // Current property is "id" + Property idProperty = objectInfo.getOidProperty(); + if (idProperty != null) { + idProperty.setValue(object, value.toString()); + } + + } else if ((property = objectInfo.getPropertyByName(key)) != null) { + // 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 + // TODO: logging + // logger.warn("Property with key " + key + " not known for type " + type); + System.err.println("Property with key " + key + " not known for type " + expectedNoSQLObjectType); + } + } + + return object; + } + + private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) { + Class expectedType = property.getJavaClass(); + Class actualType = valueFromDB != null ? valueFromDB.getClass() : expectedType; + + // handle primitives + expectedType = Types.boxedClass(expectedType); + actualType = Types.boxedClass(actualType); + + if (actualType.isAssignableFrom(expectedType)) { + property.setValue(object, valueFromDB); + } else { + // we need to convert + Object convertedValue = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedType); + property.setValue(object, convertedValue); + } + } + + @Override + public BasicDBObject convertApplicationObjectToDBObject(T applicationObject) { + ObjectInfo objectInfo = mongoDBImpl.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(); + List> props = objectInfo.getProperties(); + for (Property property : props) { + String propName = property.getName(); + Object propValue = property.getValue(applicationObject); + + // Check if we have noSQLObject, which is indication that we need to convert recursively + if (propValue instanceof NoSQLObject) { + propValue = typeConverter.convertApplicationObjectToDBObject(propValue, BasicDBObject.class); + } + + dbObject.append(propName, propValue); + } + + // Adding attributes + if (applicationObject instanceof AttributedNoSQLObject) { + AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)applicationObject; + Map attributes = attributedObject.getAttributes(); + for (Map.Entry attribute : attributes.entrySet()) { + dbObject.append(attribute.getKey(), attribute.getValue()); + } + } + + return dbObject; + } + + @Override + public Class getApplicationObjectType() { + return expectedNoSQLObjectType; + } + + @Override + public Class getDBObjectType() { + return BasicDBObject.class; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java index fc8a6e177f56..dd1040094c60 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java @@ -94,7 +94,7 @@ public void setManagementUrl(String url) { @Override public RoleAdapter getRole(String name) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("name", name) .andCondition("applicationId", getId()) .build(); @@ -122,7 +122,7 @@ public RoleAdapter addRole(String name) { @Override public List getRoles() { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("applicationId", getId()) .build(); List roles = noSQL.loadObjects(RoleData.class, query); @@ -142,7 +142,7 @@ public Set getRoleMappings(UserModel user) { Set result = new HashSet(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", roleIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); @@ -184,7 +184,7 @@ public Set getScope(UserModel agent) { Set result = new HashSet(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", scopeIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java index a0ff43589eb4..114f7b74db63 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java @@ -4,6 +4,8 @@ import com.mongodb.DB; import com.mongodb.MongoClient; +import org.jboss.resteasy.logging.Logger; +import org.keycloak.services.managers.RealmManager; import org.keycloak.services.models.KeycloakSession; import org.keycloak.services.models.KeycloakSessionFactory; import org.keycloak.services.models.nosql.api.NoSQL; @@ -26,8 +28,9 @@ * @author Marek Posolda */ public class MongoDBSessionFactory implements KeycloakSessionFactory { + protected static final Logger logger = Logger.getLogger(MongoDBSessionFactory.class); - private static final Class[] MANAGED_DATA_TYPES = { + private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { RealmData.class, UserData.class, RoleData.class, @@ -41,25 +44,17 @@ public class MongoDBSessionFactory implements KeycloakSessionFactory { private final NoSQL mongoDB; public MongoDBSessionFactory(String host, int port, String dbName, boolean removeAllObjectsAtStartup) { + logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, removeAllObjectsAtStartup)); try { // TODO: authentication support mongoClient = new MongoClient(host, port); DB db = mongoClient.getDB(dbName); - mongoDB = new MongoDBImpl(db); + mongoDB = new MongoDBImpl(db, removeAllObjectsAtStartup, MANAGED_DATA_TYPES); } catch (UnknownHostException e) { throw new RuntimeException(e); } - - if (removeAllObjectsAtStartup) { - NoSQLQuery emptyQuery = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class).build(); - for (Class type : MANAGED_DATA_TYPES) { - mongoDB.removeObjects((Class)type, emptyQuery); - } - // TODO: logging - System.out.println("All objects successfully removed from DB"); - } } @Override @@ -69,6 +64,7 @@ public KeycloakSession createSession() { @Override public void close() { + logger.info("Closing MongoDB client"); mongoClient.close(); } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java index 9e2619ec2358..838a6a4b6008 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java @@ -55,7 +55,7 @@ public RealmModel createRealm(String id, String name) { @Override public RealmModel getRealm(String id) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("id", id) .build(); RealmData realmData = noSQL.loadSingleObject(RealmData.class, query); @@ -65,7 +65,7 @@ public RealmModel getRealm(String id) { @Override public List getRealms(UserModel admin) { String userId = ((UserAdapter)admin).getUser().getId(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("realmAdmins", userId) .build(); List realms = noSQL.loadObjects(RealmData.class, query); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java index 5181c42f7213..f7e3e83e0299 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -250,7 +250,7 @@ public void setPrivateKey(PrivateKey privateKey) { @Override public UserAdapter getUser(String name) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("loginName", name) .andCondition("realmId", getOid()) .build(); @@ -280,7 +280,7 @@ public UserAdapter addUser(String username) { @Override public RoleAdapter getRole(String name) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("name", name) .andCondition("realmId", getOid()) .build(); @@ -308,7 +308,7 @@ public RoleModel addRole(String name) { @Override public List getRoles() { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("realmId", getOid()) .build(); List roles = noSQL.loadObjects(RoleData.class, query); @@ -325,7 +325,7 @@ public List getRoles() { public List getDefaultRoles() { String[] defaultRoles = realm.getDefaultRoles(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", defaultRoles) .build(); List defaultRolesData = noSQL.loadObjects(RoleData.class, query); @@ -393,7 +393,7 @@ public Map getResourceNameMap() { @Override public List getApplications() { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("realmId", getOid()) .build(); List appDatas = noSQL.loadObjects(ApplicationData.class, query); @@ -456,7 +456,7 @@ public Set getRoleMappings(UserModel user) { Set result = new HashSet(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", roleIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); @@ -492,7 +492,7 @@ public Set getScope(UserModel agent) { Set result = new HashSet(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", scopeIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); @@ -639,7 +639,7 @@ protected List getRequiredCredentials(int credentialTyp } protected List getRequiredCredentialsData(int credentialType) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("realmId", getOid()) .andCondition("clientType", credentialType) .build(); @@ -681,7 +681,7 @@ public void updateCredential(UserModel user, UserCredentialModel cred) { @Override public UserModel getUserBySocialLink(SocialLinkModel socialLink) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("socialProvider", socialLink.getSocialProvider()) .andCondition("socialUsername", socialLink.getSocialUsername()) .build(); @@ -701,7 +701,7 @@ public Set getSocialLinks(UserModel user) { UserData userData = ((UserAdapter)user).getUser(); String userId = userData.getId(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("userId", userId) .build(); List dbSocialLinks = noSQL.loadObjects(SocialLinkData.class, query); @@ -729,7 +729,7 @@ public void addSocialLink(UserModel user, SocialLinkModel socialLink) { public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { UserData userData = ((UserAdapter)user).getUser(); String userId = userData.getId(); - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("socialProvider", socialLink.getSocialProvider()) .andCondition("socialUsername", socialLink.getSocialUsername()) .andCondition("userId", userId) diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java index 32a84d7469ef..77099873dd38 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java @@ -57,7 +57,7 @@ public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordTo // If the user for the provided username cannot be found we fail validation if (user != null) { if (user.isEnabled()) { - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("userId", user.getId()) .build(); PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query); @@ -87,7 +87,7 @@ public void update(NoSQL noSQL, UserData user, String password, Date effectiveDate, Date expiryDate) { // Try to look if user already has password - NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class) + NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("userId", user.getId()) .build(); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java index 7035f9ae2904..108f1d0e515d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java @@ -1,9 +1,11 @@ package org.keycloak.services.models.nosql.keycloak.data; +import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; /** * @author Marek Posolda @@ -82,4 +84,16 @@ public String getRealmId() { public void setRealmId(String realmId) { this.realmId = realmId; } + + @Override + public void afterRemove(NoSQL noSQL) { + // Remove resourceUser of this application + noSQL.removeObject(UserData.class, resourceUserId); + + // Remove all roles, which belongs to this application + NoSQLQuery query = noSQL.createQueryBuilder() + .andCondition("applicationId", id) + .build(); + noSQL.removeObjects(RoleData.class, query); + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java index a550c6a4b22b..42f8ab246ea3 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java @@ -4,10 +4,12 @@ import java.util.Random; import java.util.UUID; +import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; /** * @author Marek Posolda @@ -169,4 +171,22 @@ public void setRealmAdmins(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); + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java index d20b595b6a80..124e50711381 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java @@ -1,5 +1,6 @@ package org.keycloak.services.models.nosql.keycloak.data; +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; @@ -9,7 +10,7 @@ * @author Marek Posolda */ @NoSQLCollection(collectionName = "requiredCredentials") -public class RequiredCredentialData implements NoSQLObject { +public class RequiredCredentialData extends AbstractNoSQLObject { public static final int CLIENT_TYPE_USER = 1; public static final int CLIENT_TYPE_RESOURCE = 2; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java index 9e00ef19fd9e..89138e68c07b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java @@ -1,9 +1,15 @@ package org.keycloak.services.models.nosql.keycloak.data; +import java.util.List; + +import org.jboss.resteasy.logging.Logger; +import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.impl.Utils; /** * @author Marek Posolda @@ -11,6 +17,8 @@ @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; @@ -62,4 +70,36 @@ public String getApplicationId() { 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 users = noSQL.loadObjects(UserData.class, query); + for (UserData user : users) { + logger.info("Removing role " + getName() + " from user " + user.getLoginName()); + String[] roleIds = user.getRoleIds(); + String[] newRoleIds = Utils.removeItemFromArray(roleIds, getId()); + user.setRoleIds(newRoleIds); + noSQL.saveObject(user); + } + + // 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()); + String[] scopeIds = user.getScopeIds(); + String[] newScopeIds = Utils.removeItemFromArray(scopeIds, getId()); + user.setScopeIds(newScopeIds); + noSQL.saveObject(user); + } + + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java index 0ed96a416e92..7cfe6f509673 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java @@ -1,5 +1,6 @@ package org.keycloak.services.models.nosql.keycloak.data; +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; @@ -9,22 +10,12 @@ * @author Marek Posolda */ @NoSQLCollection(collectionName = "socialLinks") -public class SocialLinkData implements NoSQLObject { +public class SocialLinkData extends AbstractNoSQLObject { - private String id; private String socialUsername; private String socialProvider; private String userId; - @NoSQLId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - @NoSQLField public String getSocialUsername() { return socialUsername; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java index cde0b4294036..c2321fb4308f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java @@ -1,9 +1,12 @@ package org.keycloak.services.models.nosql.keycloak.data; import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; /** * @author Marek Posolda @@ -103,4 +106,15 @@ public String[] getScopeIds() { public void setScopeIds(String[] scopeIds) { this.scopeIds = scopeIds; } + + @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); + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java index a5ffad495bc2..4834316b22c2 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java @@ -2,6 +2,7 @@ import java.util.Date; +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQLCollection; import org.keycloak.services.models.nosql.api.NoSQLField; import org.keycloak.services.models.nosql.api.NoSQLId; @@ -11,9 +12,8 @@ * @author Marek Posolda */ @NoSQLCollection(collectionName = "passwordCredentials") -public class PasswordData implements NoSQLObject { +public class PasswordData extends AbstractNoSQLObject { - private String id; private Date effectiveDate = new Date(); private Date expiryDate; private String encodedHash; @@ -21,15 +21,6 @@ public class PasswordData implements NoSQLObject { private String userId; - @NoSQLId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - @NoSQLField public Date getEffectiveDate() { return effectiveDate; diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index dc8d7111eb51..dabba4072a15 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -62,9 +62,25 @@ protected KeycloakSessionFactory createSessionFactory() { } public static KeycloakSessionFactory buildSessionFactory() { - // EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store"); - // return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager()); - return new MongoDBSessionFactory("localhost", 27017, "keycloak", true); + String sessionFactoryType = System.getProperty("keycloak.sessionFactory", "picketlink"); + if ("mongo".equals(sessionFactoryType)) { + return buildMongoDBSessionFactory(); + } else { + return buildPicketlinkSessionFactory(); + } + } + + private static KeycloakSessionFactory buildPicketlinkSessionFactory() { + EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store"); + return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager()); + } + + private static KeycloakSessionFactory buildMongoDBSessionFactory() { + String host = System.getProperty("keycloak.mongodb.host", "localhost"); + int port = Integer.parseInt(System.getProperty("keycloak.mongodb.port", "27017")); + String dbName = System.getProperty("keycloak.mongodb.databaseName", "keycloak"); + boolean removeAllObjectsOnStartup = Boolean.parseBoolean(System.getProperty("keycloak.mongodb.removeAllObjectsOnStartup", "true")); + return new MongoDBSessionFactory(host, port, dbName, removeAllObjectsOnStartup); } public KeycloakSessionFactory getFactory() { From 4db738689faf2707c46a8880acf231794aa486b7 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 10 Sep 2013 16:52:13 +0200 Subject: [PATCH 05/14] Mongo: Remove realmAdmins when realm is removed. Refactored Converters to support list of embedded objects --- .../services/models/nosql/api/NoSQL.java | 5 + .../nosql/api/query/NoSQLQueryBuilder.java | 3 +- .../models/nosql/api/types/Converter.java | 8 +- .../models/nosql/api/types/ConverterKey.java | 31 ----- .../models/nosql/api/types/TypeConverter.java | 105 +++++++++++++---- .../models/nosql/impl/MongoDBImpl.java | 111 ++++++++++++++++-- .../nosql/impl/MongoDBQueryBuilder.java | 17 +-- .../models/nosql/impl/ObjectInfo.java | 25 ++-- .../services/models/nosql/impl/Utils.java | 75 ------------ .../impl/types/BasicDBListConverter.java | 64 ++++++++++ .../BasicDBListToStringArrayConverter.java | 41 ------- .../impl/types/BasicDBObjectConverter.java | 102 ++++++++++++++++ .../nosql/impl/types/ListConverter.java | 52 ++++++++ .../impl/types/NoSQLObjectConverter.java | 79 ++----------- .../nosql/impl/types/SimpleConverter.java | 30 +++++ .../keycloak/adapters/ApplicationAdapter.java | 19 +-- .../nosql/keycloak/adapters/RealmAdapter.java | 55 +++------ .../models/nosql/keycloak/data/RealmData.java | 14 +-- .../models/nosql/keycloak/data/RoleData.java | 11 +- .../models/nosql/keycloak/data/UserData.java | 28 ++++- .../java/org/keycloak/test/nosql/Address.java | 43 +++++++ .../keycloak/test/nosql/MongoDBModelTest.java | 103 ++++++++++++++++ .../java/org/keycloak/test/nosql/Person.java | 66 +++++++++++ 23 files changed, 739 insertions(+), 348 deletions(-) delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java delete mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java create mode 100644 services/src/test/java/org/keycloak/test/nosql/Address.java create mode 100644 services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java create mode 100644 services/src/test/java/org/keycloak/test/nosql/Person.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java index 1b28fd1e338b..f30256756cc7 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java @@ -4,6 +4,7 @@ import org.keycloak.services.models.nosql.api.query.NoSQLQuery; import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.picketlink.common.properties.Property; /** * @author Marek Posolda @@ -29,4 +30,8 @@ public interface NoSQL { void removeObjects(Class type, NoSQLQuery query); NoSQLQueryBuilder createQueryBuilder(); + + void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush); + + void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java index 341a115120f8..9f2c38303d64 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java @@ -1,6 +1,7 @@ package org.keycloak.services.models.nosql.api.query; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -21,7 +22,7 @@ public NoSQLQueryBuilder andCondition(String name, Object value) { return this; } - public abstract NoSQLQueryBuilder inCondition(String name, Object[] values); + public abstract NoSQLQueryBuilder inCondition(String name, List values); protected void put(String name, Object value) { queryAttributes.put(name, value); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java index 221db2b7a93c..37402015c1c5 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java @@ -8,11 +8,9 @@ */ public interface Converter { - T convertDBObjectToApplicationObject(S dbObject); + S convertObject(T objectToConvert); - S convertApplicationObjectToDBObject(T applicationObject); + Class getConverterObjectType(); - Class getApplicationObjectType(); - - Class getDBObjectType(); + Class getExpectedReturnType(); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java deleted file mode 100644 index 68fc3cb950ef..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/ConverterKey.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.keycloak.services.models.nosql.api.types; - -/** - * @author Marek Posolda - */ -class ConverterKey { - - private final Class applicationObjectType; - private final Class dbObjectType; - - public ConverterKey(Class applicationObjectType, Class dbObjectType) { - this.applicationObjectType = applicationObjectType; - this.dbObjectType = dbObjectType; - } - - @Override - public int hashCode() { - return applicationObjectType.hashCode() * 13 + dbObjectType.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !obj.getClass().equals(this.getClass())) { - return false; - } - - ConverterKey tc = (ConverterKey)obj; - return tc.applicationObjectType.equals(this.applicationObjectType) && tc.dbObjectType.equals(this.dbObjectType); - } - -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java index 3368e4f390a5..fd2020acb01d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java @@ -3,6 +3,8 @@ import java.util.HashMap; import java.util.Map; +import org.picketlink.common.reflection.Reflections; + /** * Registry of converters, which allow to convert application object to database objects. TypeConverter is main entry point to be used by application. * Application can create instance of TypeConverter and then register required Converter objects. @@ -12,38 +14,101 @@ public class TypeConverter { // TODO: Thread-safety support (maybe...) - private Map> converterRegistry = new HashMap>(); + // Converters of Application objects to DB objects + private Map, Converter> appObjectConverters = new HashMap, Converter>(); - public void addConverter(Converter converter) { - ConverterKey converterKey = new ConverterKey(converter.getApplicationObjectType(), converter.getDBObjectType()); - converterRegistry.put(converterKey, converter); - } + // Converters of DB objects to Application objects + private Map, Map, Converter>> dbObjectConverters = new HashMap, Map, Converter>>(); - public T convertDBObjectToApplicationObject(S dbObject, Class expectedApplicationObjectType) { - // TODO: Not type safe as it expects that S type of converter must exactly match type of dbObject. Converter lookup should be more flexible - Class expectedDBObjectType = (Class)dbObject.getClass(); - Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); - return converter.convertDBObjectToApplicationObject(dbObject); + + /** + * Add converter for converting application objects to DB objects + * + * @param converter + */ + public void addAppObjectConverter(Converter converter) { + appObjectConverters.put(converter.getConverterObjectType(), converter); } - public S convertApplicationObjectToDBObject(T applicationObject, Class expectedDBObjectType) { - // TODO: Not type safe as it expects that T type of converter must exactly match type of applicationObject. Converter lookup should be more flexible - Class expectedApplicationObjectType = (Class)applicationObject.getClass(); - Converter converter = getConverter(expectedApplicationObjectType, expectedDBObjectType); - return converter.convertApplicationObjectToDBObject(applicationObject); + /** + * Add converter for converting DB objects to application objects + * + * @param converter + */ + public void addDBObjectConverter(Converter converter) { + Class dbObjectType = converter.getConverterObjectType(); + Class appObjectType = converter.getExpectedReturnType(); + Map, Converter> appObjects = dbObjectConverters.get(dbObjectType); + if (appObjects == null) { + appObjects = new HashMap, Converter>(); + dbObjectConverters.put(dbObjectType, appObjects); + } + appObjects.put(appObjectType, converter); } - private Converter getConverter( Class expectedApplicationObjectType, Class expectedDBObjectType) { - ConverterKey key = new ConverterKey(expectedApplicationObjectType, expectedDBObjectType); - Converter converter = (Converter)converterRegistry.get(key); + + public S convertDBObjectToApplicationObject(Object dbObject, Class expectedApplicationObjectType) { + Class dbObjectType = dbObject.getClass(); + Converter converter; + + Map, 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)appObjects.values().iterator().next(); + } else { + // Try to find converter for requested application type + converter = (Converter)appObjects.get(expectedApplicationObjectType); + } + } if (converter == null) { - throw new IllegalStateException("Can't found converter for expectedApplicationObject=" + expectedApplicationObjectType + ", expectedDBObjectType=" + expectedDBObjectType); + throw new IllegalArgumentException("Can't found converter for type " + dbObjectType + " and expectedApplicationType " + expectedApplicationObjectType); + } + if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) { + throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + + " but we need type " + expectedApplicationObjectType); } - return converter; + return converter.convertObject(dbObject); } + public S convertApplicationObjectToDBObject(Object applicationObject, Class expectedDBObjectType) { + Class appObjectType = applicationObject.getClass(); + Converter converter = (Converter)getAppConverterForType(appObjectType); + if (converter == null) { + throw new IllegalArgumentException("Can't found converter for type " + appObjectType + " in registered appObjectConverters"); + } + if (!expectedDBObjectType.isAssignableFrom(converter.getExpectedReturnType())) { + throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + + " but we need type " + expectedDBObjectType); + } + return converter.convertObject(applicationObject); + } + + // Try to find converter for given type or all it's supertypes + private Converter getAppConverterForType(Class appObjectType) { + Converter converter = (Converter)appObjectConverters.get(appObjectType); + if (converter != null) { + return converter; + } else { + Class[] interfaces = appObjectType.getInterfaces(); + for (Class interface1 : interfaces) { + converter = getAppConverterForType(interface1); + if (converter != null) { + return converter; + } + } + + Class superType = appObjectType.getSuperclass(); + if (superType != null) { + return getAppConverterForType(superType); + } else { + return null; + } + } + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index 0385aeef0cb4..6862a0c86ef8 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -1,11 +1,13 @@ package org.keycloak.services.models.nosql.impl; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; @@ -22,8 +24,11 @@ import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; import org.keycloak.services.models.nosql.api.types.Converter; import org.keycloak.services.models.nosql.api.types.TypeConverter; -import org.keycloak.services.models.nosql.impl.types.BasicDBListToStringArrayConverter; +import org.keycloak.services.models.nosql.impl.types.ListConverter; +import org.keycloak.services.models.nosql.impl.types.BasicDBListConverter; +import org.keycloak.services.models.nosql.impl.types.BasicDBObjectConverter; import org.keycloak.services.models.nosql.impl.types.NoSQLObjectConverter; +import org.keycloak.services.models.nosql.impl.types.SimpleConverter; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; @@ -33,6 +38,8 @@ */ public class MongoDBImpl implements NoSQL { + private static final Class[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class }; + private final DB database; private static final Logger logger = Logger.getLogger(MongoDBImpl.class); @@ -40,14 +47,27 @@ public class MongoDBImpl implements NoSQL { private ConcurrentMap, ObjectInfo> objectInfoCache = new ConcurrentHashMap, ObjectInfo>(); + public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class[] managedDataTypes) { this.database = database; typeConverter = new TypeConverter(); - typeConverter.addConverter(new BasicDBListToStringArrayConverter()); + + for (Class simpleConverterClass : SIMPLE_TYPES) { + SimpleConverter converter = new SimpleConverter(simpleConverterClass); + typeConverter.addAppObjectConverter(converter); + typeConverter.addDBObjectConverter(converter); + } + + // Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list impl will be ArrayList) + typeConverter.addAppObjectConverter(new ListConverter(typeConverter, ArrayList.class)); + typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class)); + typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter)); + for (Class type : managedDataTypes) { - typeConverter.addConverter(new NoSQLObjectConverter(this, typeConverter, type)); getObjectInfo(type); + typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type)); + typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type)); } if (removeAllObjectsAtStartup) { @@ -55,10 +75,10 @@ public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class clazz = object.getClass(); @@ -90,12 +111,12 @@ public void saveObject(NoSQLObject object) { oidProperty.setValue(object, dbObject.getString("_id")); } } else { - BasicDBObject setCommand = new BasicDBObject("$set", dbObject); BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId)); - dbCollection.update(query, setCommand); + dbCollection.update(query, dbObject); } } + @Override public T loadObject(Class type, String oid) { DBCollection dbCollection = getDBCollectionForType(type); @@ -106,6 +127,7 @@ public T loadObject(Class type, String oid) { return typeConverter.convertDBObjectToApplicationObject(dbObject, type); } + @Override public T loadSingleObject(Class type, NoSQLQuery query) { List result = loadObjects(type, query); @@ -119,6 +141,7 @@ public T loadSingleObject(Class type, NoSQLQuery quer } } + @Override public List loadObjects(Class type, NoSQLQuery query) { DBCollection dbCollection = getDBCollectionForType(type); @@ -129,6 +152,7 @@ public List loadObjects(Class type, NoSQLQuery que return convertCursor(type, cursor); } + @Override public void removeObject(NoSQLObject object) { Class type = object.getClass(); @@ -140,6 +164,7 @@ public void removeObject(NoSQLObject object) { removeObject(type, oid); } + @Override public void removeObject(Class type, String oid) { NoSQLObject found = loadObject(type, oid); @@ -155,6 +180,7 @@ public void removeObject(Class type, String oid) { } } + @Override public void removeObjects(Class type, NoSQLQuery query) { List foundObjects = loadObjects(type, query); @@ -172,14 +198,81 @@ public void removeObjects(Class type, NoSQLQuery query) { } } + @Override public NoSQLQueryBuilder createQueryBuilder() { return new MongoDBQueryBuilder(); } + + @Override + public void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush) { + Class type = object.getClass(); + ObjectInfo objectInfo = getObjectInfo(type); + + Property oidProperty = getObjectInfo(type).getOidProperty(); + if (oidProperty == null) { + throw new IllegalArgumentException("List pushes not supported for properties without oid"); + } + + // Add item to list directly in this object + Property listProperty = objectInfo.getPropertyByName(listPropertyName); + if (listProperty == null) { + throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object); + } + + List list = (List)listProperty.getValue(object); + if (list == null) { + list = new ArrayList(); + listProperty.setValue(object, list); + } + list.add(itemToPush); + + // Push item to DB. We always convert whole list, so it's not so optimal... + BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(list, BasicDBList.class); + + BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object))); + BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList); + BasicDBObject setCommand = new BasicDBObject("$set", listObject); + getDBCollectionForType(type).update(query, setCommand); + } + + + @Override + public void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull) { + Class type = object.getClass(); + ObjectInfo objectInfo = getObjectInfo(type); + + Property oidProperty = getObjectInfo(type).getOidProperty(); + if (oidProperty == null) { + throw new IllegalArgumentException("List pulls not supported for properties without oid"); + } + + // Remove item from list directly in this object + Property listProperty = objectInfo.getPropertyByName(listPropertyName); + if (listProperty == null) { + throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object); + } + List list = (List)listProperty.getValue(object); + if (list != null) { + list.remove(itemToPull); + } + + // Pull item from DB + Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class); + BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object))); + BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull); + BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject); + getDBCollectionForType(type).update(query, pullCommand); + } + // Possibility to add user-defined converters - public void addConverter(Converter converter) { - typeConverter.addConverter(converter); + public void addAppObjectConverter(Converter converter) { + typeConverter.addAppObjectConverter(converter); + } + + public void addDBObjectConverter(Converter converter) { + typeConverter.addDBObjectConverter(converter); } public ObjectInfo getObjectInfo(Class objectClass) { diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java index 94b6196dbc94..80f5efba5957 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java @@ -1,5 +1,9 @@ package org.keycloak.services.models.nosql.impl; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + import com.mongodb.BasicDBObject; import org.bson.types.ObjectId; import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; @@ -12,18 +16,17 @@ public class MongoDBQueryBuilder extends NoSQLQueryBuilder { protected MongoDBQueryBuilder() {}; @Override - public NoSQLQueryBuilder inCondition(String name, Object[] values) { + public NoSQLQueryBuilder inCondition(String name, List values) { if (values == null) { - values = new Object[0]; + values = new LinkedList(); } if ("_id".equals(name)) { // we need to convert Strings to ObjectID - ObjectId[] objIds = new ObjectId[values.length]; - for (int i=0 ; i objIds = new ArrayList(); + for (Object object : values) { + ObjectId objectId = new ObjectId(object.toString()); + objIds.add(objectId); } values = objIds; } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java index 27f782c7e29f..da9d27937c33 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java @@ -1,6 +1,10 @@ package org.keycloak.services.models.nosql.impl; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.keycloak.services.models.nosql.api.NoSQLObject; import org.picketlink.common.properties.Property; @@ -16,13 +20,18 @@ public class ObjectInfo { private final Property oidProperty; - private final List> properties; + private final Map> properties; public ObjectInfo(Class objectClass, String dbCollectionName, Property oidProperty, List> properties) { this.objectClass = objectClass; this.dbCollectionName = dbCollectionName; this.oidProperty = oidProperty; - this.properties = properties; + + Map> props= new HashMap>(); + for (Property property : properties) { + props.put(property.getName(), property); + } + this.properties = Collections.unmodifiableMap(props); } public Class getObjectClass() { @@ -37,17 +46,11 @@ public Property getOidProperty() { return oidProperty; } - public List> getProperties() { - return properties; + public Collection> getProperties() { + return properties.values(); } public Property getPropertyByName(String propertyName) { - for (Property property : properties) { - if (propertyName.equals(property.getName())) { - return property; - } - } - - return null; + return properties.get(propertyName); } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java deleted file mode 100644 index 26ab30a87c10..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.keycloak.services.models.nosql.impl; - -import java.lang.reflect.Array; -import java.util.Arrays; - -/** - * @author Marek Posolda - */ -public class Utils { - - private Utils() {}; - - /** - * Add item to the end of array - * - * @param inputArray could be null. In this case, method will return array of length 1 with added item - * @param item must be not-null - * @return array with added item to the end - */ - public static T[] addItemToArray(T[] inputArray, T item) { - if (item == null) { - throw new IllegalArgumentException("item must be non-null"); - } - - T[] outputArray; - if (inputArray == null) { - outputArray = (T[])Array.newInstance(item.getClass(), 1); - } else { - outputArray = Arrays.copyOf(inputArray, inputArray.length + 1); - } - outputArray[outputArray.length - 1] = item; - return outputArray; - } - - /** - * Return true if array contains specified item - * @param array could be null (In this case method always return false) - * @param item can't be null - * @return - */ - public static boolean contains(Object[] array, Object item) { - if (item == null) { - throw new IllegalArgumentException("item must be non-null"); - } - - if (array != null) { - for (Object current : array) { - if (item.equals(current)) { - return true; - } - } - } - return false; - } - - public static T[] removeItemFromArray(T[] inputArray, T item) { - if (item == null) { - throw new IllegalArgumentException("item must be non-null"); - } - - if (inputArray == null) { - return inputArray; - } else { - T[] outputArray = (T[])Array.newInstance(item.getClass(), inputArray.length - 1); - int counter = 0; - for (T object : inputArray) { - if (!item.equals(object)) { - outputArray[counter++] = object; - } - } - return outputArray; - } - } - -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java new file mode 100644 index 000000000000..87941d3f65e1 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java @@ -0,0 +1,64 @@ +package org.keycloak.services.models.nosql.impl.types; + +import java.util.ArrayList; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.services.models.nosql.api.types.TypeConverter; + +/** + * @author Marek Posolda + */ +public class BasicDBListConverter implements Converter { + + private final TypeConverter typeConverter; + + public BasicDBListConverter(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + @Override + public ArrayList convertObject(BasicDBList dbList) { + ArrayList appObjects = new ArrayList(); + Class expectedListElementType = null; + for (Object dbObject : dbList) { + + if (expectedListElementType == null) { + expectedListElementType = findExpectedListElementType(dbObject); + } + + appObjects.add(typeConverter.convertDBObjectToApplicationObject(dbObject, expectedListElementType)); + } + return appObjects; + } + + @Override + public Class getConverterObjectType() { + return BasicDBList.class; + } + + @Override + public Class 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 { + return Object.class; + } + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java deleted file mode 100644 index 0c3f1767439d..000000000000 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListToStringArrayConverter.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.keycloak.services.models.nosql.impl.types; - -import com.mongodb.BasicDBList; -import org.keycloak.services.models.nosql.api.types.Converter; - -/** - * Convert BasicDBList to String[] and viceversa (T needs to be declared as Object as Array is not possible here :/ ) - * - * @author Marek Posolda - */ -public class BasicDBListToStringArrayConverter implements Converter { - - private static final String[] PLACEHOLDER = new String[] {}; - - @Override - public String[] convertDBObjectToApplicationObject(BasicDBList dbObject) { - return dbObject.toArray(PLACEHOLDER); - } - - @Override - public BasicDBList convertApplicationObjectToDBObject(Object applicationObject) { - BasicDBList list = new BasicDBList(); - - String[] array = (String[])applicationObject; - for (String key : array) { - list.add(key); - } - - return list; - } - - @Override - public Class getApplicationObjectType() { - return PLACEHOLDER.getClass(); - } - - @Override - public Class getDBObjectType() { - return BasicDBList.class; - } -} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java new file mode 100644 index 000000000000..4257fb4f5063 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java @@ -0,0 +1,102 @@ +package org.keycloak.services.models.nosql.impl.types; + +import com.mongodb.BasicDBObject; +import org.jboss.resteasy.logging.Logger; +import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.services.models.nosql.api.types.TypeConverter; +import org.keycloak.services.models.nosql.impl.MongoDBImpl; +import org.keycloak.services.models.nosql.impl.ObjectInfo; +import org.picketlink.common.properties.Property; +import org.picketlink.common.reflection.Types; + +/** + * @author Marek Posolda + */ +public class BasicDBObjectConverter implements Converter { + + private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class); + + private final MongoDBImpl mongoDBImpl; + private final TypeConverter typeConverter; + private final Class expectedNoSQLObjectType; + + public BasicDBObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { + this.mongoDBImpl = mongoDBImpl; + this.typeConverter = typeConverter; + this.expectedNoSQLObjectType = expectedNoSQLObjectType; + } + + @Override + public S convertObject(BasicDBObject dbObject) { + if (dbObject == null) { + return null; + } + + ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType); + + S object; + try { + object = expectedNoSQLObjectType.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + for (String key : dbObject.keySet()) { + Object value = dbObject.get(key); + Property property; + + if ("_id".equals(key)) { + // Current property is "id" + Property idProperty = objectInfo.getOidProperty(); + if (idProperty != null) { + idProperty.setValue(object, value.toString()); + } + + } else if ((property = objectInfo.getPropertyByName(key)) != null) { + // 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); + } + } + + return object; + } + + private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) { + if (valueFromDB == null) { + property.setValue(object, null); + return; + } + + Class expectedReturnType = property.getJavaClass(); + // handle primitives + expectedReturnType = Types.boxedClass(expectedReturnType); + + 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 + + ". So can't be assigned as property " + property.getName() + " of " + object.getClass()); + } + } + + @Override + public Class getConverterObjectType() { + return BasicDBObject.class; + } + + @Override + public Class getExpectedReturnType() { + return expectedNoSQLObjectType; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java new file mode 100644 index 000000000000..a9685abad035 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java @@ -0,0 +1,52 @@ +package org.keycloak.services.models.nosql.impl.types; + +import java.util.List; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.services.models.nosql.api.types.TypeConverter; + +/** + * @author Marek Posolda + */ +public class ListConverter implements Converter { + + // 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 listType; + + public ListConverter(TypeConverter typeConverter, Class listType) { + this.typeConverter = typeConverter; + this.listType = listType; + } + + @Override + public BasicDBList convertObject(T appObjectsList) { + 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; + } + + @Override + public Class getConverterObjectType() { + return listType; + } + + @Override + public Class getExpectedReturnType() { + return BasicDBList.class; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java index a3974b30b2aa..c24460787c48 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java @@ -1,5 +1,6 @@ package org.keycloak.services.models.nosql.impl.types; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -29,84 +30,18 @@ public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter } @Override - public T convertDBObjectToApplicationObject(BasicDBObject dbObject) { - if (dbObject == null) { - return null; - } - - ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType); - - T object; - try { - object = expectedNoSQLObjectType.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - for (String key : dbObject.keySet()) { - Object value = dbObject.get(key); - Property property; - - if ("_id".equals(key)) { - // Current property is "id" - Property idProperty = objectInfo.getOidProperty(); - if (idProperty != null) { - idProperty.setValue(object, value.toString()); - } - - } else if ((property = objectInfo.getPropertyByName(key)) != null) { - // 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 - // TODO: logging - // logger.warn("Property with key " + key + " not known for type " + type); - System.err.println("Property with key " + key + " not known for type " + expectedNoSQLObjectType); - } - } - - return object; - } - - private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) { - Class expectedType = property.getJavaClass(); - Class actualType = valueFromDB != null ? valueFromDB.getClass() : expectedType; - - // handle primitives - expectedType = Types.boxedClass(expectedType); - actualType = Types.boxedClass(actualType); - - if (actualType.isAssignableFrom(expectedType)) { - property.setValue(object, valueFromDB); - } else { - // we need to convert - Object convertedValue = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedType); - property.setValue(object, convertedValue); - } - } - - @Override - public BasicDBObject convertApplicationObjectToDBObject(T applicationObject) { + public BasicDBObject convertObject(T applicationObject) { ObjectInfo objectInfo = mongoDBImpl.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(); - List> props = objectInfo.getProperties(); + Collection> props = objectInfo.getProperties(); for (Property property : props) { String propName = property.getName(); Object propValue = property.getValue(applicationObject); - // Check if we have noSQLObject, which is indication that we need to convert recursively - if (propValue instanceof NoSQLObject) { - propValue = typeConverter.convertApplicationObjectToDBObject(propValue, BasicDBObject.class); - } - - dbObject.append(propName, propValue); + Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Types.boxedClass(property.getJavaClass())); + dbObject.put(propName, dbValue); } // Adding attributes @@ -122,12 +57,12 @@ public BasicDBObject convertApplicationObjectToDBObject(T applicationObject) { } @Override - public Class getApplicationObjectType() { + public Class getConverterObjectType() { return expectedNoSQLObjectType; } @Override - public Class getDBObjectType() { + public Class getExpectedReturnType() { return BasicDBObject.class; } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java new file mode 100644 index 000000000000..8dc1b62ba830 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java @@ -0,0 +1,30 @@ +package org.keycloak.services.models.nosql.impl.types; + +import org.keycloak.services.models.nosql.api.types.Converter; + +/** + * @author Marek Posolda + */ +public class SimpleConverter implements Converter { + + private final Class expectedType; + + public SimpleConverter(Class expectedType) { + this.expectedType = expectedType; + } + + @Override + public T convertObject(T objectToConvert) { + return objectToConvert; + } + + @Override + public Class getConverterObjectType() { + return expectedType; + } + + @Override + public Class getExpectedReturnType() { + return expectedType; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java index dd1040094c60..4d7a564425a4 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java @@ -10,9 +10,6 @@ import org.keycloak.services.models.UserModel; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; -import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; -import org.keycloak.services.models.nosql.impl.Utils; import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; import org.keycloak.services.models.nosql.keycloak.data.RoleData; import org.keycloak.services.models.nosql.keycloak.data.UserData; @@ -138,7 +135,7 @@ public List getRoles() { @Override public Set getRoleMappings(UserModel user) { UserData userData = ((UserAdapter)user).getUser(); - String[] roleIds = userData.getRoleIds(); + List roleIds = userData.getRoleIds(); Set result = new HashSet(); @@ -146,7 +143,7 @@ public Set getRoleMappings(UserModel user) { .inCondition("_id", roleIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); - // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + // 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()); @@ -168,19 +165,13 @@ public void addScope(UserModel agent, String roleName) { @Override public void addScope(UserModel agent, RoleModel role) { UserData userData = ((UserAdapter)agent).getUser(); - RoleData roleData = ((RoleAdapter)role).getRole(); - - String[] scopeIds = userData.getScopeIds(); - scopeIds = Utils.addItemToArray(scopeIds, roleData.getId()); - userData.setScopeIds(scopeIds); - - noSQL.saveObject(userData); + noSQL.pushItemToList(userData, "scopeIds", role.getId()); } @Override public Set getScope(UserModel agent) { UserData userData = ((UserAdapter)agent).getUser(); - String[] scopeIds = userData.getScopeIds(); + List scopeIds = userData.getScopeIds(); Set result = new HashSet(); @@ -188,7 +179,7 @@ public Set getScope(UserModel agent) { .inCondition("_id", scopeIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); - // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + // 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()); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java index f7e3e83e0299..2165165d6a50 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -23,7 +23,6 @@ import org.keycloak.services.models.UserModel; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; import org.keycloak.services.models.nosql.keycloak.credentials.PasswordCredentialHandler; import org.keycloak.services.models.nosql.keycloak.credentials.TOTPCredentialHandler; import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; @@ -32,11 +31,7 @@ import org.keycloak.services.models.nosql.keycloak.data.RoleData; import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; import org.keycloak.services.models.nosql.keycloak.data.UserData; -import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; -import org.keycloak.services.models.nosql.impl.Utils; -import org.keycloak.services.models.picketlink.relationships.ResourceRelationship; import org.picketlink.idm.credential.Credentials; -import org.picketlink.idm.query.RelationshipQuery; /** * @author Marek Posolda @@ -323,7 +318,7 @@ public List getRoles() { @Override public List getDefaultRoles() { - String[] defaultRoles = realm.getDefaultRoles(); + List defaultRoles = realm.getDefaultRoles(); NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", defaultRoles) @@ -344,25 +339,20 @@ public void addDefaultRole(String name) { role = addRole(name); } - String[] defaultRoles = realm.getDefaultRoles(); - String[] roleIds = Utils.addItemToArray(defaultRoles, role.getId()); - - realm.setDefaultRoles(roleIds); - updateRealm(); + noSQL.pushItemToList(realm, "defaultRoles", role.getId()); } @Override public void updateDefaultRoles(String[] defaultRoles) { // defaultRoles is array with names of roles. So we need to convert to array of ids - String[] roleIds = new String[defaultRoles.length]; - for (int i=0 ; i roleIds = new ArrayList(); + for (String roleName : defaultRoles) { RoleModel role = getRole(roleName); if (role == null) { role = addRole(roleName); } - roleIds[i] = role.getId(); + roleIds.add(role.getId()); } realm.setDefaultRoles(roleIds); @@ -425,7 +415,7 @@ public ApplicationModel addApplication(String name) { public boolean hasRole(UserModel user, RoleModel role) { UserData userData = ((UserAdapter)user).getUser(); - String[] roleIds = userData.getRoleIds(); + List roleIds = userData.getRoleIds(); String roleId = role.getId(); if (roleIds != null) { for (String currentId : roleIds) { @@ -440,19 +430,13 @@ public boolean hasRole(UserModel user, RoleModel role) { @Override public void grantRole(UserModel user, RoleModel role) { UserData userData = ((UserAdapter)user).getUser(); - RoleData roleData = ((RoleAdapter)role).getRole(); - - String[] roleIds = userData.getRoleIds(); - roleIds = Utils.addItemToArray(roleIds, roleData.getId()); - userData.setRoleIds(roleIds); - - noSQL.saveObject(userData); + noSQL.pushItemToList(userData, "roleIds", role.getId()); } @Override public Set getRoleMappings(UserModel user) { UserData userData = ((UserAdapter)user).getUser(); - String[] roleIds = userData.getRoleIds(); + List roleIds = userData.getRoleIds(); Set result = new HashSet(); @@ -460,7 +444,7 @@ public Set getRoleMappings(UserModel user) { .inCondition("_id", roleIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); - // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... for (RoleData role : roles) { if (getOid().equals(role.getRealmId())) { result.add(role.getName()); @@ -476,19 +460,14 @@ public void addScope(UserModel agent, String roleName) { if (role == null) { throw new RuntimeException("Role not found"); } - RoleData roleData = role.getRole(); - - String[] scopeIds = userData.getScopeIds(); - scopeIds = Utils.addItemToArray(scopeIds, roleData.getId()); - userData.setScopeIds(scopeIds); - noSQL.saveObject(userData); + noSQL.pushItemToList(userData, "scopeIds", role.getId()); } @Override public Set getScope(UserModel agent) { UserData userData = ((UserAdapter)agent).getUser(); - String[] scopeIds = userData.getScopeIds(); + List scopeIds = userData.getScopeIds(); Set result = new HashSet(); @@ -496,7 +475,7 @@ public Set getScope(UserModel agent) { .inCondition("_id", scopeIds) .build(); List roles = noSQL.loadObjects(RoleData.class, query); - // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically... + // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... for (RoleData role : roles) { if (getOid().equals(role.getRealmId())) { result.add(role.getName()); @@ -507,20 +486,16 @@ public Set getScope(UserModel agent) { @Override public boolean isRealmAdmin(UserModel agent) { - String[] realmAdmins = realm.getRealmAdmins(); + List realmAdmins = realm.getRealmAdmins(); String userId = ((UserAdapter)agent).getUser().getId(); - return Utils.contains(realmAdmins, userId); + return realmAdmins.contains(userId); } @Override public void addRealmAdmin(UserModel agent) { UserData userData = ((UserAdapter)agent).getUser(); - String[] currentAdmins = realm.getRealmAdmins(); - String[] newAdmins = Utils.addItemToArray(currentAdmins, userData.getId()); - - realm.setRealmAdmins(newAdmins); - updateRealm(); + noSQL.pushItemToList(realm, "realmAdmins", userData.getId()); } @Override diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java index 42f8ab246ea3..8152c5bee9b0 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java @@ -1,6 +1,7 @@ package org.keycloak.services.models.nosql.keycloak.data; import java.security.SecureRandom; +import java.util.List; import java.util.Random; import java.util.UUID; @@ -32,8 +33,8 @@ public class RealmData implements NoSQLObject { private String publicKeyPem; private String privateKeyPem; - private String[] defaultRoles; - private String[] realmAdmins; + private List defaultRoles; + private List realmAdmins; @NoSQLId public String getOid() { @@ -44,7 +45,6 @@ public void setOid(String oid) { this.oid = oid; } - // TODO: Is ID really needed? It seems that it exists just to workaround picketlink... @NoSQLField public String getId() { return id; @@ -154,20 +154,20 @@ public void setPrivateKeyPem(String privateKeyPem) { } @NoSQLField - public String[] getDefaultRoles() { + public List getDefaultRoles() { return defaultRoles; } - public void setDefaultRoles(String[] defaultRoles) { + public void setDefaultRoles(List defaultRoles) { this.defaultRoles = defaultRoles; } @NoSQLField - public String[] getRealmAdmins() { + public List getRealmAdmins() { return realmAdmins; } - public void setRealmAdmins(String[] realmAdmins) { + public void setRealmAdmins(List realmAdmins) { this.realmAdmins = realmAdmins; } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java index 89138e68c07b..49483a125fea 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java @@ -9,7 +9,6 @@ import org.keycloak.services.models.nosql.api.NoSQLId; import org.keycloak.services.models.nosql.api.NoSQLObject; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.impl.Utils; /** * @author Marek Posolda @@ -81,10 +80,7 @@ public void afterRemove(NoSQL noSQL) { List users = noSQL.loadObjects(UserData.class, query); for (UserData user : users) { logger.info("Removing role " + getName() + " from user " + user.getLoginName()); - String[] roleIds = user.getRoleIds(); - String[] newRoleIds = Utils.removeItemFromArray(roleIds, getId()); - user.setRoleIds(newRoleIds); - noSQL.saveObject(user); + noSQL.pullItemFromList(user, "roleIds", getId()); } // Remove this scope from all users, which has it @@ -95,10 +91,7 @@ public void afterRemove(NoSQL noSQL) { users = noSQL.loadObjects(UserData.class, query); for (UserData user : users) { logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); - String[] scopeIds = user.getScopeIds(); - String[] newScopeIds = Utils.removeItemFromArray(scopeIds, getId()); - user.setScopeIds(newScopeIds); - noSQL.saveObject(user); + noSQL.pullItemFromList(user, "scopeIds", getId()); } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java index c2321fb4308f..83c71511b5fd 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java @@ -1,5 +1,8 @@ package org.keycloak.services.models.nosql.keycloak.data; +import java.util.List; + +import org.jboss.resteasy.logging.Logger; import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; @@ -14,6 +17,8 @@ @NoSQLCollection(collectionName = "users") public class UserData extends AbstractAttributedNoSQLObject { + private static final Logger logger = Logger.getLogger(UserData.class); + private String id; private String loginName; private String firstName; @@ -23,8 +28,8 @@ public class UserData extends AbstractAttributedNoSQLObject { private String realmId; - private String[] roleIds; - private String[] scopeIds; + private List roleIds; + private List scopeIds; @NoSQLId public String getId() { @@ -90,20 +95,20 @@ public void setRealmId(String realmId) { } @NoSQLField - public String[] getRoleIds() { + public List getRoleIds() { return roleIds; } - public void setRoleIds(String[] roleIds) { + public void setRoleIds(List roleIds) { this.roleIds = roleIds; } @NoSQLField - public String[] getScopeIds() { + public List getScopeIds() { return scopeIds; } - public void setScopeIds(String[] scopeIds) { + public void setScopeIds(List scopeIds) { this.scopeIds = scopeIds; } @@ -116,5 +121,16 @@ public void afterRemove(NoSQL noSQL) { // 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 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()); + } } } diff --git a/services/src/test/java/org/keycloak/test/nosql/Address.java b/services/src/test/java/org/keycloak/test/nosql/Address.java new file mode 100644 index 000000000000..8b56c595ae2d --- /dev/null +++ b/services/src/test/java/org/keycloak/test/nosql/Address.java @@ -0,0 +1,43 @@ +package org.keycloak.test.nosql; + +import java.util.List; + +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLField; + +/** + * @author Marek Posolda + */ +public class Address extends AbstractNoSQLObject { + + private String street; + private int number; + private List flatNumbers; + + @NoSQLField + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + @NoSQLField + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + @NoSQLField + public List getFlatNumbers() { + return flatNumbers; + } + + public void setFlatNumbers(List flatNumbers) { + this.flatNumbers = flatNumbers; + } +} diff --git a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java new file mode 100644 index 000000000000..276ea3c2c4f5 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java @@ -0,0 +1,103 @@ +package org.keycloak.test.nosql; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.impl.MongoDBImpl; + +/** + * @author Marek Posolda + */ +public class MongoDBModelTest { + + private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { + Person.class, + Address.class, + }; + + private MongoClient mongoClient; + private NoSQL mongoDB; + + @Before + public void before() throws Exception { + try { + // TODO: authentication support + mongoClient = new MongoClient("localhost", 27017); + + DB db = mongoClient.getDB("keycloakTest"); + mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES); + + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + @After + public void after() throws Exception { + mongoClient.close(); + } + + // @Test + public void mongoModelTest() throws Exception { + // Add some user + Person john = new Person(); + john.setFirstName("john"); + john.setAge(25); + + mongoDB.saveObject(john); + + // Add another user + Person mary = new Person(); + mary.setFirstName("mary"); + mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"})); + + Address addr1 = new Address(); + addr1.setStreet("Elm"); + addr1.setNumber(5); + addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"})); + Address addr2 = new Address(); + List
addresses = new ArrayList
(); + addresses.add(addr1); + addresses.add(addr2); + + mary.setAddresses(addresses); + mongoDB.saveObject(mary); + + Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size()); + + NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build(); + List persons = mongoDB.loadObjects(Person.class, query); + Assert.assertEquals(1, persons.size()); + mary = persons.get(0); + Assert.assertEquals(mary.getFirstName(), "mary"); + Assert.assertTrue(mary.getKids().contains("Paul")); + Assert.assertEquals(2, mary.getAddresses().size()); + Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass()); + + // Test push/pull + mongoDB.pushItemToList(mary, "kids", "Pauline"); + mongoDB.pullItemFromList(mary, "kids", "Paul"); + + Address addr3 = new Address(); + addr3.setNumber(6); + addr3.setStreet("Broadway"); + mongoDB.pushItemToList(mary, "addresses", addr3); + + mary = mongoDB.loadObject(Person.class, mary.getId()); + Assert.assertEquals(3, mary.getKids().size()); + Assert.assertTrue(mary.getKids().contains("Pauline")); + Assert.assertFalse(mary.getKids().contains("Paul")); + Assert.assertEquals(3, mary.getAddresses().size()); + } +} diff --git a/services/src/test/java/org/keycloak/test/nosql/Person.java b/services/src/test/java/org/keycloak/test/nosql/Person.java new file mode 100644 index 000000000000..fa091fa1a2b2 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/nosql/Person.java @@ -0,0 +1,66 @@ +package org.keycloak.test.nosql; + +import java.util.List; + +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.services.models.nosql.api.NoSQLId; + +/** + * @author Marek Posolda + */ +@NoSQLCollection(collectionName = "persons") +public class Person extends AbstractNoSQLObject { + + private String id; + private String firstName; + private int age; + private List kids; + private List
addresses; + + @NoSQLId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @NoSQLField + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + @NoSQLField + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @NoSQLField + public List getKids() { + return kids; + } + + public void setKids(List kids) { + this.kids = kids; + } + + @NoSQLField + public List
getAddresses() { + return addresses; + } + + public void setAddresses(List
addresses) { + this.addresses = addresses; + } +} From 68ed19f15d637b7998a4095d0905bfcafa6ef7ee Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 13 Sep 2013 14:50:24 +0200 Subject: [PATCH 06/14] Support MongoDB in unit tests. Added parameterized test, so ImportTest and AdapterTest work with both picketlink and mongo --- pom.xml | 5 + services/pom.xml | 5 + .../models/nosql/impl/MongoDBImpl.java | 19 +--- .../adapters/MongoDBSessionFactory.java | 6 +- .../resources/KeycloakApplication.java | 24 +++-- .../java/org/keycloak/test/AdapterTest.java | 28 ++---- .../java/org/keycloak/test/ImportTest.java | 27 ++---- .../org/keycloak/test/RealmCreationTest.java | 5 + .../test/common/AbstractKeycloakTest.java | 96 +++++++++++++++++++ .../MongoDBSessionFactoryTestContext.java | 60 ++++++++++++ .../PicketlinkSessionFactoryTestContext.java | 25 +++++ .../common/SessionFactoryTestContext.java | 19 ++++ 12 files changed, 255 insertions(+), 64 deletions(-) create mode 100644 services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java create mode 100644 services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java create mode 100644 services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java create mode 100644 services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java diff --git a/pom.xml b/pom.xml index 99a90cd93023..c2a58bcd103b 100755 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,11 @@ mongo-java-driver 2.11.2 + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 1.27 + diff --git a/services/pom.xml b/services/pom.xml index 7c38f45478a2..8696a5d1e0ac 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -152,6 +152,11 @@ org.mongodb mongo-java-driver + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + junit junit diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index 6862a0c86ef8..44a340a21a53 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -48,7 +48,7 @@ public class MongoDBImpl implements NoSQL { new ConcurrentHashMap, ObjectInfo>(); - public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class[] managedDataTypes) { + public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class[] managedDataTypes) { this.database = database; typeConverter = new TypeConverter(); @@ -70,20 +70,9 @@ public MongoDBImpl(DB database, boolean removeAllObjectsAtStartup, Class type : managedDataTypes) { - ObjectInfo objectInfo = getObjectInfo(type); - String collectionName = objectInfo.getDbCollectionName(); - if (collectionName != null) { - logger.debug("Dropping collection " + collectionName); - - DBCollection dbCollection = this.database.getCollection(collectionName); - dbCollection.drop(); - } else { - logger.debug("Skip removing objects of type " + type + " as it doesn't have it's own collection"); - } - } - logger.info("All objects successfully removed from MongoDB"); + if (dropDatabaseOnStartup) { + this.database.dropDatabase(); + logger.info("Database " + this.database.getName() + " dropped in MongoDB"); } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java index 114f7b74db63..73ba4615b767 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java @@ -43,14 +43,14 @@ public class MongoDBSessionFactory implements KeycloakSessionFactory { private final MongoClient mongoClient; private final NoSQL mongoDB; - public MongoDBSessionFactory(String host, int port, String dbName, boolean removeAllObjectsAtStartup) { - logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, removeAllObjectsAtStartup)); + 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, removeAllObjectsAtStartup, MANAGED_DATA_TYPES); + mongoDB = new MongoDBImpl(db, dropDatabaseOnStartup, MANAGED_DATA_TYPES); } catch (UnknownHostException e) { throw new RuntimeException(e); diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index dabba4072a15..fc8297522460 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -37,6 +37,16 @@ * @version $Revision: 1 $ */ public class KeycloakApplication extends Application { + + public 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 MONGO_HOST = "keycloak.mongodb.host"; + public static final String MONGO_PORT = "keycloak.mongodb.port"; + public static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName"; + public static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup"; + + protected Set singletons = new HashSet(); protected Set> classes = new HashSet>(); @@ -62,8 +72,8 @@ protected KeycloakSessionFactory createSessionFactory() { } public static KeycloakSessionFactory buildSessionFactory() { - String sessionFactoryType = System.getProperty("keycloak.sessionFactory", "picketlink"); - if ("mongo".equals(sessionFactoryType)) { + String sessionFactoryType = System.getProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK); + if (SESSION_FACTORY_MONGO.equals(sessionFactoryType)) { return buildMongoDBSessionFactory(); } else { return buildPicketlinkSessionFactory(); @@ -76,11 +86,11 @@ private static KeycloakSessionFactory buildPicketlinkSessionFactory() { } private static KeycloakSessionFactory buildMongoDBSessionFactory() { - String host = System.getProperty("keycloak.mongodb.host", "localhost"); - int port = Integer.parseInt(System.getProperty("keycloak.mongodb.port", "27017")); - String dbName = System.getProperty("keycloak.mongodb.databaseName", "keycloak"); - boolean removeAllObjectsOnStartup = Boolean.parseBoolean(System.getProperty("keycloak.mongodb.removeAllObjectsOnStartup", "true")); - return new MongoDBSessionFactory(host, port, dbName, removeAllObjectsOnStartup); + String host = System.getProperty(MONGO_HOST, "localhost"); + int port = Integer.parseInt(System.getProperty(MONGO_PORT, "27017")); + String dbName = System.getProperty(MONGO_DB_NAME, "keycloak"); + boolean dropDatabaseOnStartup = Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true")); + return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup); } public KeycloakSessionFactory getFactory() { diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java index 1bdaa822545e..08420363e7c5 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/services/src/test/java/org/keycloak/test/AdapterTest.java @@ -12,6 +12,8 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; +import org.keycloak.test.common.SessionFactoryTestContext; import java.util.HashSet; @@ -24,30 +26,16 @@ * @version $Revision: 1 $ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class AdapterTest { - private KeycloakSessionFactory factory; - private KeycloakSession identitySession; - private RealmManager adapter; +public class AdapterTest extends AbstractKeycloakTest { private RealmModel realmModel; - @Before - public void before() throws Exception { - factory = KeycloakApplication.buildSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - adapter = new RealmManager(identitySession); - } - - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); + public AdapterTest(SessionFactoryTestContext testContext) { + super(testContext); } @Test public void installTest() throws Exception { - new InstallationManager().install(adapter); + new InstallationManager().install(getRealmManager()); } @@ -63,7 +51,7 @@ public void testMe() { @Test public void test1CreateRealm() throws Exception { - realmModel = adapter.createRealm("JUGGLER"); + realmModel = getRealmManager().createRealm("JUGGLER"); realmModel.setAccessCodeLifespan(100); realmModel.setAccessCodeLifespanUserAction(600); realmModel.setCookieLoginAllowed(true); @@ -76,7 +64,7 @@ public void test1CreateRealm() throws Exception { realmModel.addDefaultRole("foo"); System.out.println(realmModel.getId()); - realmModel = adapter.getRealm(realmModel.getId()); + realmModel = getRealmManager().getRealm(realmModel.getId()); Assert.assertNotNull(realmModel); Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100); Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction()); diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java index d426d4e4d372..33348ed25245 100755 --- a/services/src/test/java/org/keycloak/test/ImportTest.java +++ b/services/src/test/java/org/keycloak/test/ImportTest.java @@ -19,6 +19,8 @@ import org.keycloak.models.UserModel; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.SaasService; +import org.keycloak.test.common.AbstractKeycloakTest; +import org.keycloak.test.common.SessionFactoryTestContext; import java.util.List; import java.util.Set; @@ -28,29 +30,15 @@ * @version $Revision: 1 $ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class ImportTest { - private KeycloakSessionFactory factory; - private KeycloakSession identitySession; - private RealmManager manager; - private RealmModel realmModel; - - @Before - public void before() throws Exception { - factory = KeycloakApplication.buildSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - manager = new RealmManager(identitySession); - } +public class ImportTest extends AbstractKeycloakTest { - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); + public ImportTest(SessionFactoryTestContext testContext) { + super(testContext); } @Test public void install() throws Exception { + RealmManager manager = getRealmManager(); RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM); defaultRealm.setName(RealmModel.DEFAULT_REALM); defaultRealm.setEnabled(true); @@ -93,7 +81,7 @@ public void install() throws Exception { List resources = realm.getApplications(); Assert.assertEquals(2, resources.size()); - List realms = identitySession.getRealms(admin); + List realms = getIdentitySession().getRealms(admin); Assert.assertEquals(1, realms.size()); // Test scope relationship @@ -129,6 +117,7 @@ public void install() throws Exception { @Test public void install2() throws Exception { + RealmManager manager = getRealmManager(); RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM); defaultRealm.setName(RealmModel.DEFAULT_REALM); defaultRealm.setEnabled(true); diff --git a/services/src/test/java/org/keycloak/test/RealmCreationTest.java b/services/src/test/java/org/keycloak/test/RealmCreationTest.java index a99042f6c568..725a0d122821 100755 --- a/services/src/test/java/org/keycloak/test/RealmCreationTest.java +++ b/services/src/test/java/org/keycloak/test/RealmCreationTest.java @@ -11,6 +11,11 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; +import org.keycloak.test.common.SessionFactoryTestContext; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.client.Entity; diff --git a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java new file mode 100644 index 000000000000..0e9b692efd09 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java @@ -0,0 +1,96 @@ +package org.keycloak.test.common; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.resources.KeycloakApplication; + +/** + * @author Marek Posolda + */ +@RunWith(Parameterized.class) +public abstract class AbstractKeycloakTest { + + protected static final SessionFactoryTestContext[] TEST_CONTEXTS; + + private final SessionFactoryTestContext testContext; + private KeycloakSessionFactory factory; + private KeycloakSession identitySession; + private RealmManager realmManager; + + // STATIC METHODS + + static + { + // TODO: Disable MongoDB by default and enable it just for some specific maven profile (system property)? + TEST_CONTEXTS = new SessionFactoryTestContext[] { + new PicketlinkSessionFactoryTestContext(), + new MongoDBSessionFactoryTestContext() + }; + } + + @Parameterized.Parameters + public static Iterable parameters() { + List params = new ArrayList(); + + for (SessionFactoryTestContext testContext : TEST_CONTEXTS) { + params.add(new Object[] {testContext}); + } + 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; + } + + @Before + public void before() throws Exception { + testContext.initEnvironment(); + factory = KeycloakApplication.buildSessionFactory(); + identitySession = factory.createSession(); + identitySession.getTransaction().begin(); + realmManager = new RealmManager(identitySession); + } + + @After + public void after() throws Exception { + identitySession.getTransaction().commit(); + identitySession.close(); + factory.close(); + } + + protected RealmManager getRealmManager() { + return realmManager; + } + + protected KeycloakSession getIdentitySession() { + return identitySession; + } + +} diff --git a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java new file mode 100644 index 000000000000..fc723a2137b4 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java @@ -0,0 +1,60 @@ +package org.keycloak.test.common; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +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.models.KeycloakSessionFactory; +import org.keycloak.services.resources.KeycloakApplication; + +/** + * @author Marek Posolda + */ +public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext { + + protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class); + private static final int PORT = 27777; + + 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"); + + // Null this, so other tests are not affected + System.setProperty(KeycloakApplication.SESSION_FACTORY, ""); + } + + @Override + public void initEnvironment() { + System.setProperty(KeycloakApplication.SESSION_FACTORY, KeycloakApplication.SESSION_FACTORY_MONGO); + System.setProperty(KeycloakApplication.MONGO_HOST, "localhost"); + System.setProperty(KeycloakApplication.MONGO_PORT, String.valueOf(PORT)); + System.setProperty(KeycloakApplication.MONGO_DB_NAME, "keycloakTest"); + System.setProperty(KeycloakApplication.MONGO_DROP_DB_ON_STARTUP, "true"); + } +} diff --git a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java new file mode 100644 index 000000000000..7ffbbf359d1c --- /dev/null +++ b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java @@ -0,0 +1,25 @@ +package org.keycloak.test.common; + +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.resources.KeycloakApplication; + +/** + * @author Marek Posolda + */ +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() { + System.setProperty(KeycloakApplication.SESSION_FACTORY, KeycloakApplication.SESSION_FACTORY_PICKETLINK); + } +} diff --git a/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java new file mode 100644 index 000000000000..3bbc4bd65500 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java @@ -0,0 +1,19 @@ +package org.keycloak.test.common; + +import org.keycloak.services.models.KeycloakSessionFactory; + +/** + * @author Marek Posolda + */ +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(); +} From 58d862819a90986d4e0be693a0fb59f2fb5d8577 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 13 Sep 2013 20:48:21 +0200 Subject: [PATCH 07/14] Added performance tests to testsuite to compare Picketlink+JPA+MySQL with Mongo. --- examples/as7-eap-demo/server/pom.xml | 5 +- .../models/picketlink/RealmAdapter.java | 3 + .../relationships/SocialLinkRelationship.java | 16 ++ pom.xml | 72 +++++++- services/pom.xml | 4 +- .../nosql/keycloak/adapters/NoSQLSession.java | 4 + .../nosql/keycloak/adapters/RealmAdapter.java | 11 ++ .../nosql/keycloak/data/SocialLinkData.java | 12 ++ testsuite/pom.xml | 166 ++++++++++++++++++ .../BaseJMeterPerformanceTest.java | 141 +++++++++++++++ .../performance/CreateRealmsWorker.java | 101 +++++++++++ .../performance/CreateUsersWorker.java | 120 +++++++++++++ .../testsuite/performance/PerfTestUtils.java | 46 +++++ .../performance/ReadUsersWorker.java | 127 ++++++++++++++ .../performance/RemoveUsersWorker.java | 72 ++++++++ .../testsuite/performance/Worker.java | 19 ++ testsuite/src/test/jmeter/jmeter.properties | 20 +++ testsuite/src/test/jmeter/mongo_test.jmx | 39 ++++ testsuite/src/test/jmeter/system.properties | 79 +++++++++ .../META-INF/persistence-performance.xml | 40 +++++ 20 files changed, 1092 insertions(+), 5 deletions(-) create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java create mode 100644 testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java create mode 100644 testsuite/src/test/jmeter/jmeter.properties create mode 100644 testsuite/src/test/jmeter/mongo_test.jmx create mode 100644 testsuite/src/test/jmeter/system.properties create mode 100644 testsuite/src/test/resources/META-INF/persistence-performance.xml diff --git a/examples/as7-eap-demo/server/pom.xml b/examples/as7-eap-demo/server/pom.xml index 7027dc52cb90..f7e619e543c6 100755 --- a/examples/as7-eap-demo/server/pom.xml +++ b/examples/as7-eap-demo/server/pom.xml @@ -118,7 +118,10 @@ com.h2database h2 - 1.3.161 + + + org.mongodb + mongo-java-driver junit diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java index 6c62007bb514..d5412cee08ad 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java @@ -819,6 +819,7 @@ public UserModel getUserBySocialLink(SocialLinkModel socialLink) { RelationshipQuery query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class); query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider()); query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername()); + query.setParameter(SocialLinkRelationship.REALM, realm.getName()); List results = query.getResultList(); if (results.isEmpty()) { return null; @@ -850,6 +851,7 @@ public void addSocialLink(UserModel user, SocialLinkModel socialLink) { relationship.setUser(((UserAdapter)user).getUser()); relationship.setSocialProvider(socialLink.getSocialProvider()); relationship.setSocialUsername(socialLink.getSocialUsername()); + relationship.setRealm(realm.getName()); getRelationshipManager().add(relationship); } @@ -860,6 +862,7 @@ public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { relationship.setUser(((UserAdapter)user).getUser()); relationship.setSocialProvider(socialLink.getSocialProvider()); relationship.setSocialUsername(socialLink.getSocialUsername()); + relationship.setRealm(realm.getName()); getRelationshipManager().remove(relationship); } diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java index e9be9d467b19..da8f04f01ce7 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/relationships/SocialLinkRelationship.java @@ -3,6 +3,7 @@ import org.picketlink.idm.model.AbstractAttributedType; import org.picketlink.idm.model.Attribute; import org.picketlink.idm.model.Relationship; +import org.picketlink.idm.model.annotation.AttributeProperty; import org.picketlink.idm.model.sample.User; import org.picketlink.idm.query.AttributeParameter; import org.picketlink.idm.query.RelationshipQueryParameter; @@ -21,6 +22,10 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider"); public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername"); + // realm is needed to allow searching as combination socialUsername+socialProvider may not be unique + // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2") + public static final AttributeParameter REALM = new AttributeParameter("realm"); + public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() { @Override @@ -39,6 +44,7 @@ public void setUser(User user) { this.user = user; } + @AttributeProperty public String getSocialProvider() { return (String)getAttribute("socialProvider").getValue(); } @@ -47,6 +53,7 @@ public void setSocialProvider(String socialProvider) { setAttribute(new Attribute("socialProvider", socialProvider)); } + @AttributeProperty public String getSocialUsername() { return (String)getAttribute("socialUsername").getValue(); } @@ -54,4 +61,13 @@ public String getSocialUsername() { public void setSocialUsername(String socialProviderUserId) { setAttribute(new Attribute("socialUsername", socialProviderUserId)); } + + @AttributeProperty + public String getRealm() { + return (String)getAttribute("realm").getValue(); + } + + public void setRealm(String realm) { + setAttribute(new Attribute("realm", realm)); + } } diff --git a/pom.xml b/pom.xml index c2a58bcd103b..9ab5c51f5bbe 100755 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,13 @@ 3.0.4.Final 1.0.0.Beta12 2.5.0.Beta6 + 2.11.2 + 3.1.1.GA + 1.0.1.Final + 3.6.6.Final + 1.3.161 + 1.6.1 + 1.6.1 http://keycloak.org @@ -177,7 +184,7 @@ org.jboss.logging jboss-logging - 3.1.1.GA + ${jboss.logging.version} junit @@ -187,7 +194,17 @@ org.hibernate.javax.persistence hibernate-jpa-2.0-api - 1.0.1.Final + ${hibernate.javax.persistence.version} + + + com.h2database + h2 + ${h2.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} com.google.api-client @@ -259,6 +276,42 @@ de.flapdoodle.embed.mongo 1.27 + + org.apache.jmeter + ApacheJMeter_java + 2.9 + + + dom4j + dom4j + ${dom4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + mysql + mysql-connector-java + 5.1.25 + + + + org.jboss.arquillian + arquillian-bom + 1.1.1.Final + pom + import + + + org.jboss.arquillian.extension + arquillian-drone-bom + 1.2.0.Beta1 + pom + import + @@ -340,6 +393,21 @@ false + + com.lazerycode.jmeter + jmeter-maven-plugin + 1.8.1 + + + com.lazerycode.jmeter + jmeter-analysis-maven-plugin + 1.0.4 + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + diff --git a/services/pom.xml b/services/pom.xml index 8696a5d1e0ac..b35bef413549 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -147,10 +147,12 @@ org.picketlink picketlink-common + provided org.mongodb mongo-java-driver + provided de.flapdoodle.embed @@ -170,13 +172,11 @@ com.h2database h2 - 1.3.161 test org.hibernate hibernate-entitymanager - 3.6.6.Final test diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java index 838a6a4b6008..71cd3a1e581f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java @@ -43,6 +43,10 @@ public RealmModel createRealm(String 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); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java index 2165165d6a50..85c9fcdb0de6 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -273,6 +273,15 @@ public UserAdapter addUser(String username) { return new UserAdapter(userData, noSQL); } + // This method doesn't exists on interface actually + public void removeUser(String name) { + NoSQLQuery query = noSQL.createQueryBuilder() + .andCondition("loginName", name) + .andCondition("realmId", getOid()) + .build(); + noSQL.removeObjects(UserData.class, query); + } + @Override public RoleAdapter getRole(String name) { NoSQLQuery query = noSQL.createQueryBuilder() @@ -659,6 +668,7 @@ public UserModel getUserBySocialLink(SocialLinkModel socialLink) { NoSQLQuery query = noSQL.createQueryBuilder() .andCondition("socialProvider", socialLink.getSocialProvider()) .andCondition("socialUsername", socialLink.getSocialUsername()) + .andCondition("realmId", getOid()) .build(); SocialLinkData socialLinkData = noSQL.loadSingleObject(SocialLinkData.class, query); @@ -696,6 +706,7 @@ public void addSocialLink(UserModel user, SocialLinkModel socialLink) { socialLinkData.setSocialProvider(socialLink.getSocialProvider()); socialLinkData.setSocialUsername(socialLink.getSocialUsername()); socialLinkData.setUserId(userData.getId()); + socialLinkData.setRealmId(getOid()); noSQL.saveObject(socialLinkData); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java index 7cfe6f509673..46b1c2622d71 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java @@ -15,6 +15,9 @@ public class SocialLinkData extends AbstractNoSQLObject { private String socialUsername; private String socialProvider; private String userId; + // realmId is needed to allow searching as combination socialUsername+socialProvider may not be unique + // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2") + private String realmId; @NoSQLField public String getSocialUsername() { @@ -42,4 +45,13 @@ public String getUserId() { public void setUserId(String userId) { this.userId = userId; } + + @NoSQLField + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } } diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 7c5278d89966..adb5aa3e7edd 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -182,6 +182,10 @@ org.seleniumhq.selenium selenium-java + + org.apache.jmeter + ApacheJMeter_java + @@ -193,6 +197,17 @@ 1.6 + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + @@ -219,5 +234,156 @@ + + + performance-tests + + + + com.lazerycode.jmeter + jmeter-maven-plugin + + + jmeter-tests + verify + + jmeter + + + + + + org.keycloak + keycloak-testsuite + ${project.version} + test-jar + + + org.keycloak + keycloak-services + ${project.version} + + + org.jboss.resteasy + jaxrs-api + ${resteasy.version} + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version} + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + commons-io + commons-io + + + + + org.jboss.logging + jboss-logging + ${jboss.logging.version} + + + org.picketlink + picketlink-idm-impl + ${picketlink.version} + + + org.picketlink + picketlink-idm-simple-schema + ${picketlink.version} + + + org.picketlink + picketlink-config + ${picketlink.version} + + + org.mongodb + mongo-java-driver + ${mongo.driver.version} + + + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + ${hibernate.javax.persistence.version} + + + com.h2database + h2 + ${h2.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} + + + dom4j + dom4j + ${dom4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + mysql + mysql-connector-java + + + + + + + com.lazerycode.jmeter + jmeter-analysis-maven-plugin + + + jmeter-tests-analyze + verify + + analyze + + + ${project.build.directory}/jmeter/results/*.jtl + ${project.build.directory}/jmeter/results + false + + + + + + + + + + + + + + + + + + diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java new file mode 100644 index 000000000000..bc38e6d7bd3a --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java @@ -0,0 +1,141 @@ +package org.keycloak.testsuite.performance; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.KeycloakSessionFactory; +import org.keycloak.services.models.KeycloakTransaction; +import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; +import org.keycloak.services.resources.KeycloakApplication; + +/** + * @author Marek Posolda + */ +public class BaseJMeterPerformanceTest extends AbstractJavaSamplerClient { + + + private static FutureTask factoryProvider = new FutureTask(new Callable() { + + @Override + public KeycloakSessionFactory call() throws Exception { + return KeycloakApplication.buildSessionFactory(); + } + + }); + private static AtomicInteger counter = new AtomicInteger(); + + private KeycloakSessionFactory factory; + // private KeycloakSession identitySession; + private Worker worker; + private boolean setupSuccess = false; + + + // Executed once per JMeter thread + @Override + public void setupTest(JavaSamplerContext context) { + super.setupTest(context); + + worker = getWorker(); + + factory = getFactory(); + KeycloakSession identitySession = factory.createSession(); + KeycloakTransaction transaction = identitySession.getTransaction(); + transaction.begin(); + + int workerId = counter.getAndIncrement(); + try { + worker.setup(workerId, identitySession); + setupSuccess = true; + } finally { + if (setupSuccess) { + transaction.commit(); + } else { + transaction.rollback(); + } + identitySession.close(); + } + } + + private static KeycloakSessionFactory getFactory() { + factoryProvider.run(); + try { + return factoryProvider.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + private Worker getWorker() { + String workerClass = System.getProperty("keycloak.perf.workerClass"); + if (workerClass == null) { + throw new IllegalArgumentException("System property keycloak.perf.workerClass needs to be provided"); + } + + try { + Class workerClazz = Class.forName(workerClass); + return (Worker)workerClazz.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + @Override + public SampleResult runTest(JavaSamplerContext context) { + SampleResult result = new SampleResult(); + result.sampleStart(); + + if (!setupSuccess) { + getLogger().error("setupTest didn't executed successfully. Skipping"); + result.setResponseCode("500"); + result.sampleEnd(); + result.setSuccessful(true); + return result; + } + + KeycloakSession identitySession = factory.createSession(); + KeycloakTransaction transaction = identitySession.getTransaction(); + try { + transaction.begin(); + + worker.run(result, identitySession); + + result.setResponseCodeOK(); + transaction.commit(); + } catch (Exception e) { + getLogger().error("Error during worker processing", e); + result.setResponseCode("500"); + transaction.rollback(); + } finally { + result.sampleEnd(); + result.setSuccessful(true); + identitySession.close(); + } + + return result; + } + + + // Executed once per JMeter thread + @Override + public void teardownTest(JavaSamplerContext context) { + super.teardownTest(context); + + if (worker != null) { + worker.tearDown(); + } + + // TODO: Assumption is that tearDownTest is executed for each setupTest. Verify if it's always true... + if (counter.decrementAndGet() == 0) { + if (factory != null) { + factory.close(); + } + } + } +} diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java new file mode 100644 index 000000000000..a3b37e091ff7 --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java @@ -0,0 +1,101 @@ +package org.keycloak.testsuite.performance; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.models.ApplicationModel; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.RealmModel; + +/** + * @author Marek Posolda + */ +public class CreateRealmsWorker implements Worker { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NUMBER_OF_REALMS_IN_EACH_REPORT = 100; + + private static AtomicInteger realmCounter = new AtomicInteger(0); + + private int offset; + private int appsPerRealm; + private int rolesPerRealm; + private int defaultRolesPerRealm; + private int rolesPerApp; + private boolean createRequiredCredentials; + + @Override + public void setup(int workerId, KeycloakSession identitySession) { + offset = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.realms.offset", Integer.class); + appsPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.appsPerRealm", Integer.class); + rolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerRealm", Integer.class); + defaultRolesPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.defaultRolesPerRealm", Integer.class); + rolesPerApp = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.rolesPerApp", Integer.class); + createRequiredCredentials = PerfTestUtils.readSystemProperty("keycloak.perf.createRealms.createRequiredCredentials", Boolean.class); + + realmCounter.compareAndSet(0, offset); + + StringBuilder logBuilder = new StringBuilder("Read setup: ") + .append("offset=" + offset) + .append(", appsPerRealm=" + appsPerRealm) + .append(", rolesPerRealm=" + rolesPerRealm) + .append(", defaultRolesPerRealm=" + defaultRolesPerRealm) + .append(", rolesPerApp=" + rolesPerApp) + .append(", createRequiredCredentials=" + createRequiredCredentials); + log.info(logBuilder.toString()); + } + + @Override + public void run(SampleResult result, KeycloakSession identitySession) { + int realmNumber = realmCounter.getAndIncrement(); + String realmName = PerfTestUtils.getRealmName(realmNumber); + RealmManager realmManager = new RealmManager(identitySession); + RealmModel realm = realmManager.createRealm(realmName, realmName); + + // Add roles + for (int i=1 ; i<=rolesPerRealm ; i++) { + realm.addRole(PerfTestUtils.getRoleName(realmNumber, i)); + } + + // Add default roles + for (int i=1 ; i<=defaultRolesPerRealm ; i++) { + realm.addDefaultRole(PerfTestUtils.getDefaultRoleName(realmNumber, i)); + } + + // Add applications + for (int i=1 ; i<=appsPerRealm ; i++) { + ApplicationModel application = realm.addApplication(PerfTestUtils.getApplicationName(realmNumber, i)); + for (int j=1 ; j<=rolesPerApp ; j++) { + application.addRole(PerfTestUtils.getApplicationRoleName(realmNumber, i, j)); + } + } + + // Add required credentials + if (createRequiredCredentials) { + realmManager.addRequiredCredential(realm, CredentialRepresentation.PASSWORD); + realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.PASSWORD); + realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.PASSWORD); + realmManager.addRequiredCredential(realm, CredentialRepresentation.TOTP); + realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.TOTP); + realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.TOTP); + realmManager.addRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT); + realmManager.addResourceRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT); + realmManager.addOAuthClientRequiredCredential(realm, CredentialRepresentation.CLIENT_CERT); + } + + log.info("Finished creation of realm " + realmName); + + int labelC = ((realmNumber - 1) / NUMBER_OF_REALMS_IN_EACH_REPORT) * NUMBER_OF_REALMS_IN_EACH_REPORT; + result.setSampleLabel("CreateRealms " + (labelC + 1) + "-" + (labelC + NUMBER_OF_REALMS_IN_EACH_REPORT)); + } + + @Override + public void tearDown() { + } + +} diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java new file mode 100644 index 000000000000..f4279442d38e --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java @@ -0,0 +1,120 @@ +package org.keycloak.testsuite.performance; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.RoleModel; +import org.keycloak.services.models.SocialLinkModel; +import org.keycloak.services.models.UserCredentialModel; +import org.keycloak.services.models.UserModel; + +/** + * @author Marek Posolda + */ +public class CreateUsersWorker implements Worker { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000; + + // Total number of users created during whole test + private static AtomicInteger totalUserCounter = new AtomicInteger(); + + // Adding users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker + private int userCounterInRealm = 0; + private String realmId; + + private int realmsOffset; + private boolean addBasicUserAttributes; + private boolean addDefaultRoles; + private boolean addPassword; + private int socialLinksPerUserCount; + + @Override + public void setup(int workerId, KeycloakSession identitySession) { + realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.realms.offset", Integer.class); + addBasicUserAttributes = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addBasicUserAttributes", Boolean.class); + addDefaultRoles = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addDefaultRoles", Boolean.class); + addPassword = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.addPassword", Boolean.class); + socialLinksPerUserCount = PerfTestUtils.readSystemProperty("keycloak.perf.createUsers.socialLinksPerUserCount", Integer.class); + + int realmNumber = realmsOffset + workerId; + realmId = PerfTestUtils.getRealmName(realmNumber); + + StringBuilder logBuilder = new StringBuilder("Read setup: ") + .append("realmsOffset=" + realmsOffset) + .append(", addBasicUserAttributes=" + addBasicUserAttributes) + .append(", addDefaultRoles=" + addDefaultRoles) + .append(", addPassword=" + addPassword) + .append(", socialLinksPerUserCount=" + socialLinksPerUserCount) + .append(", realmId=" + realmId); + log.info(logBuilder.toString()); + } + + @Override + public void run(SampleResult result, KeycloakSession identitySession) { + // We need to obtain realm first + RealmModel realm = identitySession.getRealm(realmId); + if (realm == null) { + throw new IllegalStateException("Realm '" + realmId + "' not found"); + } + + int userNumber = ++userCounterInRealm; + int totalUserNumber = totalUserCounter.incrementAndGet(); + + String username = PerfTestUtils.getUsername(userNumber); + + UserModel user = realm.addUser(username); + + // Add basic user attributes (NOTE: Actually backend is automatically upgraded during each setter call) + if (addBasicUserAttributes) { + user.setFirstName(username + "FN"); + user.setLastName(username + "LN"); + user.setEmail(username + "@email.com"); + } + + // Adding default roles of realm to user + if (addDefaultRoles) { + for (RoleModel role : realm.getDefaultRoles()) { + realm.grantRole(user, role); + } + } + + // Creating password (will be same as username) + if (addPassword) { + UserCredentialModel password = new UserCredentialModel(); + password.setType(CredentialRepresentation.PASSWORD); + password.setValue(username); + realm.updateCredential(user, password); + } + + // Creating some socialLinks + for (int i=0 ; iMarek Posolda + */ +public class PerfTestUtils { + + public static T readSystemProperty(String propertyName, Class expectedClass) { + String propAsString = System.getProperty(propertyName); + if (propAsString == null || propAsString.length() == 0) { + throw new IllegalArgumentException("Property '" + propertyName + "' not specified"); + } + + if (Integer.class.equals(expectedClass)) { + return expectedClass.cast(Integer.parseInt(propAsString)); + } else if (Boolean.class.equals(expectedClass)) { + return expectedClass.cast(Boolean.valueOf(propAsString)); + } else { + throw new IllegalArgumentException("Not supported type " + expectedClass); + } + } + + public static String getRealmName(int realmNumber) { + return "realm" + realmNumber; + } + + public static String getApplicationName(int realmNumber, int applicationNumber) { + return getRealmName(realmNumber) + "application" + applicationNumber; + } + + public static String getRoleName(int realmNumber, int roleNumber) { + return getRealmName(realmNumber) + "role" + roleNumber; + } + + public static String getDefaultRoleName(int realmNumber, int defaultRoleNumber) { + return getRealmName(realmNumber) + "defrole" + defaultRoleNumber; + } + + public static String getApplicationRoleName(int realmNumber, int applicationNumber, int roleNumber) { + return getApplicationName(realmNumber, applicationNumber) + "role" + roleNumber; + } + + public static String getUsername(int userNumber) { + return "user" + userNumber; + } +} diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java new file mode 100644 index 000000000000..14399195126e --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java @@ -0,0 +1,127 @@ +package org.keycloak.testsuite.performance; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.SocialLinkModel; +import org.keycloak.services.models.UserModel; + +/** + * @author Marek Posolda + */ +public class ReadUsersWorker implements Worker { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NUMBER_OF_ITERATIONS_IN_EACH_REPORT = 5000; + + // Total number of iterations read during whole test + private static AtomicInteger totalIterationCounter = new AtomicInteger(); + + // Reading users will always start from 1. Each worker thread needs to read users to single realm, which is dedicated just for this worker + private int userCounterInRealm = 0; + + private int realmsOffset; + private int readUsersPerIteration; + private int countOfUsersPerRealm; + private boolean readRoles; + private boolean readScopes; + private boolean readPassword; + private boolean readSocialLinks; + private boolean searchBySocialLinks; + + private String realmId; + private int iterationNumber; + + @Override + public void setup(int workerId, KeycloakSession identitySession) { + realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.realms.offset", Integer.class); + readUsersPerIteration = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readUsersPerIteration", Integer.class); + countOfUsersPerRealm = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.countOfUsersPerRealm", Integer.class); + readRoles = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readRoles", Boolean.class); + readScopes = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readScopes", Boolean.class); + readPassword = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readPassword", Boolean.class); + readSocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.readSocialLinks", Boolean.class); + searchBySocialLinks = PerfTestUtils.readSystemProperty("keycloak.perf.readUsers.searchBySocialLinks", Boolean.class); + + int realmNumber = realmsOffset + workerId; + realmId = PerfTestUtils.getRealmName(realmNumber); + + StringBuilder logBuilder = new StringBuilder("Read setup: ") + .append("realmsOffset=" + realmsOffset) + .append(", readUsersPerIteration=" + readUsersPerIteration) + .append(", countOfUsersPerRealm=" + countOfUsersPerRealm) + .append(", readRoles=" + readRoles) + .append(", readScopes=" + readScopes) + .append(", readPassword=" + readPassword) + .append(", readSocialLinks=" + readSocialLinks) + .append(", searchBySocialLinks=" + searchBySocialLinks) + .append(", realmId=" + realmId); + log.info(logBuilder.toString()); + } + + @Override + public void run(SampleResult result, KeycloakSession identitySession) { + // We need to obtain realm first + RealmModel realm = identitySession.getRealm(realmId); + if (realm == null) { + throw new IllegalStateException("Realm '" + realmId + "' not found"); + } + + int totalIterationNumber = totalIterationCounter.incrementAndGet(); + String lastUsername = null; + + for (int i=0 ; i countOfUsersPerRealm) { + userCounterInRealm = 1; + } + + String username = PerfTestUtils.getUsername(userCounterInRealm); + lastUsername = username; + + UserModel user = realm.getUser(username); + + // Read roles of user in realm + if (readRoles) { + realm.getRoleMappings(user); + } + + // Read scopes of user in realm + if (readScopes) { + realm.getScope(user); + } + + // Validate password (shoould be same as username) + if (readPassword) { + realm.validatePassword(user, username); + } + + // Read socialLinks of user + if (readSocialLinks) { + realm.getSocialLinks(user); + } + + // Try to search by social links + if (searchBySocialLinks) { + SocialLinkModel socialLink = new SocialLinkModel("facebook", username); + realm.getUserBySocialLink(socialLink); + } + } + + log.info("Finished iteration " + ++iterationNumber + " in ReadUsers test for " + realmId + " worker. Last read user " + lastUsername + " in realm: " + realmId); + + int labelC = ((totalIterationNumber - 1) / NUMBER_OF_ITERATIONS_IN_EACH_REPORT) * NUMBER_OF_ITERATIONS_IN_EACH_REPORT; + result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_ITERATIONS_IN_EACH_REPORT)); + } + + @Override + public void tearDown() { + } +} diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java new file mode 100644 index 000000000000..966024ac73eb --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java @@ -0,0 +1,72 @@ +package org.keycloak.testsuite.performance; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.keycloak.services.models.KeycloakSession; +import org.keycloak.services.models.RealmModel; +import org.keycloak.services.models.SocialLinkModel; +import org.keycloak.services.models.UserModel; +import org.keycloak.services.models.nosql.keycloak.adapters.RealmAdapter; +import org.keycloak.services.resources.KeycloakApplication; + +/** + * @author Marek Posolda + */ +public class RemoveUsersWorker implements Worker { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private static final int NUMBER_OF_USERS_IN_EACH_REPORT = 5000; + + // Total number of users removed during whole test + private static AtomicInteger totalUserCounter = new AtomicInteger(); + + // Removing users will always start from 1. Each worker thread needs to add users to single realm, which is dedicated just for this worker + private int userCounterInRealm = 0; + private RealmModel realm; + + private int realmsOffset; + + @Override + public void setup(int workerId, KeycloakSession identitySession) { + realmsOffset = PerfTestUtils.readSystemProperty("keycloak.perf.removeUsers.realms.offset", Integer.class); + + int realmNumber = realmsOffset + workerId; + String realmId = PerfTestUtils.getRealmName(realmNumber); + realm = identitySession.getRealm(realmId); + if (realm == null) { + throw new IllegalStateException("Realm '" + realmId + "' not found"); + } + + log.info("Read setup: realmsOffset=" + realmsOffset); + } + + @Override + public void run(SampleResult result, KeycloakSession identitySession) { + int userNumber = ++userCounterInRealm; + int totalUserNumber = totalUserCounter.incrementAndGet(); + + String username = PerfTestUtils.getUsername(userNumber); + + // TODO: Not supported in model actually. We support operation just in MongoDB + // UserModel user = realm.removeUser(username); + if (KeycloakApplication.SESSION_FACTORY_MONGO.equals(System.getProperty(KeycloakApplication.SESSION_FACTORY))) { + RealmAdapter mongoRealm = (RealmAdapter)realm; + mongoRealm.removeUser(username); + } else { + throw new IllegalArgumentException("Actually removing of users is supported just for MongoDB"); + } + + log.info("Finished removing of user " + username + " in realm: " + realm.getId()); + + int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT; + result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT)); + } + + @Override + public void tearDown() { + } +} diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java b/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java new file mode 100644 index 000000000000..6060f49a3fa4 --- /dev/null +++ b/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java @@ -0,0 +1,19 @@ +package org.keycloak.testsuite.performance; + +import org.apache.jmeter.samplers.SampleResult; +import org.apache.log.Logger; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.models.KeycloakSession; + +/** + * @author Marek Posolda + */ +public interface Worker { + + void setup(int workerId, KeycloakSession identitySession); + + void run(SampleResult result, KeycloakSession identitySession); + + void tearDown(); + +} diff --git a/testsuite/src/test/jmeter/jmeter.properties b/testsuite/src/test/jmeter/jmeter.properties new file mode 100644 index 000000000000..39b6ce46abbb --- /dev/null +++ b/testsuite/src/test/jmeter/jmeter.properties @@ -0,0 +1,20 @@ +#Thu Mar 07 18:46:04 BRT 2013 +not_in_menu=HTML Parameter Mask,HTTP User Parameter Modifier +xml.parser=org.apache.xerces.parsers.SAXParser +cookies=cookies +wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser +HTTPResponse.parsers=htmlParser wmlParser +remote_hosts=127.0.0.1 +system.properties=system.properties +beanshell.server.file=../extras/startup.bsh +log_level.jmeter.junit=DEBUG +sampleresult.timestamp.start=true +jmeter.laf.mac=System +log_level.jorphan=INFO +classfinder.functions.contain=.functions. +user.properties=user.properties +wmlParser.types=text/vnd.wap.wml +log_level.jmeter=DEBUG +classfinder.functions.notContain=.gui. +htmlParser.types=text/html application/xhtml+xml application/xml text/xml +upgrade_properties=/bin/upgrade.properties \ No newline at end of file diff --git a/testsuite/src/test/jmeter/mongo_test.jmx b/testsuite/src/test/jmeter/mongo_test.jmx new file mode 100644 index 000000000000..b96dcf36d1cf --- /dev/null +++ b/testsuite/src/test/jmeter/mongo_test.jmx @@ -0,0 +1,39 @@ + + + + + + false + false + + + + + + + + continue + + false + 10 + + 1 + 0 + 1362689985000 + 1362689985000 + false + + + + + + + + + + org.keycloak.testsuite.performance.BaseJMeterPerformanceTest + + + + + \ No newline at end of file diff --git a/testsuite/src/test/jmeter/system.properties b/testsuite/src/test/jmeter/system.properties new file mode 100644 index 000000000000..26d24aab9b6f --- /dev/null +++ b/testsuite/src/test/jmeter/system.properties @@ -0,0 +1,79 @@ +## Choose implementation of KeycloakSessionFactory +# keycloak.sessionFactory=picketlink +keycloak.sessionFactory=mongo + +## Configure JPA (just hbm2ddl schema configurable here. Rest of the stuff in META-INF/persistence.xml) +keycloak.jpa.hbm2ddl.auto=create +# keycloak.jpa.hbm2ddl.auto=update + + +## Configure MongoDB (Useful just when keycloak.sessionFactory=mongo) +keycloak.mongodb.host=localhost +keycloak.mongodb.port=27017 +keycloak.mongodb.databaseName=keycloakPerfTest +# Should be DB dropped at startup of the test? +keycloak.mongodb.dropDatabaseOnStartup=true + + +## Specify Keycloak worker class +keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateRealmsWorker +# keycloak.perf.workerClass=org.keycloak.testsuite.performance.CreateUsersWorker +# keycloak.perf.workerClass=org.keycloak.testsuite.performance.ReadUsersWorker +# keycloak.perf.workerClass=org.keycloak.testsuite.performance.RemoveUsersWorker + + +## Properties for CreateRealms test. This test is used to create some realms. +# Each iteration of single worker thread will add one realm and it will add some roles, defaultRoles, credentials and applications to it +# Offset where to start creating realms. Count (total number of realms to create) is configurable as number of JMeter threads*loopCount +# For example: if offset==1 and in JMeter properties we have LoopController.loops=10 and num_threads=2 then we will create 20 realms in total and we will create realms "realm1" - "realm10" +# NOTE: Count (total number of realms to create) is configurable as number of JMeter threads*loopCount +keycloak.perf.createRealms.realms.offset=1 +# Count of apps per each realm (For example if count=5, we will create apps like "realm1app1" - "realm1app5" for realm "realm1" +# and similarly for all other created realms) +keycloak.perf.createRealms.appsPerRealm=5 +# Count of roles per each realm (For example if count=5, we will create roles like "realm1role1" - "realm1role5" for realm "realm1" +# and similarly for all other created realms) +keycloak.perf.createRealms.rolesPerRealm=5 +# Count of default roles per each realm (For example if count=2, we will create roles like "realm1defrole1" and "realm1defrole2" +# for realm "realm1" and similarly for all other created realms) +keycloak.perf.createRealms.defaultRolesPerRealm=2 +# Count of roles per each application (For example if count=3 we will have roles "realm1app1role1" - "realm1app1role3" for realm=1 and application=1 +# (if realmsCount=10, appsPerRealm=5 it will be 150 application roles totally) +keycloak.perf.createRealms.rolesPerApp=3 +# Whether to create required credentials in each realm (If true, we will create "password", "totp" and client-certificate) +keycloak.perf.createRealms.createRequiredCredentials=true + + +## Properties for CreateUsers test. This test is used to create some users +# Each iteration of single worker thread will add one user and it will add some default roles, passwords and bind him with some social accounts +# Each worker will use separate realm dedicated just for him, so each worker will create user1, user2, ... , userN . N (number of users to create per realm) +# is configurable in JMeter configuration as loopCount. Total number of created users for whole test will be threads*loopCount +# NOTE: For each thread, the corresponding realm must already exists +# Realm where to start creating users +keycloak.perf.createUsers.realms.offset=1 +# Whether to add basic attributes like firstName/lastName/email to each user +keycloak.perf.createUsers.addBasicUserAttributes=true +# Whether to add all default roles of realm to this user +keycloak.perf.createUsers.addDefaultRoles=true +# Whether to add password to this user +keycloak.perf.createUsers.addPassword=true +# Number of social links to create for each user. Possible values are 0, 1, 2, 3 (For 3 it will create Facebook, Twitter and Google) +keycloak.perf.createUsers.socialLinksPerUserCount=0 + + +## Properties for ReadUsers test. This test is used to read some users from DB and alternatively read some of his properties (passwords, roles, scopes, socialLinks) +keycloak.perf.readUsers.realms.offset=1 +# Number of read users in each iteration +keycloak.perf.readUsers.readUsersPerIteration=5 +# Number of users to read in each realm. After reading all 2000 users, reading will start again from user1 +keycloak.perf.readUsers.countOfUsersPerRealm=2000 +keycloak.perf.readUsers.readRoles=true +keycloak.perf.readUsers.readScopes=true +keycloak.perf.readUsers.readPassword=true +keycloak.perf.readUsers.readSocialLinks=false +keycloak.perf.readUsers.searchBySocialLinks=false + + +## Properties for RemoveUsers worker. This test is used to remove some users from DB (and all their stuff actually) +# Similarly like in CreateUsers test, each worker works just with one realm. Number of removed users depends on JMeter property loopCount +keycloak.perf.removeUsers.realms.offset=1 diff --git a/testsuite/src/test/resources/META-INF/persistence-performance.xml b/testsuite/src/test/resources/META-INF/persistence-performance.xml new file mode 100644 index 000000000000..1dff64183fae --- /dev/null +++ b/testsuite/src/test/resources/META-INF/persistence-performance.xml @@ -0,0 +1,40 @@ + + + org.hibernate.ejb.HibernatePersistence + + org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity + org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity + org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity + org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity + org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity + org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity + org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity + org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity + org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity + org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity + org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity + org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity + org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity + org.keycloak.services.models.picketlink.mappings.RealmEntity + org.keycloak.services.models.picketlink.mappings.ApplicationEntity + + true + + + + + + + + + + + + + + + + \ No newline at end of file From 86cf0909098610737a75f61c362d244624045ea6 Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 18 Sep 2013 23:51:42 +0200 Subject: [PATCH 08/14] Adapt MongoDB impl with latest changes on UserModel and RealmModel. Support for Enums in Converter SPI --- .../models/nosql/api/types/TypeConverter.java | 14 ++--- .../models/nosql/impl/MongoDBImpl.java | 6 ++ .../impl/types/BasicDBListConverter.java | 11 +++- .../models/nosql/impl/types/ClassCache.java | 37 ++++++++++++ .../impl/types/EnumToStringConverter.java | 26 +++++++++ .../impl/types/NoSQLObjectConverter.java | 2 +- .../impl/types/StringToEnumConverter.java | 32 +++++++++++ .../nosql/keycloak/adapters/RealmAdapter.java | 23 +++++++- .../nosql/keycloak/adapters/UserAdapter.java | 57 ++++++++++++++++++- .../models/nosql/keycloak/data/RealmData.java | 20 +++++++ .../models/nosql/keycloak/data/UserData.java | 43 ++++++++++++-- .../keycloak/test/nosql/MongoDBModelTest.java | 9 +++ .../java/org/keycloak/test/nosql/Person.java | 35 ++++++++++++ 13 files changed, 298 insertions(+), 17 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java index fd2020acb01d..00c8e5e01a32 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java @@ -60,17 +60,17 @@ public S convertDBObjectToApplicationObject(Object dbObject, Class expect converter = (Converter)appObjects.values().iterator().next(); } else { // Try to find converter for requested application type - converter = (Converter)appObjects.get(expectedApplicationObjectType); + converter = (Converter)getAppConverterForType(expectedApplicationObjectType, appObjects); } } if (converter == null) { throw new IllegalArgumentException("Can't found converter for type " + dbObjectType + " and expectedApplicationType " + expectedApplicationObjectType); } - if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) { + /*if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) { throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + " but we need type " + expectedApplicationObjectType); - } + } */ return converter.convertObject(dbObject); } @@ -78,7 +78,7 @@ public S convertDBObjectToApplicationObject(Object dbObject, Class expect public S convertApplicationObjectToDBObject(Object applicationObject, Class expectedDBObjectType) { Class appObjectType = applicationObject.getClass(); - Converter converter = (Converter)getAppConverterForType(appObjectType); + Converter converter = (Converter)getAppConverterForType(appObjectType, appObjectConverters); if (converter == null) { throw new IllegalArgumentException("Can't found converter for type " + appObjectType + " in registered appObjectConverters"); } @@ -90,14 +90,14 @@ public S convertApplicationObjectToDBObject(Object applicationObject, Class< } // Try to find converter for given type or all it's supertypes - private Converter getAppConverterForType(Class appObjectType) { + private static Converter getAppConverterForType(Class appObjectType, Map, Converter> appObjectConverters) { Converter converter = (Converter)appObjectConverters.get(appObjectType); if (converter != null) { return converter; } else { Class[] interfaces = appObjectType.getInterfaces(); for (Class interface1 : interfaces) { - converter = getAppConverterForType(interface1); + converter = getAppConverterForType(interface1, appObjectConverters); if (converter != null) { return converter; } @@ -105,7 +105,7 @@ public S convertApplicationObjectToDBObject(Object applicationObject, Class< Class superType = appObjectType.getSuperclass(); if (superType != null) { - return getAppConverterForType(superType); + return getAppConverterForType(superType, appObjectConverters); } else { return null; } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java index 44a340a21a53..a6b49300b3d7 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java @@ -24,11 +24,13 @@ import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; import org.keycloak.services.models.nosql.api.types.Converter; import org.keycloak.services.models.nosql.api.types.TypeConverter; +import org.keycloak.services.models.nosql.impl.types.EnumToStringConverter; import org.keycloak.services.models.nosql.impl.types.ListConverter; import org.keycloak.services.models.nosql.impl.types.BasicDBListConverter; import org.keycloak.services.models.nosql.impl.types.BasicDBObjectConverter; import org.keycloak.services.models.nosql.impl.types.NoSQLObjectConverter; import org.keycloak.services.models.nosql.impl.types.SimpleConverter; +import org.keycloak.services.models.nosql.impl.types.StringToEnumConverter; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; @@ -64,6 +66,10 @@ public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class type : managedDataTypes) { getObjectInfo(type); typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type)); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java index 87941d3f65e1..75dae75259e1 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java @@ -58,7 +58,16 @@ private Class findExpectedListElementType(Object dbObject) { throw new RuntimeException(cnfe); } } else { - return Object.class; + // 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(); } } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java new file mode 100644 index 000000000000..cf4372b561d5 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java @@ -0,0 +1,37 @@ +package org.keycloak.services.models.nosql.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 Marek Posolda + */ +public class ClassCache { + + public static final String SPLIT = "###"; + private static final ClassCache INSTANCE = new ClassCache(); + + private ConcurrentMap> cache = new ConcurrentHashMap>(); + + 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; + } + +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java new file mode 100644 index 000000000000..fd32db51ac79 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java @@ -0,0 +1,26 @@ +package org.keycloak.services.models.nosql.impl.types; + +import org.keycloak.services.models.nosql.api.types.Converter; + +/** + * @author Marek Posolda + */ +public class EnumToStringConverter implements Converter { + + // 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(); + } + + @Override + public Class getConverterObjectType() { + return Enum.class; + } + + @Override + public Class getExpectedReturnType() { + return String.class; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java index c24460787c48..cd783672c3bd 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java @@ -40,7 +40,7 @@ public BasicDBObject convertObject(T applicationObject) { String propName = property.getName(); Object propValue = property.getValue(applicationObject); - Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Types.boxedClass(property.getJavaClass())); + Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Object.class); dbObject.put(propName, dbValue); } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java new file mode 100644 index 000000000000..d40594384af4 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java @@ -0,0 +1,32 @@ +package org.keycloak.services.models.nosql.impl.types; + +import org.keycloak.services.models.nosql.api.types.Converter; + +/** + * @author Marek Posolda + */ +public class StringToEnumConverter implements Converter { + + @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); + } + + String className = objectToConvert.substring(0, index); + String enumValue = objectToConvert.substring(index + 3); + Class clazz = (Class)ClassCache.getInstance().getOrLoadClass(className); + return Enum.valueOf(clazz, enumValue); + } + + @Override + public Class getConverterObjectType() { + return String.class; + } + + @Override + public Class getExpectedReturnType() { + return Enum.class; + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java index 85c9fcdb0de6..24b0146867c7 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -139,6 +139,17 @@ public void setRegistrationAllowed(boolean registrationAllowed) { updateRealm(); } + @Override + public boolean isVerifyEmail() { + return realm.isVerifyEmail(); + } + + @Override + public void setVerifyEmail(boolean verifyEmail) { + realm.setVerifyEmail(verifyEmail); + updateRealm(); + } + @Override public int getTokenLifespan() { return realm.getTokenLifespan(); @@ -161,6 +172,17 @@ public void setAccessCodeLifespan(int accessCodeLifespan) { updateRealm(); } + @Override + public int getAccessCodeLifespanUserAction() { + return realm.getAccessCodeLifespanUserAction(); + } + + @Override + public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { + realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction); + updateRealm(); + } + @Override public String getPublicKeyPem() { return realm.getPublicKeyPem(); @@ -266,7 +288,6 @@ public UserAdapter addUser(String username) { UserData userData = new UserData(); userData.setLoginName(username); - userData.setEnabled(true); userData.setRealmId(getOid()); noSQL.saveObject(userData); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java index e9085688b742..24c3a29c1883 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java @@ -1,5 +1,6 @@ package org.keycloak.services.models.nosql.keycloak.adapters; +import java.util.List; import java.util.Map; import org.keycloak.services.models.UserModel; @@ -32,9 +33,15 @@ public boolean isEnabled() { } @Override - public void setEnabled(boolean enabled) { - user.setEnabled(enabled); - noSQL.saveObject(user); + public void setStatus(Status status) { + user.setStatus(status); + noSQL.saveObject(user); + } + + @Override + public Status getStatus() { + Status status = user.getStatus(); + return status != null ? status : Status.ENABLED; } @Override @@ -70,6 +77,17 @@ public void setEmail(String email) { noSQL.saveObject(user); } + @Override + public boolean isEmailVerified() { + return user.isEmailVerified(); + } + + @Override + public void setEmailVerified(boolean verified) { + user.setEmailVerified(verified); + noSQL.saveObject(user); + } + @Override public void setAttribute(String name, String value) { user.setAttribute(name, value); @@ -94,4 +112,37 @@ public Map getAttributes() { public UserData getUser() { return user; } + + @Override + public List getRequiredActions() { + List requiredActions = user.getRequiredActions(); + + // Compatibility with picketlink impl + if (requiredActions == null || requiredActions.size() == 0) { + return null; + } + + return requiredActions; + } + + @Override + public void addRequiredAction(RequiredAction action) { + noSQL.pushItemToList(user, "requiredActions", action); + } + + @Override + public void removeRequiredAction(RequiredAction action) { + noSQL.pullItemFromList(user, "requiredActions", action); + } + + @Override + public boolean isTotp() { + return user.isTotp(); + } + + @Override + public void setTotp(boolean totp) { + user.setTotp(totp); + noSQL.saveObject(user); + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java index 8152c5bee9b0..70d0a511a57b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java @@ -26,10 +26,12 @@ public class RealmData implements NoSQLObject { private boolean sslNotRequired; private boolean cookieLoginAllowed; private boolean registrationAllowed; + private boolean verifyEmail; private boolean social; private boolean automaticRegistrationAfterSocialLogin; private int tokenLifespan; private int accessCodeLifespan; + private int accessCodeLifespanUserAction; private String publicKeyPem; private String privateKeyPem; @@ -99,6 +101,15 @@ 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 isSocial() { return social; @@ -135,6 +146,15 @@ 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; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java index 83c71511b5fd..6206289adb3e 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java @@ -3,6 +3,7 @@ import java.util.List; import org.jboss.resteasy.logging.Logger; +import org.keycloak.services.models.UserModel; import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.NoSQLCollection; @@ -24,12 +25,15 @@ public class UserData extends AbstractAttributedNoSQLObject { private String firstName; private String lastName; private String email; - private boolean enabled; + private boolean emailVerified; + private boolean totp; + private UserModel.Status status; private String realmId; private List roleIds; private List scopeIds; + private List requiredActions; @NoSQLId public String getId() { @@ -77,12 +81,34 @@ public void setEmail(String email) { } @NoSQLField + public boolean isEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(boolean emailVerified) { + this.emailVerified = emailVerified; + } + public boolean isEnabled() { - return enabled; + return !UserModel.Status.DISABLED.equals(getStatus()); } - public void setEnabled(boolean enabled) { - this.enabled = enabled; + @NoSQLField + public boolean isTotp() { + return totp; + } + + public void setTotp(boolean totp) { + this.totp = totp; + } + + @NoSQLField + public UserModel.Status getStatus() { + return status; + } + + public void setStatus(UserModel.Status status) { + this.status = status; } @NoSQLField @@ -112,6 +138,15 @@ public void setScopeIds(List scopeIds) { this.scopeIds = scopeIds; } + @NoSQLField + public List getRequiredActions() { + return requiredActions; + } + + public void setRequiredActions(List requiredActions) { + this.requiredActions = requiredActions; + } + @Override public void afterRemove(NoSQL noSQL) { NoSQLQuery query = noSQL.createQueryBuilder() diff --git a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java index 276ea3c2c4f5..b59fdffe5942 100644 --- a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java +++ b/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java @@ -54,6 +54,7 @@ public void mongoModelTest() throws Exception { Person john = new Person(); john.setFirstName("john"); john.setAge(25); + john.setGender(Person.Gender.MALE); mongoDB.saveObject(john); @@ -72,6 +73,9 @@ public void mongoModelTest() throws Exception { addresses.add(addr2); mary.setAddresses(addresses); + mary.setMainAddress(addr1); + mary.setGender(Person.Gender.FEMALE); + mary.setGenders(Arrays.asList(new Person.Gender[] {Person.Gender.FEMALE})); mongoDB.saveObject(mary); Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size()); @@ -99,5 +103,10 @@ public void mongoModelTest() throws Exception { Assert.assertTrue(mary.getKids().contains("Pauline")); Assert.assertFalse(mary.getKids().contains("Paul")); Assert.assertEquals(3, mary.getAddresses().size()); + Address mainAddress = mary.getMainAddress(); + Assert.assertEquals("Elm", mainAddress.getStreet()); + Assert.assertEquals(5, mainAddress.getNumber()); + Assert.assertEquals(Person.Gender.FEMALE, mary.getGender()); + Assert.assertTrue(mary.getGenders().contains(Person.Gender.FEMALE)); } } diff --git a/services/src/test/java/org/keycloak/test/nosql/Person.java b/services/src/test/java/org/keycloak/test/nosql/Person.java index fa091fa1a2b2..3ead51235f51 100644 --- a/services/src/test/java/org/keycloak/test/nosql/Person.java +++ b/services/src/test/java/org/keycloak/test/nosql/Person.java @@ -18,6 +18,10 @@ public class Person extends AbstractNoSQLObject { private int age; private List kids; private List
addresses; + private Address mainAddress; + private Gender gender; + private List genders; + @NoSQLId public String getId() { @@ -46,6 +50,24 @@ public void setAge(int age) { this.age = age; } + @NoSQLField + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @NoSQLField + public List getGenders() { + return genders; + } + + public void setGenders(List genders) { + this.genders = genders; + } + @NoSQLField public List getKids() { return kids; @@ -63,4 +85,17 @@ public List
getAddresses() { public void setAddresses(List
addresses) { this.addresses = addresses; } + + @NoSQLField + public Address getMainAddress() { + return mainAddress; + } + + public void setMainAddress(Address mainAddress) { + this.mainAddress = mainAddress; + } + + public static enum Gender { + MALE, FEMALE + } } From ae4bd42ff7754174687f81a77c549f5e5229bf3a Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 19 Sep 2013 22:12:52 +0200 Subject: [PATCH 09/14] Add support for TOTP in MongoDB --- .../adapters/MongoDBSessionFactory.java | 2 + .../nosql/keycloak/adapters/RealmAdapter.java | 16 +-- .../PasswordCredentialHandler.java | 11 +- .../credentials/TOTPCredentialHandler.java | 129 +++++++++++++++++- .../keycloak/data/credentials/OTPData.java | 66 +++++++++ .../managers/AuthenticationManagerTest.java | 22 +-- 6 files changed, 217 insertions(+), 29 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java index 73ba4615b767..6f1d7f760e8e 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java @@ -20,6 +20,7 @@ import org.keycloak.services.models.nosql.keycloak.data.UserData; import org.keycloak.services.models.nosql.impl.MongoDBImpl; import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; +import org.keycloak.services.models.nosql.keycloak.data.credentials.OTPData; import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; /** @@ -36,6 +37,7 @@ public class MongoDBSessionFactory implements KeycloakSessionFactory { RoleData.class, RequiredCredentialData.class, PasswordData.class, + OTPData.class, SocialLinkData.class, ApplicationData.class }; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java index 24b0146867c7..4d613d50c402 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java @@ -32,6 +32,8 @@ import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; import org.keycloak.services.models.nosql.keycloak.data.UserData; import org.picketlink.idm.credential.Credentials; +import org.picketlink.idm.credential.Password; +import org.picketlink.idm.credential.TOTPCredentials; /** * @author Marek Posolda @@ -44,9 +46,9 @@ public class RealmAdapter implements RealmModel { protected volatile transient PublicKey publicKey; protected volatile transient PrivateKey privateKey; - // TODO: likely shouldn't be static. And setup is not called ATM, which means that it's not possible to configure stuff like PasswordEncoder etc. - private static PasswordCredentialHandler passwordCredentialHandler = new PasswordCredentialHandler(); - private static TOTPCredentialHandler totpCredentialHandler = new TOTPCredentialHandler(); + // TODO: likely shouldn't be static. And ATM, just empty map is passed -> It's not possible to configure stuff like PasswordEncoder etc. + private static PasswordCredentialHandler passwordCredentialHandler = new PasswordCredentialHandler(new HashMap()); + private static TOTPCredentialHandler totpCredentialHandler = new TOTPCredentialHandler(new HashMap()); public RealmAdapter(RealmData realmData, NoSQL noSQL) { this.realm = realmData; @@ -659,7 +661,8 @@ public boolean validatePassword(UserModel user, String password) { @Override public boolean validateTOTP(UserModel user, String password, String token) { - return false; //To change body of implemented methods use File | Settings | File Templates. + Credentials.Status status = totpCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password, token, null); + return status == Credentials.Status.VALID; } @Override @@ -667,10 +670,7 @@ public void updateCredential(UserModel user, UserCredentialModel cred) { if (cred.getType().equals(CredentialRepresentation.PASSWORD)) { passwordCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), null, null); } else if (cred.getType().equals(CredentialRepresentation.TOTP)) { - // TODO -// TOTPCredential totp = new TOTPCredential(cred.getValue()); -// totp.setDevice(cred.getDevice()); -// idm.updateCredential(((UserAdapter)user).getUser(), totp); + totpCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), cred.getDevice(), null, null); } else if (cred.getType().equals(CredentialRepresentation.CLIENT_CERT)) { // TODO // X509Certificate cert = null; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java index 77099873dd38..52d706e7f604 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java @@ -1,15 +1,11 @@ package org.keycloak.services.models.nosql.keycloak.credentials; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.util.Date; import java.util.Map; import java.util.UUID; import org.keycloak.services.models.nosql.api.NoSQL; import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; -import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; import org.keycloak.services.models.nosql.keycloak.data.UserData; import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; import org.picketlink.idm.credential.Credentials; @@ -34,7 +30,11 @@ public class PasswordCredentialHandler { private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);; - public void setup(Map options) { + public PasswordCredentialHandler(Map options) { + setup(options); + } + + private void setup(Map options) { if (options != null) { Object providedEncoder = options.get(PASSWORD_ENCODER); @@ -64,6 +64,7 @@ public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordTo // 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()); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java index a5dc8d2e4491..84b5f0a5e31a 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java @@ -1,5 +1,21 @@ package org.keycloak.services.models.nosql.keycloak.credentials; +import java.util.Date; +import java.util.Map; + +import org.keycloak.services.models.nosql.api.NoSQL; +import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.services.models.nosql.keycloak.data.credentials.OTPData; +import org.picketlink.idm.credential.Credentials; +import org.picketlink.idm.credential.util.TimeBasedOTP; + +import static org.picketlink.common.util.StringUtil.isNullOrEmpty; +import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_ALGORITHM; +import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_DELAY_WINDOW; +import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS; +import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_NUMBER_DIGITS; + /** * Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler} * @@ -7,5 +23,116 @@ */ public class TOTPCredentialHandler extends PasswordCredentialHandler { - // TODO: implement + 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 options) { + super(options); + setup(options); + } + + private void setup(Map 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 options, String key, String defaultValue) { + Object value = options.get(key); + + if (value != null) { + return String.valueOf(value); + } + + return defaultValue; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java new file mode 100644 index 000000000000..cf4fef864f0b --- /dev/null +++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java @@ -0,0 +1,66 @@ +package org.keycloak.services.models.nosql.keycloak.data.credentials; + +import java.util.Date; + +import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; +import org.keycloak.services.models.nosql.api.NoSQLCollection; +import org.keycloak.services.models.nosql.api.NoSQLField; + +/** + * @author Marek Posolda + */ +@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; + } +} diff --git a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java index ce3ed3d8c18a..a2049fd8bded 100755 --- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java +++ b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java @@ -18,24 +18,20 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; +import org.keycloak.test.common.SessionFactoryTestContext; import org.picketlink.idm.credential.util.TimeBasedOTP; -public class AuthenticationManagerTest { +public class AuthenticationManagerTest extends AbstractKeycloakTest { - private RealmManager adapter; private AuthenticationManager am; - private KeycloakSessionFactory factory; private MultivaluedMap formData; - private KeycloakSession identitySession; private TimeBasedOTP otp; private RealmModel realm; private UserModel user; - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); + public AuthenticationManagerTest(SessionFactoryTestContext testContext) { + super(testContext); } @Test @@ -134,12 +130,8 @@ public void authFormWithTotpMissingTotp() { @Before public void before() throws Exception { - factory = KeycloakApplication.buildSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - adapter = new RealmManager(identitySession); - - realm = adapter.createRealm("Test"); + super.before(); + realm = getRealmManager().createRealm("Test"); realm.setAccessCodeLifespan(100); realm.setCookieLoginAllowed(true); realm.setEnabled(true); From 71cd9cffa4240abdb4f6020ed2c9f23bcd621b8a Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 9 Oct 2013 19:41:59 +0200 Subject: [PATCH 10/14] Rebased MongoDB on top of latest master. Divide testsuite into integration and performance submodules --- .../models/utils/KeycloakSessionUtils.java | 15 + model/jpa/pom.xml | 2 +- model/mongo/pom.xml | 77 ++++ .../api/AbstractAttributedNoSQLObject.java | 2 +- .../mongo}/api/AbstractNoSQLObject.java | 2 +- .../mongo}/api/AttributedNoSQLObject.java | 2 +- .../org/keycloak/models/mongo}/api/NoSQL.java | 6 +- .../models/mongo}/api/NoSQLCollection.java | 2 +- .../models/mongo}/api/NoSQLField.java | 2 +- .../keycloak/models/mongo}/api/NoSQLId.java | 2 +- .../models/mongo}/api/NoSQLObject.java | 2 +- .../models/mongo}/api/query/NoSQLQuery.java | 3 +- .../mongo}/api/query/NoSQLQueryBuilder.java | 2 +- .../models/mongo}/api/types/Converter.java | 2 +- .../mongo}/api/types/TypeConverter.java | 2 +- .../models/mongo}/impl/MongoDBImpl.java | 36 +- .../mongo}/impl/MongoDBQueryBuilder.java | 4 +- .../models/mongo}/impl/ObjectInfo.java | 4 +- .../impl/types/BasicDBListConverter.java | 6 +- .../impl/types/BasicDBObjectConverter.java | 16 +- .../models/mongo}/impl/types/ClassCache.java | 2 +- .../impl/types/EnumToStringConverter.java | 4 +- .../mongo}/impl/types/ListConverter.java | 6 +- .../impl/types/NoSQLObjectConverter.java | 16 +- .../mongo}/impl/types/SimpleConverter.java | 4 +- .../impl/types/StringToEnumConverter.java | 4 +- .../keycloak/adapters/ApplicationAdapter.java | 89 +++- .../adapters/MongoDBSessionFactory.java | 38 +- .../keycloak/adapters/NoSQLSession.java | 23 +- .../keycloak/adapters/NoSQLTransaction.java | 4 +- .../keycloak/adapters/OAuthClientAdapter.java | 43 ++ .../keycloak/adapters/RealmAdapter.java | 140 +++++-- .../mongo}/keycloak/adapters/RoleAdapter.java | 8 +- .../mongo}/keycloak/adapters/UserAdapter.java | 37 +- .../PasswordCredentialHandler.java | 10 +- .../credentials/TOTPCredentialHandler.java | 10 +- .../mongo}/keycloak/data/ApplicationData.java | 24 +- .../mongo/keycloak/data/OAuthClientData.java | 62 +++ .../mongo}/keycloak/data/RealmData.java | 27 +- .../keycloak/data/RequiredCredentialData.java | 11 +- .../models/mongo}/keycloak/data/RoleData.java | 16 +- .../mongo}/keycloak/data/SocialLinkData.java | 10 +- .../models/mongo}/keycloak/data/UserData.java | 38 +- .../keycloak/data/credentials/OTPData.java | 8 +- .../data/credentials/PasswordData.java | 10 +- .../keycloak/models/mongo/test}/Address.java | 6 +- .../models/mongo/test}/MongoDBModelTest.java | 11 +- .../keycloak/models/mongo/test}/Person.java | 10 +- model/picketlink/pom.xml | 2 +- .../picketlink/PicketlinkKeycloakSession.java | 8 +- model/pom.xml | 1 + pom.xml | 3 +- services/pom.xml | 6 + .../resources/KeycloakApplication.java | 9 +- .../java/org/keycloak/test/AdapterTest.java | 2 + .../org/keycloak/test/RealmCreationTest.java | 5 - .../test/common/AbstractKeycloakTest.java | 4 +- .../MongoDBSessionFactoryTestContext.java | 3 - .../PicketlinkSessionFactoryTestContext.java | 1 - .../common/SessionFactoryTestContext.java | 2 - testsuite/integration/pom.xml | 223 ++++++++++ .../keycloak/testutils/KeycloakServer.java | 0 .../org/keycloak/testutils/MailServer.java | 0 .../org/keycloak/testutils/TotpGenerator.java | 0 .../main/resources/META-INF/persistence.xml | 0 .../testsuite/ApplicationServlet.java | 0 .../org/keycloak/testsuite/Constants.java | 0 .../org/keycloak/testsuite/DummySocial.java | 0 .../testsuite/DummySocialServlet.java | 0 .../org/keycloak/testsuite/OAuthClient.java | 0 .../RequiredActionEmailVerificationTest.java | 0 .../RequiredActionMultipleActionsTest.java | 0 .../RequiredActionResetPasswordTest.java | 0 .../actions/RequiredActionTotpSetupTest.java | 0 .../RequiredActionUpdateProfileTest.java | 0 .../keycloak/testsuite/forms/AccountTest.java | 0 .../keycloak/testsuite/forms/LoginTest.java | 0 .../testsuite/forms/LoginTotpTest.java | 0 .../testsuite/forms/RegisterTest.java | 0 .../testsuite/forms/ResetPasswordTest.java | 0 .../testsuite/oauth/AccessTokenTest.java | 0 .../oauth/AuthorizationCodeTest.java | 0 .../testsuite/pages/AccountPasswordPage.java | 0 .../testsuite/pages/AccountTotpPage.java | 0 .../pages/AccountUpdateProfilePage.java | 0 .../org/keycloak/testsuite/pages/AppPage.java | 0 .../testsuite/pages/LoginConfigTotpPage.java | 0 .../keycloak/testsuite/pages/LoginPage.java | 0 .../pages/LoginPasswordResetPage.java | 0 .../pages/LoginPasswordUpdatePage.java | 0 .../testsuite/pages/LoginTotpPage.java | 0 .../pages/LoginUpdateProfilePage.java | 0 .../org/keycloak/testsuite/pages/Page.java | 0 .../testsuite/pages/RegisterPage.java | 0 .../testsuite/pages/VerifyEmailPage.java | 0 .../testsuite/rule/GreenMailRule.java | 0 .../keycloak/testsuite/rule/KeycloakRule.java | 0 .../keycloak/testsuite/rule/WebResource.java | 0 .../org/keycloak/testsuite/rule/WebRule.java | 0 .../testsuite/social/SocialLoginTest.java | 0 .../org.keycloak.social.SocialProvider | 0 .../src/test/resources/testrealm.json | 0 testsuite/performance/pom.xml | 240 +++++++++++ .../BaseJMeterPerformanceTest.java | 7 +- .../performance/CreateRealmsWorker.java | 6 +- .../performance/CreateUsersWorker.java | 12 +- .../testsuite/performance/PerfTestUtils.java | 0 .../performance/ReadUsersWorker.java | 10 +- .../performance/RemoveUsersWorker.java | 8 +- .../testsuite/performance/Worker.java | 4 +- .../src/test/jmeter/jmeter.properties | 0 .../src/test/jmeter/keycloak_perf_test.jmx} | 0 .../src/test/jmeter/system.properties | 0 .../test/resources/META-INF/persistence.xml} | 0 testsuite/pom.xml | 383 +----------------- 115 files changed, 1098 insertions(+), 701 deletions(-) create mode 100644 model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java create mode 100644 model/mongo/pom.xml rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/AbstractAttributedNoSQLObject.java (95%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/AbstractNoSQLObject.java (83%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/AttributedNoSQLObject.java (87%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/NoSQL.java (84%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/NoSQLCollection.java (90%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/NoSQLField.java (91%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/NoSQLId.java (90%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/NoSQLObject.java (89%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/query/NoSQLQuery.java (87%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/query/NoSQLQueryBuilder.java (92%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/types/Converter.java (90%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/api/types/TypeConverter.java (98%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/MongoDBImpl.java (91%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/MongoDBQueryBuilder.java (89%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/ObjectInfo.java (93%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/BasicDBListConverter.java (92%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/BasicDBObjectConverter.java (87%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/ClassCache.java (94%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/EnumToStringConverter.java (85%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/ListConverter.java (89%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/NoSQLObjectConverter.java (81%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/SimpleConverter.java (83%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/impl/types/StringToEnumConverter.java (88%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/ApplicationAdapter.java (66%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/MongoDBSessionFactory.java (57%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/NoSQLSession.java (72%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/NoSQLTransaction.java (88%) create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/RealmAdapter.java (83%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/RoleAdapter.java (81%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/adapters/UserAdapter.java (78%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/credentials/PasswordCredentialHandler.java (93%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/credentials/TOTPCredentialHandler.java (94%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/ApplicationData.java (79%) create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/RealmData.java (88%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/RequiredCredentialData.java (82%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/RoleData.java (82%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/SocialLinkData.java (77%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/UserData.java (80%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/credentials/OTPData.java (82%) rename {services/src/main/java/org/keycloak/services/models/nosql => model/mongo/src/main/java/org/keycloak/models/mongo}/keycloak/data/credentials/PasswordData.java (76%) rename {services/src/test/java/org/keycloak/test/nosql => model/mongo/src/test/java/org/keycloak/models/mongo/test}/Address.java (82%) rename {services/src/test/java/org/keycloak/test/nosql => model/mongo/src/test/java/org/keycloak/models/mongo/test}/MongoDBModelTest.java (92%) rename {services/src/test/java/org/keycloak/test/nosql => model/mongo/src/test/java/org/keycloak/models/mongo/test}/Person.java (86%) create mode 100644 testsuite/integration/pom.xml rename testsuite/{ => integration}/src/main/java/org/keycloak/testutils/KeycloakServer.java (100%) rename testsuite/{ => integration}/src/main/java/org/keycloak/testutils/MailServer.java (100%) rename testsuite/{ => integration}/src/main/java/org/keycloak/testutils/TotpGenerator.java (100%) rename testsuite/{ => integration}/src/main/resources/META-INF/persistence.xml (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/ApplicationServlet.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/Constants.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/DummySocial.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/DummySocialServlet.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/OAuthClient.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/forms/AccountTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/forms/LoginTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/AppPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/Page.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/rule/WebResource.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/rule/WebRule.java (100%) rename testsuite/{ => integration}/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java (100%) rename testsuite/{ => integration}/src/test/resources/META-INF/services/org.keycloak.social.SocialProvider (100%) rename testsuite/{ => integration}/src/test/resources/testrealm.json (100%) create mode 100644 testsuite/performance/pom.xml rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java (94%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java (96%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java (94%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java (100%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java (95%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java (90%) rename testsuite/{ => performance}/src/test/java/org/keycloak/testsuite/performance/Worker.java (72%) rename testsuite/{ => performance}/src/test/jmeter/jmeter.properties (100%) rename testsuite/{src/test/jmeter/mongo_test.jmx => performance/src/test/jmeter/keycloak_perf_test.jmx} (100%) rename testsuite/{ => performance}/src/test/jmeter/system.properties (100%) rename testsuite/{src/test/resources/META-INF/persistence-performance.xml => performance/src/test/resources/META-INF/persistence.xml} (100%) diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java new file mode 100644 index 000000000000..9119282b4f4c --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java @@ -0,0 +1,15 @@ +package org.keycloak.models.utils; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author Marek Posolda + */ +public class KeycloakSessionUtils { + + private static AtomicLong counter = new AtomicLong(1); + + public static String generateId() { + return counter.getAndIncrement() + "-" + System.currentTimeMillis(); + } +} diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml index 8f6edf9cc967..243e43801bc6 100755 --- a/model/jpa/pom.xml +++ b/model/jpa/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak 1.0-alpha-1 - ../pom.xml + ../../pom.xml 4.0.0 diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml new file mode 100644 index 000000000000..537112a6185e --- /dev/null +++ b/model/mongo/pom.xml @@ -0,0 +1,77 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1 + ../../pom.xml + + 4.0.0 + + keycloak-model-mongo + Keycloak Model Mongo + + + + + org.bouncycastle + bcprov-jdk16 + provided + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + + + org.jboss.logging + jboss-logging + provided + + + org.picketlink + picketlink-common + provided + + + org.picketlink + picketlink-idm-api + provided + + + org.mongodb + mongo-java-driver + provided + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java similarity index 95% rename from services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java index a3a6ddb1bfb3..81546ba4698b 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractAttributedNoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.util.Collections; import java.util.HashMap; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java similarity index 83% rename from services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java index 692839bc0061..837e5e46441a 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/AbstractNoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java similarity index 87% rename from services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java index f750e82a8cc3..45accd1c1cad 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/AttributedNoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.util.Map; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java similarity index 84% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java index f30256756cc7..3bc62a590f76 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java @@ -1,9 +1,9 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.util.List; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.models.mongo.api.query.NoSQLQuery; +import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; import org.picketlink.common.properties.Property; /** diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java similarity index 90% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java index ff41188736c5..80b63326f0f5 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLCollection.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java similarity index 91% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java index 8c5d2a2940f2..3af69a71357d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLField.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java similarity index 90% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java index 0cbca8542f8a..06ed01e655bb 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLId.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java similarity index 89% rename from services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java index 7d31b687001d..02422439367f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api; +package org.keycloak.models.mongo.api; /** * Base interface for object, which is persisted in NoSQL database diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java similarity index 87% rename from services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java index 90be20142066..29cc0f31ab36 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQuery.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java @@ -1,7 +1,6 @@ -package org.keycloak.services.models.nosql.api.query; +package org.keycloak.models.mongo.api.query; import java.util.Collections; -import java.util.HashMap; import java.util.Map; /** diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java similarity index 92% rename from services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java index 9f2c38303d64..dcdb5752a37f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api.query; +package org.keycloak.models.mongo.api.query; import java.util.HashMap; import java.util.List; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java similarity index 90% rename from services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java index 37402015c1c5..a6b6c869e6d8 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/Converter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api.types; +package org.keycloak.models.mongo.api.types; /** * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application. diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java similarity index 98% rename from services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java index 00c8e5e01a32..a7c12c0cfb97 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/api/types/TypeConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.api.types; +package org.keycloak.models.mongo.api.types; import java.util.HashMap; import java.util.Map; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java similarity index 91% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java index a6b49300b3d7..6273ba2eb843 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.impl; +package org.keycloak.models.mongo.impl; import java.util.ArrayList; import java.util.Date; @@ -14,23 +14,23 @@ import com.mongodb.DBCursor; import com.mongodb.DBObject; import org.bson.types.ObjectId; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; -import org.keycloak.services.models.nosql.api.types.Converter; -import org.keycloak.services.models.nosql.api.types.TypeConverter; -import org.keycloak.services.models.nosql.impl.types.EnumToStringConverter; -import org.keycloak.services.models.nosql.impl.types.ListConverter; -import org.keycloak.services.models.nosql.impl.types.BasicDBListConverter; -import org.keycloak.services.models.nosql.impl.types.BasicDBObjectConverter; -import org.keycloak.services.models.nosql.impl.types.NoSQLObjectConverter; -import org.keycloak.services.models.nosql.impl.types.SimpleConverter; -import org.keycloak.services.models.nosql.impl.types.StringToEnumConverter; +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.types.Converter; +import org.keycloak.models.mongo.api.types.TypeConverter; +import org.keycloak.models.mongo.impl.types.EnumToStringConverter; +import org.keycloak.models.mongo.impl.types.ListConverter; +import org.keycloak.models.mongo.impl.types.BasicDBListConverter; +import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter; +import org.keycloak.models.mongo.impl.types.NoSQLObjectConverter; +import org.keycloak.models.mongo.impl.types.SimpleConverter; +import org.keycloak.models.mongo.impl.types.StringToEnumConverter; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java similarity index 89% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java index 80f5efba5957..f56c799aeac9 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.impl; +package org.keycloak.models.mongo.impl; import java.util.ArrayList; import java.util.LinkedList; @@ -6,7 +6,7 @@ import com.mongodb.BasicDBObject; import org.bson.types.ObjectId; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; +import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java similarity index 93% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java index da9d27937c33..ae548a607af4 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/ObjectInfo.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.impl; +package org.keycloak.models.mongo.impl; import java.util.Collection; import java.util.Collections; @@ -6,7 +6,7 @@ import java.util.List; import java.util.Map; -import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.models.mongo.api.NoSQLObject; import org.picketlink.common.properties.Property; /** diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java similarity index 92% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java index 75dae75259e1..896257fa8b11 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBListConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java @@ -1,11 +1,11 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; import java.util.ArrayList; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; -import org.keycloak.services.models.nosql.api.types.Converter; -import org.keycloak.services.models.nosql.api.types.TypeConverter; +import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.TypeConverter; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java similarity index 87% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java index 4257fb4f5063..a423652b38d4 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/BasicDBObjectConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java @@ -1,13 +1,13 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; import com.mongodb.BasicDBObject; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.types.Converter; -import org.keycloak.services.models.nosql.api.types.TypeConverter; -import org.keycloak.services.models.nosql.impl.MongoDBImpl; -import org.keycloak.services.models.nosql.impl.ObjectInfo; +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.types.Converter; +import org.keycloak.models.mongo.api.types.TypeConverter; +import org.keycloak.models.mongo.impl.MongoDBImpl; +import org.keycloak.models.mongo.impl.ObjectInfo; import org.picketlink.common.properties.Property; import org.picketlink.common.reflection.Types; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java similarity index 94% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java index cf4372b561d5..891ccdd0e561 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ClassCache.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java similarity index 85% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java index fd32db51ac79..2a800df46b64 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/EnumToStringConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java @@ -1,6 +1,6 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; -import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.models.mongo.api.types.Converter; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java similarity index 89% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java index a9685abad035..8b72ca223c60 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/ListConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java @@ -1,11 +1,11 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; import java.util.List; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; -import org.keycloak.services.models.nosql.api.types.Converter; -import org.keycloak.services.models.nosql.api.types.TypeConverter; +import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.TypeConverter; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java similarity index 81% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java index cd783672c3bd..f7be7aea3328 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/NoSQLObjectConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java @@ -1,18 +1,16 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; import java.util.Collection; -import java.util.List; import java.util.Map; import com.mongodb.BasicDBObject; -import org.keycloak.services.models.nosql.api.AttributedNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.types.Converter; -import org.keycloak.services.models.nosql.api.types.TypeConverter; -import org.keycloak.services.models.nosql.impl.MongoDBImpl; -import org.keycloak.services.models.nosql.impl.ObjectInfo; +import org.keycloak.models.mongo.api.AttributedNoSQLObject; +import org.keycloak.models.mongo.api.NoSQLObject; +import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.TypeConverter; +import org.keycloak.models.mongo.impl.MongoDBImpl; +import org.keycloak.models.mongo.impl.ObjectInfo; import org.picketlink.common.properties.Property; -import org.picketlink.common.reflection.Types; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java similarity index 83% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java index 8dc1b62ba830..5ba1de54981c 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/SimpleConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java @@ -1,6 +1,6 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; -import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.models.mongo.api.types.Converter; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java similarity index 88% rename from services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java index d40594384af4..0c948eccb4bf 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/impl/types/StringToEnumConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java @@ -1,6 +1,6 @@ -package org.keycloak.services.models.nosql.impl.types; +package org.keycloak.models.mongo.impl.types; -import org.keycloak.services.models.nosql.api.types.Converter; +import org.keycloak.models.mongo.api.types.Converter; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java similarity index 66% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java index 4d7a564425a4..72dd3d672c9a 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java @@ -1,18 +1,18 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.keycloak.services.models.ApplicationModel; -import org.keycloak.services.models.RoleModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; -import org.keycloak.services.models.nosql.keycloak.data.RoleData; -import org.keycloak.services.models.nosql.keycloak.data.UserData; +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; /** * @author Marek Posolda @@ -30,12 +30,12 @@ public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) { } @Override - public void updateResource() { + public void updateApplication() { noSQL.saveObject(application); } @Override - public UserModel getResourceUser() { + public UserModel 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()); @@ -89,6 +89,16 @@ public void setManagementUrl(String url) { application.setManagementUrl(url); } + @Override + public void setBaseUrl(String url) { + application.setBaseUrl(url); + } + + @Override + public String getBaseUrl() { + return application.getBaseUrl(); + } + @Override public RoleAdapter getRole(String name) { NoSQLQuery query = noSQL.createQueryBuilder() @@ -103,6 +113,22 @@ public RoleAdapter getRole(String name) { } } + @Override + public RoleModel getRoleById(String id) { + RoleData role = noSQL.loadObject(RoleData.class, id); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, noSQL); + } + } + + @Override + public void grantRole(UserModel user, RoleModel role) { + UserData userData = ((UserAdapter)user).getUser(); + noSQL.pushItemToList(userData, "roleIds", role.getId()); + } + @Override public RoleAdapter addRole(String name) { if (getRole(name) != null) { @@ -132,17 +158,21 @@ public List getRoles() { return result; } - @Override - public Set getRoleMappings(UserModel user) { + // Static so that it can be used from RealmAdapter as well + static List getAllRolesOfUser(UserModel user, NoSQL noSQL) { UserData userData = ((UserAdapter)user).getUser(); List roleIds = userData.getRoleIds(); - Set result = new HashSet(); - NoSQLQuery query = noSQL.createQueryBuilder() .inCondition("_id", roleIds) .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + return noSQL.loadObjects(RoleData.class, query); + } + + @Override + public Set getRoleMappingValues(UserModel user) { + Set result = new HashSet(); + List roles = getAllRolesOfUser(user, 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())) { @@ -153,23 +183,42 @@ public Set getRoleMappings(UserModel user) { } @Override - public void addScope(UserModel agent, String roleName) { + public List getRoleMappings(UserModel user) { + List result = new ArrayList(); + List roles = getAllRolesOfUser(user, 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)); + } + } + 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"); } - addScope(agent, role); + addScopeMapping(agent, role); } @Override - public void addScope(UserModel agent, RoleModel role) { + public void addScopeMapping(UserModel agent, RoleModel role) { UserData userData = ((UserAdapter)agent).getUser(); noSQL.pushItemToList(userData, "scopeIds", role.getId()); } @Override - public Set getScope(UserModel agent) { + public Set getScopeMapping(UserModel agent) { UserData userData = ((UserAdapter)agent).getUser(); List scopeIds = userData.getScopeIds(); diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java similarity index 57% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java index 6f1d7f760e8e..b1ac5093ec10 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java @@ -1,27 +1,24 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; import java.net.UnknownHostException; import com.mongodb.DB; import com.mongodb.MongoClient; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakSessionFactory; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; -import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; -import org.keycloak.services.models.nosql.keycloak.data.RealmData; -import org.keycloak.services.models.nosql.keycloak.data.RequiredCredentialData; -import org.keycloak.services.models.nosql.keycloak.data.RoleData; -import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; -import org.keycloak.services.models.nosql.keycloak.data.UserData; -import org.keycloak.services.models.nosql.impl.MongoDBImpl; -import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; -import org.keycloak.services.models.nosql.keycloak.data.credentials.OTPData; -import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; +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.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.impl.MongoDBImpl; +import org.keycloak.models.mongo.keycloak.data.credentials.OTPData; +import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData; /** * NoSQL implementation based on MongoDB @@ -39,7 +36,8 @@ public class MongoDBSessionFactory implements KeycloakSessionFactory { PasswordData.class, OTPData.class, SocialLinkData.class, - ApplicationData.class + ApplicationData.class, + OAuthClientData.class }; private final MongoClient mongoClient; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java similarity index 72% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java index 71cd3a1e581f..2bc413de5f89 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java @@ -1,19 +1,16 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; import java.util.ArrayList; import java.util.List; -import org.jboss.resteasy.spi.NotImplementedYetException; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakTransaction; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder; -import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder; -import org.keycloak.services.models.nosql.keycloak.data.RealmData; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; +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.query.NoSQLQuery; +import org.keycloak.models.mongo.keycloak.data.RealmData; +import org.keycloak.models.mongo.api.NoSQL; +import org.keycloak.models.utils.KeycloakSessionUtils; /** * @author Marek Posolda @@ -38,7 +35,7 @@ public void close() { @Override public RealmModel createRealm(String name) { - return createRealm(PicketlinkKeycloakSession.generateId(), name); + return createRealm(KeycloakSessionUtils.generateId(), name); } @Override diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java similarity index 88% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java index 2842852d6bfa..3d166357aeb3 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java @@ -1,6 +1,6 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; -import org.keycloak.services.models.KeycloakTransaction; +import org.keycloak.models.KeycloakTransaction; /** * @author Marek Posolda diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java new file mode 100644 index 000000000000..d79b0a178246 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java @@ -0,0 +1,43 @@ +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; + +/** + * @author Marek Posolda + */ +public class OAuthClientAdapter implements OAuthClientModel { + + private final OAuthClientData delegate; + private final UserAdapter oauthAgent; + private final NoSQL noSQL; + + public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) { + this.delegate = oauthClientData; + this.oauthAgent = oauthAgent; + this.noSQL = noSQL; + } + + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public UserModel getOAuthAgent() { + return oauthAgent; + } + + @Override + public String getBaseUrl() { + return delegate.getBaseUrl(); + } + + @Override + public void setBaseUrl(String base) { + delegate.setBaseUrl(base); + noSQL.saveObject(delegate); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java similarity index 83% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 4d613d50c402..73fcf3aaf153 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1,4 +1,4 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; import java.io.IOException; import java.io.StringWriter; @@ -12,28 +12,30 @@ import java.util.Set; import org.bouncycastle.openssl.PEMWriter; -import org.jboss.resteasy.security.PemUtils; +import org.keycloak.PemUtils; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.SocialLinkModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; +import org.keycloak.models.mongo.keycloak.data.OAuthClientData; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.models.ApplicationModel; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.RequiredCredentialModel; -import org.keycloak.services.models.RoleModel; -import org.keycloak.services.models.SocialLinkModel; -import org.keycloak.services.models.UserCredentialModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.keycloak.credentials.PasswordCredentialHandler; -import org.keycloak.services.models.nosql.keycloak.credentials.TOTPCredentialHandler; -import org.keycloak.services.models.nosql.keycloak.data.ApplicationData; -import org.keycloak.services.models.nosql.keycloak.data.RealmData; -import org.keycloak.services.models.nosql.keycloak.data.RequiredCredentialData; -import org.keycloak.services.models.nosql.keycloak.data.RoleData; -import org.keycloak.services.models.nosql.keycloak.data.SocialLinkData; -import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.models.mongo.api.NoSQL; +import org.keycloak.models.mongo.api.query.NoSQLQuery; +import org.keycloak.models.mongo.keycloak.credentials.PasswordCredentialHandler; +import org.keycloak.models.mongo.keycloak.credentials.TOTPCredentialHandler; +import org.keycloak.models.mongo.keycloak.data.ApplicationData; +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.picketlink.idm.credential.Credentials; -import org.picketlink.idm.credential.Password; -import org.picketlink.idm.credential.TOTPCredentials; +import org.picketlink.idm.model.sample.User; /** * @author Marek Posolda @@ -152,6 +154,17 @@ public void setVerifyEmail(boolean verifyEmail) { updateRealm(); } + @Override + public boolean isResetPasswordAllowed() { + return realm.isResetPasswordAllowed(); + } + + @Override + public void setResetPasswordAllowed(boolean resetPassword) { + realm.setResetPasswordAllowed(resetPassword); + updateRealm(); + } + @Override public int getTokenLifespan() { return realm.getTokenLifespan(); @@ -290,6 +303,7 @@ public UserAdapter addUser(String username) { UserData userData = new UserData(); userData.setLoginName(username); + userData.setEnabled(true); userData.setRealmId(getOid()); noSQL.saveObject(userData); @@ -405,7 +419,7 @@ public ApplicationModel getApplicationById(String id) { } @Override - public Map getResourceNameMap() { + public Map getApplicationNameMap() { Map resourceMap = new HashMap(); for (ApplicationModel resource : getApplications()) { resourceMap.put(resource.getName(), resource); @@ -439,7 +453,7 @@ public ApplicationModel addApplication(String name) { ApplicationModel resource = new ApplicationAdapter(appData, noSQL); resource.addRole("*"); - resource.addScope(resourceUser, "*"); + resource.addScopeMapping(resourceUser, "*"); return resource; } @@ -466,16 +480,22 @@ public void grantRole(UserModel user, RoleModel role) { } @Override - public Set getRoleMappings(UserModel user) { - UserData userData = ((UserAdapter)user).getUser(); - List roleIds = userData.getRoleIds(); + public List getRoleMappings(UserModel user) { + List result = new ArrayList(); + List roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL); + // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... + for (RoleData role : roles) { + if (getOid().equals(role.getRealmId())) { + result.add(new RoleAdapter(role, noSQL)); + } + } + return result; + } + @Override + public Set getRoleMappingValues(UserModel user) { Set result = new HashSet(); - - NoSQLQuery query = noSQL.createQueryBuilder() - .inCondition("_id", roleIds) - .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + List roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL); // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... for (RoleData role : roles) { if (getOid().equals(role.getRealmId())) { @@ -486,7 +506,13 @@ public Set getRoleMappings(UserModel user) { } @Override - public void addScope(UserModel agent, String roleName) { + 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) { UserData userData = ((UserAdapter)agent).getUser(); RoleAdapter role = getRole(roleName); if (role == null) { @@ -497,7 +523,31 @@ public void addScope(UserModel agent, String roleName) { } @Override - public Set getScope(UserModel agent) { + public OAuthClientModel addOAuthClient(String name) { + UserAdapter oauthAgent = addUser(name); + + OAuthClientData oauthClient = new OAuthClientData(); + oauthClient.setOauthAgentId(oauthAgent.getUser().getId()); + oauthClient.setRealmId(getOid()); + noSQL.saveObject(oauthClient); + + return new OAuthClientAdapter(oauthClient, oauthAgent, noSQL); + } + + @Override + public OAuthClientModel getOAuthClient(String name) { + UserAdapter user = getUser(name); + if (user == null) return null; + NoSQLQuery query = noSQL.createQueryBuilder() + .andCondition("realmId", getOid()) + .andCondition("oauthAgentId", user.getUser().getId()) + .build(); + OAuthClientData oauthClient = noSQL.loadSingleObject(OAuthClientData.class, query); + return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, noSQL); + } + + @Override + public Set getScopeMapping(UserModel agent) { UserData userData = ((UserAdapter)agent).getUser(); List scopeIds = userData.getScopeIds(); @@ -755,4 +805,28 @@ protected RequiredCredentialModel initRequiredCredentialModel(String type) { } return model; } + + @Override + public List searchForUserByAttributes(Map attributes) { + NoSQLQueryBuilder queryBuilder = noSQL.createQueryBuilder(); + for (Map.Entry entry : attributes.entrySet()) { + if (entry.getKey().equals(UserModel.LOGIN_NAME)) { + queryBuilder.andCondition("loginName", entry.getValue()); + } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) { + queryBuilder.andCondition(UserModel.FIRST_NAME, entry.getValue()); + + } else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) { + queryBuilder.andCondition(UserModel.LAST_NAME, entry.getValue()); + + } else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) { + queryBuilder.andCondition(UserModel.EMAIL, entry.getValue()); + } + } + List users = noSQL.loadObjects(UserData.class, queryBuilder.build()); + List userModels = new ArrayList(); + for (UserData user : users) { + userModels.add(new UserAdapter(user, noSQL)); + } + return userModels; + } } diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java similarity index 81% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java index 9c3f1394b09b..7b2692f42c2d 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java @@ -1,8 +1,8 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; -import org.keycloak.services.models.RoleModel; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.keycloak.data.RoleData; +import org.keycloak.models.RoleModel; +import org.keycloak.models.mongo.api.NoSQL; +import org.keycloak.models.mongo.keycloak.data.RoleData; /** * Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java similarity index 78% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 24c3a29c1883..06e70cf679b1 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -1,11 +1,14 @@ -package org.keycloak.services.models.nosql.keycloak.adapters; +package org.keycloak.models.mongo.keycloak.adapters; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.keycloak.data.UserData; +import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.NoSQL; +import org.keycloak.models.mongo.keycloak.data.UserData; /** * Wrapper around UserData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) @@ -33,17 +36,11 @@ public boolean isEnabled() { } @Override - public void setStatus(Status status) { - user.setStatus(status); + public void setEnabled(boolean enabled) { + user.setEnabled(enabled); noSQL.saveObject(user); } - @Override - public Status getStatus() { - Status status = user.getStatus(); - return status != null ? status : Status.ENABLED; - } - @Override public String getFirstName() { return user.getFirstName(); @@ -114,15 +111,19 @@ public UserData getUser() { } @Override - public List getRequiredActions() { - List requiredActions = user.getRequiredActions(); + public Set getRequiredActions() { + List actions = user.getRequiredActions(); // Compatibility with picketlink impl - if (requiredActions == null || requiredActions.size() == 0) { - return null; + if (actions == null) { + return Collections.emptySet(); + } else { + Set s = new HashSet(); + for (RequiredAction a : actions) { + s.add(a); + } + return Collections.unmodifiableSet(s); } - - return requiredActions; } @Override diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java similarity index 93% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java index 52d706e7f604..a2f61c981194 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java @@ -1,13 +1,13 @@ -package org.keycloak.services.models.nosql.keycloak.credentials; +package org.keycloak.models.mongo.keycloak.credentials; import java.util.Date; import java.util.Map; import java.util.UUID; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.keycloak.data.UserData; -import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; +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; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java similarity index 94% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java index 84b5f0a5e31a..b8f02e7d6e8f 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java @@ -1,12 +1,12 @@ -package org.keycloak.services.models.nosql.keycloak.credentials; +package org.keycloak.models.mongo.keycloak.credentials; import java.util.Date; import java.util.Map; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.keycloak.data.UserData; -import org.keycloak.services.models.nosql.keycloak.data.credentials.OTPData; +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; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java similarity index 79% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java index 108f1d0e515d..5ceb788dbbd6 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java @@ -1,11 +1,11 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +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; /** * @author Marek Posolda @@ -18,6 +18,7 @@ public class ApplicationData implements NoSQLObject { private boolean enabled; private boolean surrogateAuthRequired; private String managementUrl; + private String baseUrl; private String resourceUserId; private String realmId; @@ -67,6 +68,15 @@ public void setManagementUrl(String managementUrl) { this.managementUrl = managementUrl; } + @NoSQLField + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + @NoSQLField public String getResourceUserId() { return resourceUserId; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java new file mode 100644 index 000000000000..67f74ee6a329 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java @@ -0,0 +1,62 @@ +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 Marek Posolda + */ +@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); + } +} diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java similarity index 88% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java index 70d0a511a57b..5247d60034f5 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RealmData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java @@ -1,16 +1,13 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; -import java.security.SecureRandom; import java.util.List; -import java.util.Random; -import java.util.UUID; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +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; /** * @author Marek Posolda @@ -27,6 +24,7 @@ public class RealmData implements NoSQLObject { private boolean cookieLoginAllowed; private boolean registrationAllowed; private boolean verifyEmail; + private boolean resetPasswordAllowed; private boolean social; private boolean automaticRegistrationAfterSocialLogin; private int tokenLifespan; @@ -110,6 +108,15 @@ 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; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java similarity index 82% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java index 124e50711381..e46ee9fd074c 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java @@ -1,10 +1,9 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; +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 Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java similarity index 82% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java index 49483a125fea..29bc1f893024 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RoleData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java @@ -1,14 +1,14 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; import java.util.List; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; +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; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java similarity index 77% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java index 46b1c2622d71..37ea43d44efb 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java @@ -1,10 +1,8 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.models.mongo.api.AbstractNoSQLObject; +import org.keycloak.models.mongo.api.NoSQLCollection; +import org.keycloak.models.mongo.api.NoSQLField; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java similarity index 80% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java index 6206289adb3e..cfeb67d6d11c 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/UserData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java @@ -1,16 +1,16 @@ -package org.keycloak.services.models.nosql.keycloak.data; +package org.keycloak.models.mongo.keycloak.data; import java.util.List; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.api.AbstractAttributedNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData; +import org.jboss.logging.Logger; +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; /** * @author Marek Posolda @@ -27,7 +27,7 @@ public class UserData extends AbstractAttributedNoSQLObject { private String email; private boolean emailVerified; private boolean totp; - private UserModel.Status status; + private boolean enabled; private String realmId; @@ -89,8 +89,13 @@ public void setEmailVerified(boolean emailVerified) { this.emailVerified = emailVerified; } + @NoSQLField public boolean isEnabled() { - return !UserModel.Status.DISABLED.equals(getStatus()); + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; } @NoSQLField @@ -102,15 +107,6 @@ public void setTotp(boolean totp) { this.totp = totp; } - @NoSQLField - public UserModel.Status getStatus() { - return status; - } - - public void setStatus(UserModel.Status status) { - this.status = status; - } - @NoSQLField public String getRealmId() { return realmId; diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java similarity index 82% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java index cf4fef864f0b..8ab31a65fcff 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/OTPData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java @@ -1,10 +1,10 @@ -package org.keycloak.services.models.nosql.keycloak.data.credentials; +package org.keycloak.models.mongo.keycloak.data.credentials; import java.util.Date; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.models.mongo.api.AbstractNoSQLObject; +import org.keycloak.models.mongo.api.NoSQLCollection; +import org.keycloak.models.mongo.api.NoSQLField; /** * @author Marek Posolda diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java similarity index 76% rename from services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java index 4834316b22c2..7480e1fb87ea 100644 --- a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java @@ -1,12 +1,10 @@ -package org.keycloak.services.models.nosql.keycloak.data.credentials; +package org.keycloak.models.mongo.keycloak.data.credentials; import java.util.Date; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; -import org.keycloak.services.models.nosql.api.NoSQLObject; +import org.keycloak.models.mongo.api.AbstractNoSQLObject; +import org.keycloak.models.mongo.api.NoSQLCollection; +import org.keycloak.models.mongo.api.NoSQLField; /** * @author Marek Posolda diff --git a/services/src/test/java/org/keycloak/test/nosql/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java similarity index 82% rename from services/src/test/java/org/keycloak/test/nosql/Address.java rename to model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java index 8b56c595ae2d..8f6b6f885eec 100644 --- a/services/src/test/java/org/keycloak/test/nosql/Address.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java @@ -1,9 +1,9 @@ -package org.keycloak.test.nosql; +package org.keycloak.models.mongo.test; import java.util.List; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLField; +import org.keycloak.models.mongo.api.AbstractNoSQLObject; +import org.keycloak.models.mongo.api.NoSQLField; /** * @author Marek Posolda diff --git a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java similarity index 92% rename from services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java rename to model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java index b59fdffe5942..262ade855b22 100644 --- a/services/src/test/java/org/keycloak/test/nosql/MongoDBModelTest.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test.nosql; +package org.keycloak.models.mongo.test; import java.net.UnknownHostException; import java.util.ArrayList; @@ -10,11 +10,10 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Test; -import org.keycloak.services.models.nosql.api.NoSQL; -import org.keycloak.services.models.nosql.api.NoSQLObject; -import org.keycloak.services.models.nosql.api.query.NoSQLQuery; -import org.keycloak.services.models.nosql.impl.MongoDBImpl; +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; /** * @author Marek Posolda diff --git a/services/src/test/java/org/keycloak/test/nosql/Person.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java similarity index 86% rename from services/src/test/java/org/keycloak/test/nosql/Person.java rename to model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java index 3ead51235f51..ab2ded3844ab 100644 --- a/services/src/test/java/org/keycloak/test/nosql/Person.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java @@ -1,11 +1,11 @@ -package org.keycloak.test.nosql; +package org.keycloak.models.mongo.test; import java.util.List; -import org.keycloak.services.models.nosql.api.AbstractNoSQLObject; -import org.keycloak.services.models.nosql.api.NoSQLCollection; -import org.keycloak.services.models.nosql.api.NoSQLField; -import org.keycloak.services.models.nosql.api.NoSQLId; +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 Marek Posolda diff --git a/model/picketlink/pom.xml b/model/picketlink/pom.xml index a035b19a3730..b42cd805ba92 100755 --- a/model/picketlink/pom.xml +++ b/model/picketlink/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak 1.0-alpha-1 - ../pom.xml + ../../pom.xml 4.0.0 diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java index 4139e03ee766..8e2a75d46098 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/PicketlinkKeycloakSession.java @@ -6,6 +6,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.picketlink.mappings.RealmData; import org.keycloak.models.picketlink.relationships.RealmAdminRelationship; +import org.keycloak.models.utils.KeycloakSessionUtils; import org.picketlink.idm.PartitionManager; import org.picketlink.idm.RelationshipManager; import org.picketlink.idm.query.RelationshipQuery; @@ -25,11 +26,6 @@ public class PicketlinkKeycloakSession implements KeycloakSession { protected PartitionManager partitionManager; protected EntityManager entityManager; - private static AtomicLong counter = new AtomicLong(1); - public static String generateId() { - return counter.getAndIncrement() + "-" + System.currentTimeMillis(); - } - public PicketlinkKeycloakSession(PartitionManager partitionManager, EntityManager entityManager) { this.partitionManager = partitionManager; this.entityManager = entityManager; @@ -50,7 +46,7 @@ public KeycloakTransaction getTransaction() { @Override public RealmAdapter createRealm(String name) { - return createRealm(generateId(), name); + return createRealm(KeycloakSessionUtils.generateId(), name); } @Override diff --git a/model/pom.xml b/model/pom.xml index c6a4a9ef90c3..3d0b0dc31b68 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -37,5 +37,6 @@ api picketlink jpa + mongo diff --git a/pom.xml b/pom.xml index 9ab5c51f5bbe..04b6ec76f272 100755 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 3.6.6.Final 1.3.161 1.6.1 + 5.1.25 1.6.1 @@ -295,7 +296,7 @@ mysql mysql-connector-java - 5.1.25 + ${mysql.version} diff --git a/services/pom.xml b/services/pom.xml index b35bef413549..9cee9f509ed7 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -34,6 +34,12 @@ keycloak-model-picketlink ${project.version} + + + org.keycloak + keycloak-model-mongo + ${project.version} + org.keycloak keycloak-social-core diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index fc8297522460..a642bc9aa35d 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -1,18 +1,13 @@ package org.keycloak.services.resources; import org.keycloak.SkeletonKeyContextResolver; +import org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory; import org.keycloak.services.managers.TokenManager; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.picketlink.PicketlinkKeycloakSession; import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory; import org.keycloak.models.picketlink.mappings.ApplicationEntity; import org.keycloak.models.picketlink.mappings.RealmEntity; -import org.keycloak.services.models.KeycloakSessionFactory; -import org.keycloak.services.models.nosql.keycloak.adapters.MongoDBSessionFactory; -import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; -import org.keycloak.services.models.picketlink.PicketlinkKeycloakSessionFactory; -import org.keycloak.services.models.picketlink.mappings.ApplicationEntity; -import org.keycloak.services.models.picketlink.mappings.RealmEntity; import org.keycloak.social.SocialRequestManager; import org.picketlink.idm.PartitionManager; import org.picketlink.idm.config.IdentityConfigurationBuilder; @@ -25,8 +20,6 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.servlet.ServletContext; -import javax.persistence.EntityManagerFactory; -import javax.persistence.Persistence; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; import java.util.HashSet; diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java index 08420363e7c5..41aa8e15b283 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/services/src/test/java/org/keycloak/test/AdapterTest.java @@ -141,6 +141,8 @@ public void testUserSearch() throws Exception { user.setEmail("bburke@redhat.com"); } + RealmManager adapter = getRealmManager(); + { List userModels = adapter.searchUsers("total junk query", realmModel); Assert.assertEquals(userModels.size(), 0); diff --git a/services/src/test/java/org/keycloak/test/RealmCreationTest.java b/services/src/test/java/org/keycloak/test/RealmCreationTest.java index 725a0d122821..a99042f6c568 100755 --- a/services/src/test/java/org/keycloak/test/RealmCreationTest.java +++ b/services/src/test/java/org/keycloak/test/RealmCreationTest.java @@ -11,11 +11,6 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.resources.KeycloakApplication; -import org.keycloak.test.common.AbstractKeycloakTest; -import org.keycloak.test.common.SessionFactoryTestContext; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.client.Entity; diff --git a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java index 0e9b692efd09..34f052bb5d9f 100644 --- a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java +++ b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java @@ -10,9 +10,9 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakSessionFactory; import org.keycloak.services.resources.KeycloakApplication; /** diff --git a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java index fc723a2137b4..ece90a10e486 100644 --- a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java +++ b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java @@ -1,7 +1,5 @@ package org.keycloak.test.common; -import com.mongodb.DB; -import com.mongodb.MongoClient; import de.flapdoodle.embed.mongo.MongodExecutable; import de.flapdoodle.embed.mongo.MongodProcess; import de.flapdoodle.embed.mongo.MongodStarter; @@ -9,7 +7,6 @@ import de.flapdoodle.embed.mongo.distribution.Version; import de.flapdoodle.embed.process.runtime.Network; import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.models.KeycloakSessionFactory; import org.keycloak.services.resources.KeycloakApplication; /** diff --git a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java index 7ffbbf359d1c..1f757bf92c57 100644 --- a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java +++ b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java @@ -1,6 +1,5 @@ package org.keycloak.test.common; -import org.keycloak.services.models.KeycloakSessionFactory; import org.keycloak.services.resources.KeycloakApplication; /** diff --git a/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java index 3bbc4bd65500..a35cfd25c6f8 100644 --- a/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java +++ b/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java @@ -1,7 +1,5 @@ package org.keycloak.test.common; -import org.keycloak.services.models.KeycloakSessionFactory; - /** * @author Marek Posolda */ diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml new file mode 100644 index 000000000000..d8e45372061d --- /dev/null +++ b/testsuite/integration/pom.xml @@ -0,0 +1,223 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1 + ../../pom.xml + + 4.0.0 + + keycloak-testsuite-integration + Keycloak Integration TestSuite + + + + + + org.keycloak + keycloak-as7-adapter + ${project.version} + + + + + + + org.bouncycastle + bcprov-jdk16 + + + org.keycloak + keycloak-core + ${project.version} + + + org.keycloak + keycloak-services + ${project.version} + + + + org.keycloak + keycloak-social-core + ${project.version} + + + org.keycloak + keycloak-social-google + ${project.version} + + + org.keycloak + keycloak-social-twitter + ${project.version} + + + org.keycloak + keycloak-social-facebook + ${project.version} + + + org.keycloak + keycloak-forms + ${project.version} + + + + org.jboss.logging + jboss-logging + + + org.picketlink + picketlink-idm-api + + + org.picketlink + picketlink-common + + + org.picketlink + picketlink-idm-impl + + + org.picketlink + picketlink-idm-simple-schema + + + org.picketlink + picketlink-config + + + org.jboss.resteasy + resteasy-jaxrs + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + + + org.jboss.resteasy + jaxrs-api + + + org.jboss.resteasy + resteasy-client + + + org.jboss.resteasy + resteasy-crypto + + + org.jboss.resteasy + jose-jwt + + + org.jboss.resteasy + resteasy-undertow + + + io.undertow + undertow-servlet + + + io.undertow + undertow-core + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-mapper-asl + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + + + org.codehaus.jackson + jackson-xc + + + junit + junit + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + + + com.h2database + h2 + 1.3.161 + + + org.hibernate + hibernate-entitymanager + 3.6.6.Final + + + com.icegreen + greenmail + + + org.seleniumhq.selenium + selenium-java + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + + + + jboss-managed + + + org.jboss.as + jboss-as-arquillian-container-managed + test + 7.1.1.Final + + + + + jboss-remote + + + org.jboss.as + jboss-as-arquillian-container-remote + test + 7.1.1.Final + + + + + \ No newline at end of file diff --git a/testsuite/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java similarity index 100% rename from testsuite/src/main/java/org/keycloak/testutils/KeycloakServer.java rename to testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java diff --git a/testsuite/src/main/java/org/keycloak/testutils/MailServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/MailServer.java similarity index 100% rename from testsuite/src/main/java/org/keycloak/testutils/MailServer.java rename to testsuite/integration/src/main/java/org/keycloak/testutils/MailServer.java diff --git a/testsuite/src/main/java/org/keycloak/testutils/TotpGenerator.java b/testsuite/integration/src/main/java/org/keycloak/testutils/TotpGenerator.java similarity index 100% rename from testsuite/src/main/java/org/keycloak/testutils/TotpGenerator.java rename to testsuite/integration/src/main/java/org/keycloak/testutils/TotpGenerator.java diff --git a/testsuite/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml similarity index 100% rename from testsuite/src/main/resources/META-INF/persistence.xml rename to testsuite/integration/src/main/resources/META-INF/persistence.xml diff --git a/testsuite/src/test/java/org/keycloak/testsuite/ApplicationServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/ApplicationServlet.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/Constants.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/Constants.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/Constants.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/Constants.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/DummySocial.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/DummySocial.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocial.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/DummySocialServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/DummySocialServlet.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/forms/AccountTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/forms/LoginTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/AppPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/Page.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/Page.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/Page.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/Page.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/rule/WebResource.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebResource.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/rule/WebResource.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebResource.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/rule/WebRule.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java diff --git a/testsuite/src/test/resources/META-INF/services/org.keycloak.social.SocialProvider b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialProvider similarity index 100% rename from testsuite/src/test/resources/META-INF/services/org.keycloak.social.SocialProvider rename to testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialProvider diff --git a/testsuite/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json similarity index 100% rename from testsuite/src/test/resources/testrealm.json rename to testsuite/integration/src/test/resources/testrealm.json diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml new file mode 100644 index 000000000000..1cb822099bcc --- /dev/null +++ b/testsuite/performance/pom.xml @@ -0,0 +1,240 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1 + ../../pom.xml + + 4.0.0 + + keycloak-testsuite-performance + Keycloak Performance TestSuite + + + + + org.keycloak + keycloak-core + ${project.version} + + + org.keycloak + keycloak-services + ${project.version} + + + org.jboss.resteasy + resteasy-jaxrs + provided + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + + + org.jboss.resteasy + jaxrs-api + provided + + + org.jboss.resteasy + resteasy-client + provided + + + org.apache.jmeter + ApacheJMeter_java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + + + + performance-tests + + + + com.lazerycode.jmeter + jmeter-maven-plugin + + + jmeter-tests + verify + + jmeter + + + + + + org.keycloak + keycloak-testsuite + ${project.version} + test-jar + + + org.keycloak + keycloak-services + ${project.version} + + + org.jboss.resteasy + jaxrs-api + ${resteasy.version} + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy.version} + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + commons-io + commons-io + + + + + org.jboss.logging + jboss-logging + ${jboss.logging.version} + + + org.picketlink + picketlink-idm-impl + ${picketlink.version} + + + org.picketlink + picketlink-idm-simple-schema + ${picketlink.version} + + + org.picketlink + picketlink-config + ${picketlink.version} + + + org.mongodb + mongo-java-driver + ${mongo.driver.version} + + + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + ${hibernate.javax.persistence.version} + + + com.h2database + h2 + ${h2.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} + + + dom4j + dom4j + ${dom4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + mysql + mysql-connector-java + ${mysql.version} + + + + + + + com.lazerycode.jmeter + jmeter-analysis-maven-plugin + + + jmeter-tests-analyze + verify + + analyze + + + ${project.build.directory}/jmeter/results/*.jtl + ${project.build.directory}/jmeter/results + false + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java similarity index 94% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java index bc38e6d7bd3a..efe57c128a67 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/BaseJMeterPerformanceTest.java @@ -7,10 +7,9 @@ import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.KeycloakSessionFactory; -import org.keycloak.services.models.KeycloakTransaction; -import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.KeycloakTransaction; import org.keycloak.services.resources.KeycloakApplication; /** diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java similarity index 96% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java index a3b37e091ff7..27499982b24e 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateRealmsWorker.java @@ -5,11 +5,11 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.models.ApplicationModel; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.RealmModel; /** * @author Marek Posolda diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java similarity index 94% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java index f4279442d38e..29bd33cc925a 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/CreateUsersWorker.java @@ -5,13 +5,13 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.SocialLinkModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.RoleModel; -import org.keycloak.services.models.SocialLinkModel; -import org.keycloak.services.models.UserCredentialModel; -import org.keycloak.services.models.UserModel; /** * @author Marek Posolda diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java similarity index 100% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java similarity index 95% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java index 14399195126e..30bab6058665 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java @@ -5,10 +5,10 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.SocialLinkModel; -import org.keycloak.services.models.UserModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.SocialLinkModel; +import org.keycloak.models.UserModel; /** * @author Marek Posolda @@ -95,7 +95,7 @@ public void run(SampleResult result, KeycloakSession identitySession) { // Read scopes of user in realm if (readScopes) { - realm.getScope(user); + realm.getScopeMapping(user); } // Validate password (shoould be same as username) diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java similarity index 90% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java index 966024ac73eb..f23977283a75 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java @@ -5,11 +5,9 @@ import org.apache.jmeter.samplers.SampleResult; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; -import org.keycloak.services.models.KeycloakSession; -import org.keycloak.services.models.RealmModel; -import org.keycloak.services.models.SocialLinkModel; -import org.keycloak.services.models.UserModel; -import org.keycloak.services.models.nosql.keycloak.adapters.RealmAdapter; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.mongo.keycloak.adapters.RealmAdapter; import org.keycloak.services.resources.KeycloakApplication; /** diff --git a/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java similarity index 72% rename from testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java rename to testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java index 6060f49a3fa4..69732cf19203 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/performance/Worker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/Worker.java @@ -1,9 +1,7 @@ package org.keycloak.testsuite.performance; import org.apache.jmeter.samplers.SampleResult; -import org.apache.log.Logger; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.models.KeycloakSession; +import org.keycloak.models.KeycloakSession; /** * @author Marek Posolda diff --git a/testsuite/src/test/jmeter/jmeter.properties b/testsuite/performance/src/test/jmeter/jmeter.properties similarity index 100% rename from testsuite/src/test/jmeter/jmeter.properties rename to testsuite/performance/src/test/jmeter/jmeter.properties diff --git a/testsuite/src/test/jmeter/mongo_test.jmx b/testsuite/performance/src/test/jmeter/keycloak_perf_test.jmx similarity index 100% rename from testsuite/src/test/jmeter/mongo_test.jmx rename to testsuite/performance/src/test/jmeter/keycloak_perf_test.jmx diff --git a/testsuite/src/test/jmeter/system.properties b/testsuite/performance/src/test/jmeter/system.properties similarity index 100% rename from testsuite/src/test/jmeter/system.properties rename to testsuite/performance/src/test/jmeter/system.properties diff --git a/testsuite/src/test/resources/META-INF/persistence-performance.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml similarity index 100% rename from testsuite/src/test/resources/META-INF/persistence-performance.xml rename to testsuite/performance/src/test/resources/META-INF/persistence.xml diff --git a/testsuite/pom.xml b/testsuite/pom.xml index adb5aa3e7edd..c8b7bcf19de1 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -8,382 +8,13 @@ 4.0.0 - keycloak-testsuite - Keycloak TestSuite + keycloak-testsuite-pom + pom + Keycloak TestSuite + + integration + performance + - - - - org.keycloak - keycloak-as7-adapter - ${project.version} - - - - - - - org.bouncycastle - bcprov-jdk16 - - - org.keycloak - keycloak-core - ${project.version} - - - org.keycloak - keycloak-services - ${project.version} - - - - org.keycloak - keycloak-social-core - ${project.version} - - - org.keycloak - keycloak-social-google - ${project.version} - - - org.keycloak - keycloak-social-twitter - ${project.version} - - - org.keycloak - keycloak-social-facebook - ${project.version} - - - org.keycloak - keycloak-forms - ${project.version} - - - - org.jboss.logging - jboss-logging - - - org.picketlink - picketlink-idm-api - - - org.picketlink - picketlink-common - - - org.picketlink - picketlink-idm-impl - - - org.picketlink - picketlink-idm-simple-schema - - - org.picketlink - picketlink-config - - - org.jboss.resteasy - resteasy-jaxrs - - - log4j - log4j - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - - - - - org.jboss.resteasy - jaxrs-api - - - org.jboss.resteasy - resteasy-client - - - org.jboss.resteasy - resteasy-crypto - - - org.jboss.resteasy - jose-jwt - - - org.jboss.resteasy - resteasy-undertow - - - io.undertow - undertow-servlet - - - io.undertow - undertow-core - - - org.codehaus.jackson - jackson-core-asl - - - org.codehaus.jackson - jackson-mapper-asl - - - org.jboss.spec.javax.servlet - jboss-servlet-api_3.0_spec - - - org.codehaus.jackson - jackson-xc - - - junit - junit - - - org.hibernate.javax.persistence - hibernate-jpa-2.0-api - - - com.h2database - h2 - 1.3.161 - - - org.hibernate - hibernate-entitymanager - 3.6.6.Final - - - com.icegreen - greenmail - - - org.seleniumhq.selenium - selenium-java - - - org.apache.jmeter - ApacheJMeter_java - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - - - - jboss-managed - - - org.jboss.as - jboss-as-arquillian-container-managed - test - 7.1.1.Final - - - - - jboss-remote - - - org.jboss.as - jboss-as-arquillian-container-remote - test - 7.1.1.Final - - - - - - performance-tests - - - - com.lazerycode.jmeter - jmeter-maven-plugin - - - jmeter-tests - verify - - jmeter - - - - - - org.keycloak - keycloak-testsuite - ${project.version} - test-jar - - - org.keycloak - keycloak-services - ${project.version} - - - org.jboss.resteasy - jaxrs-api - ${resteasy.version} - - - org.jboss.resteasy - resteasy-jaxrs - ${resteasy.version} - - - log4j - log4j - - - org.slf4j - slf4j-api - - - org.slf4j - slf4j-simple - - - commons-io - commons-io - - - - - org.jboss.logging - jboss-logging - ${jboss.logging.version} - - - org.picketlink - picketlink-idm-impl - ${picketlink.version} - - - org.picketlink - picketlink-idm-simple-schema - ${picketlink.version} - - - org.picketlink - picketlink-config - ${picketlink.version} - - - org.mongodb - mongo-java-driver - ${mongo.driver.version} - - - - - org.hibernate.javax.persistence - hibernate-jpa-2.0-api - ${hibernate.javax.persistence.version} - - - com.h2database - h2 - ${h2.version} - - - org.hibernate - hibernate-entitymanager - ${hibernate.entitymanager.version} - - - dom4j - dom4j - ${dom4j.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - - mysql - mysql-connector-java - - - - - - - com.lazerycode.jmeter - jmeter-analysis-maven-plugin - - - jmeter-tests-analyze - verify - - analyze - - - ${project.build.directory}/jmeter/results/*.jtl - ${project.build.directory}/jmeter/results - false - - - - - - - - - - - - - - - - - - - From 453e1c5cf792e509585d381b34a0b6af75217c87 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 10 Oct 2013 09:31:16 +0200 Subject: [PATCH 11/14] Added ServletContextListener for start of embedded MongoDB at Keycloak deploy. Added PropertiesManager --- examples/as7-eap-demo/server/pom.xml | 4 + .../server/src/main/webapp/WEB-INF/web.xml | 4 + services/pom.xml | 2 +- .../listeners/MongoRunnerListener.java | 53 ++++++++++++ .../resources/KeycloakApplication.java | 25 ++---- .../services/utils/PropertiesManager.java | 82 +++++++++++++++++++ .../MongoDBSessionFactoryTestContext.java | 17 ++-- .../PicketlinkSessionFactoryTestContext.java | 3 +- .../performance/RemoveUsersWorker.java | 3 +- 9 files changed, 166 insertions(+), 27 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java create mode 100644 services/src/main/java/org/keycloak/services/utils/PropertiesManager.java diff --git a/examples/as7-eap-demo/server/pom.xml b/examples/as7-eap-demo/server/pom.xml index f7e619e543c6..3282368dd403 100755 --- a/examples/as7-eap-demo/server/pom.xml +++ b/examples/as7-eap-demo/server/pom.xml @@ -123,6 +123,10 @@ org.mongodb mongo-java-driver + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + junit junit diff --git a/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml b/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml index 6829b0e56e9d..fafd74452f72 100755 --- a/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml +++ b/examples/as7-eap-demo/server/src/main/webapp/WEB-INF/web.xml @@ -21,6 +21,10 @@ true + + org.keycloak.services.listeners.MongoRunnerListener + + Keycloak Session Management org.keycloak.services.filters.KeycloakSessionServletFilter diff --git a/services/pom.xml b/services/pom.xml index 9cee9f509ed7..6421497db29c 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -163,7 +163,7 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - test + provided junit diff --git a/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java b/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java new file mode 100644 index 000000000000..f0df0a627ff6 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/listeners/MongoRunnerListener.java @@ -0,0 +1,53 @@ +package org.keycloak.services.listeners; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +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 Marek Posolda + */ +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(); + } + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index a642bc9aa35d..df8eec3ae74a 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -8,6 +8,7 @@ import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory; import org.keycloak.models.picketlink.mappings.ApplicationEntity; import org.keycloak.models.picketlink.mappings.RealmEntity; +import org.keycloak.services.utils.PropertiesManager; import org.keycloak.social.SocialRequestManager; import org.picketlink.idm.PartitionManager; import org.picketlink.idm.config.IdentityConfigurationBuilder; @@ -31,15 +32,6 @@ */ public class KeycloakApplication extends Application { - public 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 MONGO_HOST = "keycloak.mongodb.host"; - public static final String MONGO_PORT = "keycloak.mongodb.port"; - public static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName"; - public static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup"; - - protected Set singletons = new HashSet(); protected Set> classes = new HashSet>(); @@ -65,11 +57,12 @@ protected KeycloakSessionFactory createSessionFactory() { } public static KeycloakSessionFactory buildSessionFactory() { - String sessionFactoryType = System.getProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK); - if (SESSION_FACTORY_MONGO.equals(sessionFactoryType)) { + if (PropertiesManager.isMongoSessionFactory()) { return buildMongoDBSessionFactory(); - } else { + } else if (PropertiesManager.isPicketlinkSessionFactory()) { return buildPicketlinkSessionFactory(); + } else { + throw new IllegalStateException("Unknown session factory type: " + PropertiesManager.getSessionFactoryType()); } } @@ -79,10 +72,10 @@ private static KeycloakSessionFactory buildPicketlinkSessionFactory() { } private static KeycloakSessionFactory buildMongoDBSessionFactory() { - String host = System.getProperty(MONGO_HOST, "localhost"); - int port = Integer.parseInt(System.getProperty(MONGO_PORT, "27017")); - String dbName = System.getProperty(MONGO_DB_NAME, "keycloak"); - boolean dropDatabaseOnStartup = Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true")); + String host = PropertiesManager.getMongoHost(); + int port = PropertiesManager.getMongoPort(); + String dbName = PropertiesManager.getMongoDbName(); + boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup(); return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup); } diff --git a/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java b/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java new file mode 100644 index 000000000000..ee1654710442 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java @@ -0,0 +1,82 @@ +package org.keycloak.services.utils; + +/** + * @author Marek Posolda + */ +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"; + + 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_PICKETLINK); + } + + public static void setSessionFactoryType(String sessionFactoryType) { + System.setProperty(SESSION_FACTORY, sessionFactoryType); + } + + public static void setDefaultSessionFactoryType() { + System.setProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK); + } + + public static boolean isMongoSessionFactory() { + return getSessionFactoryType().equals(SESSION_FACTORY_MONGO); + } + + public static boolean isPicketlinkSessionFactory() { + return getSessionFactoryType().equals(SESSION_FACTORY_PICKETLINK); + } + + 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")); + } +} diff --git a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java index ece90a10e486..def3f342c44c 100644 --- a/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java +++ b/services/src/test/java/org/keycloak/test/common/MongoDBSessionFactoryTestContext.java @@ -8,6 +8,7 @@ import de.flapdoodle.embed.process.runtime.Network; import org.jboss.resteasy.logging.Logger; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.utils.PropertiesManager; /** * @author Marek Posolda @@ -15,7 +16,7 @@ public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext { protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class); - private static final int PORT = 27777; + private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS; private MongodExecutable mongodExe; private MongodProcess mongod; @@ -42,16 +43,16 @@ public void afterTestClass() { } logger.info("MongoDB stopped successfully"); - // Null this, so other tests are not affected - System.setProperty(KeycloakApplication.SESSION_FACTORY, ""); + // Reset this, so other tests are not affected + PropertiesManager.setDefaultSessionFactoryType(); } @Override public void initEnvironment() { - System.setProperty(KeycloakApplication.SESSION_FACTORY, KeycloakApplication.SESSION_FACTORY_MONGO); - System.setProperty(KeycloakApplication.MONGO_HOST, "localhost"); - System.setProperty(KeycloakApplication.MONGO_PORT, String.valueOf(PORT)); - System.setProperty(KeycloakApplication.MONGO_DB_NAME, "keycloakTest"); - System.setProperty(KeycloakApplication.MONGO_DROP_DB_ON_STARTUP, "true"); + PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO); + PropertiesManager.setMongoHost("localhost"); + PropertiesManager.setMongoPort(PORT); + PropertiesManager.setMongoDbName("keycloakTest"); + PropertiesManager.setDropDatabaseOnStartup(true); } } diff --git a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java index 1f757bf92c57..80b2cbc31dac 100644 --- a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java +++ b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java @@ -1,6 +1,7 @@ package org.keycloak.test.common; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.utils.PropertiesManager; /** * @author Marek Posolda @@ -19,6 +20,6 @@ public void afterTestClass() { @Override public void initEnvironment() { - System.setProperty(KeycloakApplication.SESSION_FACTORY, KeycloakApplication.SESSION_FACTORY_PICKETLINK); + PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_PICKETLINK); } } diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java index f23977283a75..c5b3633445a9 100644 --- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java @@ -9,6 +9,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.mongo.keycloak.adapters.RealmAdapter; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.utils.PropertiesManager; /** * @author Marek Posolda @@ -51,7 +52,7 @@ public void run(SampleResult result, KeycloakSession identitySession) { // TODO: Not supported in model actually. We support operation just in MongoDB // UserModel user = realm.removeUser(username); - if (KeycloakApplication.SESSION_FACTORY_MONGO.equals(System.getProperty(KeycloakApplication.SESSION_FACTORY))) { + if (PropertiesManager.isMongoSessionFactory()) { RealmAdapter mongoRealm = (RealmAdapter)realm; mongoRealm.removeUser(username); } else { From 6ac643c45b66e7bb5ca17d4b32f03e2812ad86ff Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 10 Oct 2013 09:59:39 +0200 Subject: [PATCH 12/14] Creating MongoDBSessionFactory in KeycloakApplication via reflection to avoid compile-time dependency on mongo module --- .../services/resources/KeycloakApplication.java | 13 +++++++++++-- .../testsuite/performance/RemoveUsersWorker.java | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index df8eec3ae74a..03407519c780 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -1,7 +1,6 @@ package org.keycloak.services.resources; import org.keycloak.SkeletonKeyContextResolver; -import org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory; import org.keycloak.services.managers.TokenManager; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.picketlink.PicketlinkKeycloakSession; @@ -23,6 +22,8 @@ import javax.servlet.ServletContext; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; + +import java.lang.reflect.Constructor; import java.util.HashSet; import java.util.Set; @@ -76,7 +77,15 @@ private static KeycloakSessionFactory buildMongoDBSessionFactory() { int port = PropertiesManager.getMongoPort(); String dbName = PropertiesManager.getMongoDbName(); boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup(); - return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup); + + // Create MongoDBSessionFactory via reflection now + try { + Class mongoDBSessionFactoryClass = (Class)Class.forName("org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory"); + Constructor constr = mongoDBSessionFactoryClass.getConstructor(String.class, int.class, String.class, boolean.class); + return constr.newInstance(host, port, dbName, dropDatabaseOnStartup); + } catch (Exception e) { + throw new RuntimeException(e); + } } public KeycloakSessionFactory getFactory() { diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java index c5b3633445a9..262ad932a6f8 100644 --- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/RemoveUsersWorker.java @@ -7,8 +7,6 @@ import org.apache.log.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.mongo.keycloak.adapters.RealmAdapter; -import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.utils.PropertiesManager; /** @@ -45,6 +43,8 @@ public void setup(int workerId, KeycloakSession identitySession) { @Override public void run(SampleResult result, KeycloakSession identitySession) { + throw new IllegalStateException("Not yet supported"); + /* int userNumber = ++userCounterInRealm; int totalUserNumber = totalUserCounter.incrementAndGet(); @@ -63,6 +63,7 @@ public void run(SampleResult result, KeycloakSession identitySession) { int labelC = ((totalUserNumber - 1) / NUMBER_OF_USERS_IN_EACH_REPORT) * NUMBER_OF_USERS_IN_EACH_REPORT; result.setSampleLabel("ReadUsers " + (labelC + 1) + "-" + (labelC + NUMBER_OF_USERS_IN_EACH_REPORT)); + */ } @Override From 9f91002348a0902a54a44aa069377360a2fecdea Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 10 Oct 2013 12:10:16 +0200 Subject: [PATCH 13/14] Adapt latest model changes --- .../keycloak/adapters/ApplicationAdapter.java | 57 +++++++++++++++++-- .../keycloak/adapters/OAuthClientAdapter.java | 13 ++++- .../mongo/keycloak/adapters/RealmAdapter.java | 48 +++++++++++++--- .../performance/ReadUsersWorker.java | 2 +- 4 files changed, 103 insertions(+), 17 deletions(-) diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java index 72dd3d672c9a..49bcd31b8895 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java @@ -129,6 +129,28 @@ public void grantRole(UserModel user, RoleModel role) { 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 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) { @@ -218,16 +240,26 @@ public void addScopeMapping(UserModel agent, RoleModel role) { } @Override - public Set getScopeMapping(UserModel agent) { - UserData userData = ((UserAdapter)agent).getUser(); - List scopeIds = userData.getScopeIds(); + public void deleteScopeMapping(UserModel user, RoleModel role) { + UserData userData = ((UserAdapter)user).getUser(); + noSQL.pullItemFromList(userData, "scopeIds", role.getId()); + } - Set result = new HashSet(); + // Static so that it can be used from RealmAdapter as well + static List getAllScopesOfUser(UserModel user, NoSQL noSQL) { + UserData userData = ((UserAdapter)user).getUser(); + List roleIds = userData.getScopeIds(); NoSQLQuery query = noSQL.createQueryBuilder() - .inCondition("_id", scopeIds) + .inCondition("_id", roleIds) .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + return noSQL.loadObjects(RoleData.class, query); + } + + @Override + public Set getScopeMappingValues(UserModel agent) { + Set result = new HashSet(); + List 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())) { @@ -236,4 +268,17 @@ public Set getScopeMapping(UserModel agent) { } return result; } + + @Override + public List getScopeMappings(UserModel agent) { + List result = new ArrayList(); + List 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)); + } + } + return result; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java index d79b0a178246..34f455eb393c 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java @@ -4,6 +4,7 @@ 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; /** * @author Marek Posolda @@ -11,7 +12,7 @@ public class OAuthClientAdapter implements OAuthClientModel { private final OAuthClientData delegate; - private final UserAdapter oauthAgent; + private UserAdapter oauthAgent; private final NoSQL noSQL; public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) { @@ -20,6 +21,11 @@ public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgen this.noSQL = noSQL; } + public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) { + this.delegate = oauthClientData; + this.noSQL = noSQL; + } + @Override public String getId() { return delegate.getId(); @@ -27,6 +33,11 @@ public String getId() { @Override 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; + } return oauthAgent; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 73fcf3aaf153..837f9856245f 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -513,15 +513,26 @@ public void deleteRoleMapping(UserModel user, RoleModel role) { @Override public void addScopeMapping(UserModel agent, String roleName) { - UserData userData = ((UserAdapter)agent).getUser(); 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()); + } + @Override public OAuthClientModel addOAuthClient(String name) { UserAdapter oauthAgent = addUser(name); @@ -547,16 +558,35 @@ public OAuthClientModel getOAuthClient(String name) { } @Override - public Set getScopeMapping(UserModel agent) { - UserData userData = ((UserAdapter)agent).getUser(); - List scopeIds = userData.getScopeIds(); - - Set result = new HashSet(); - + public List getOAuthClients() { NoSQLQuery query = noSQL.createQueryBuilder() - .inCondition("_id", scopeIds) + .andCondition("realmId", getOid()) .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + List results = noSQL.loadObjects(OAuthClientData.class, query); + List list = new ArrayList(); + for (OAuthClientData data : results) { + list.add(new OAuthClientAdapter(data, noSQL)); + } + return list; + } + + @Override + public List getScopeMappings(UserModel agent) { + List result = new ArrayList(); + List roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL); + // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... + for (RoleData role : roles) { + if (getOid().equals(role.getRealmId())) { + result.add(new RoleAdapter(role, noSQL)); + } + } + return result; + } + + @Override + public Set getScopeMappingValues(UserModel agent) { + Set result = new HashSet(); + List roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL); // TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically... for (RoleData role : roles) { if (getOid().equals(role.getRealmId())) { diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java index 30bab6058665..416cd6029f54 100644 --- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/ReadUsersWorker.java @@ -95,7 +95,7 @@ public void run(SampleResult result, KeycloakSession identitySession) { // Read scopes of user in realm if (readScopes) { - realm.getScopeMapping(user); + realm.getScopeMappings(user); } // Validate password (shoould be same as username) From 7b357fbfecf3e9757b30f8543820be95ace3ff0b Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 10 Oct 2013 10:24:54 +0200 Subject: [PATCH 14/14] Don't use keycloak-model-mongo model by default --- model/pom.xml | 2 +- pom.xml | 14 -------------- services/pom.xml | 6 +++--- .../keycloak/test/common/AbstractKeycloakTest.java | 4 ++-- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/model/pom.xml b/model/pom.xml index 3d0b0dc31b68..7e2fca5b9b2a 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -37,6 +37,6 @@ api picketlink jpa - mongo + diff --git a/pom.xml b/pom.xml index 04b6ec76f272..f79b6338e26c 100755 --- a/pom.xml +++ b/pom.xml @@ -299,20 +299,6 @@ ${mysql.version} - - org.jboss.arquillian - arquillian-bom - 1.1.1.Final - pom - import - - - org.jboss.arquillian.extension - arquillian-drone-bom - 1.2.0.Beta1 - pom - import - diff --git a/services/pom.xml b/services/pom.xml index 6421497db29c..187d7be6956d 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -34,12 +34,12 @@ keycloak-model-picketlink ${project.version} - - + + org.keycloak keycloak-social-core diff --git a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java index 34f052bb5d9f..baaa9f665d2c 100644 --- a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java +++ b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java @@ -32,10 +32,10 @@ public abstract class AbstractKeycloakTest { static { - // TODO: Disable MongoDB by default and enable it just for some specific maven profile (system property)? + // TODO: MongoDB disabled by default TEST_CONTEXTS = new SessionFactoryTestContext[] { new PicketlinkSessionFactoryTestContext(), - new MongoDBSessionFactoryTestContext() + // new MongoDBSessionFactoryTestContext() }; }