From 81ff7b0c6dfd1f340aa9c6ddc12bb31d04a6bb9d Mon Sep 17 00:00:00 2001 From: mposolda Date: Sat, 1 Feb 2014 00:08:40 +0100 Subject: [PATCH] Refactoring Mongo model. All unit tests passing with mongo --- model/api/pom.xml | 11 + .../java/org/keycloak/models/IdGenerator.java | 15 - .../models/utils/KeycloakModelUtils.java | 86 ++ .../models/utils/KeycloakSessionUtils.java | 18 - .../models/jpa/JpaKeycloakSession.java | 4 +- .../org/keycloak/models/jpa/RealmAdapter.java | 43 +- .../org/keycloak/models/jpa/RoleAdapter.java | 17 +- model/mongo/pom.xml | 9 +- .../MongoDBSessionFactoryTestContext.java | 57 -- .../models/mongo/MongoRunnerListener.java | 53 -- .../models/mongo/PropertiesManager.java | 58 -- .../api/AbstractAttributedNoSQLObject.java | 37 - ...QLObject.java => AbstractMongoEntity.java} | 4 +- .../mongo/api/AttributedNoSQLObject.java | 17 - ...QLCollection.java => MongoCollection.java} | 2 +- .../{NoSQLObject.java => MongoEntity.java} | 8 +- .../api/{NoSQLField.java => MongoField.java} | 2 +- .../mongo/api/{NoSQLId.java => MongoId.java} | 2 +- .../keycloak/models/mongo/api/MongoStore.java | 43 + .../org/keycloak/models/mongo/api/NoSQL.java | 36 - .../models/mongo/api/query/NoSQLQuery.java | 26 - .../mongo/api/query/NoSQLQueryBuilder.java | 31 - .../models/mongo/api/types/Converter.java | 8 +- .../mongo/api/types/ConverterContext.java | 36 + .../models/mongo/api/types/TypeConverter.java | 15 +- .../mongo/impl/MongoDBQueryBuilder.java | 38 - .../{MongoDBImpl.java => MongoStoreImpl.java} | 198 ++-- .../models/mongo/impl/ObjectInfo.java | 8 +- .../impl/types/BasicDBListConverter.java | 51 +- .../impl/types/BasicDBObjectConverter.java | 70 +- .../types/BasicDBObjectToMapConverter.java | 44 + .../models/mongo/impl/types/ClassCache.java | 37 - .../impl/types/EnumToStringConverter.java | 8 +- .../mongo/impl/types/ListConverter.java | 14 +- .../models/mongo/impl/types/MapConverter.java | 52 ++ ...nverter.java => MongoEntityConverter.java} | 30 +- .../mongo/impl/types/SimpleConverter.java | 6 +- .../impl/types/StringToEnumConverter.java | 12 +- .../mongo/keycloak/MongoModelProvider.java | 16 +- .../keycloak/adapters/ApplicationAdapter.java | 262 +++--- .../adapters/MongoDBSessionFactory.java | 70 -- .../adapters/MongoKeycloakSession.java | 92 ++ .../adapters/MongoKeycloakSessionFactory.java | 83 ++ ...ion.java => MongoKeycloakTransaction.java} | 2 +- .../mongo/keycloak/adapters/NoSQLSession.java | 86 -- .../keycloak/adapters/OAuthClientAdapter.java | 25 +- .../mongo/keycloak/adapters/RealmAdapter.java | 861 ++++++++++-------- .../mongo/keycloak/adapters/RoleAdapter.java | 138 ++- .../mongo/keycloak/adapters/UserAdapter.java | 166 ++-- .../PasswordCredentialHandler.java | 154 ---- .../credentials/TOTPCredentialHandler.java | 135 --- .../mongo/keycloak/data/OAuthClientData.java | 62 -- .../models/mongo/keycloak/data/RealmData.java | 219 ----- .../keycloak/data/RequiredCredentialData.java | 90 -- .../models/mongo/keycloak/data/RoleData.java | 98 -- .../keycloak/data/credentials/OTPData.java | 66 -- .../data/credentials/PasswordData.java | 66 -- .../ApplicationEntity.java} | 62 +- .../keycloak/entities/CredentialEntity.java | 51 ++ .../keycloak/entities/OAuthClientEntity.java | 62 ++ .../mongo/keycloak/entities/RealmEntity.java | 255 ++++++ .../entities/RequiredCredentialEntity.java | 51 ++ .../mongo/keycloak/entities/RoleEntity.java | 148 +++ .../SocialLinkEntity.java} | 20 +- .../UserEntity.java} | 117 ++- .../models/mongo/utils/EmbeddedMongo.java | 50 + .../mongo/utils/MongoConfiguration.java | 50 + .../models/mongo/utils/MongoModelUtils.java | 59 ++ ...SystemPropertiesConfigurationProvider.java | 56 ++ .../keycloak/models/mongo/test/Address.java | 12 +- .../models/mongo/test/MongoDBModelTest.java | 56 +- .../keycloak/models/mongo/test/Person.java | 48 +- model/pom.xml | 2 +- pom.xml | 4 +- services/pom.xml | 18 +- .../resources/KeycloakApplication.java | 28 +- .../services/utils/ModelProviderUtils.java | 50 + .../services/utils/PropertiesManager.java | 88 -- .../managers/AuthenticationManagerTest.java | 8 +- .../java/org/keycloak/test/AdapterTest.java | 15 +- .../keycloak/test/ApplicationModelTest.java | 36 +- .../java/org/keycloak/test/ImportTest.java | 9 +- .../java/org/keycloak/test/ModelTest.java | 41 +- .../java/org/keycloak/test/UserModelTest.java | 38 +- .../test/common/AbstractKeycloakTest.java | 65 +- .../common/JpaSessionFactoryTestContext.java | 24 - .../PicketlinkSessionFactoryTestContext.java | 24 - .../common/SessionFactoryTestContext.java | 17 - 88 files changed, 2638 insertions(+), 2823 deletions(-) delete mode 100755 model/api/src/main/java/org/keycloak/models/IdGenerator.java create mode 100644 model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java delete mode 100644 model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java rename model/mongo/src/main/java/org/keycloak/models/mongo/api/{AbstractNoSQLObject.java => AbstractMongoEntity.java} (58%) delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java rename model/mongo/src/main/java/org/keycloak/models/mongo/api/{NoSQLCollection.java => MongoCollection.java} (92%) rename model/mongo/src/main/java/org/keycloak/models/mongo/api/{NoSQLObject.java => MongoEntity.java} (62%) rename model/mongo/src/main/java/org/keycloak/models/mongo/api/{NoSQLField.java => MongoField.java} (94%) rename model/mongo/src/main/java/org/keycloak/models/mongo/api/{NoSQLId.java => MongoId.java} (94%) create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/types/ConverterContext.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/{MongoDBImpl.java => MongoStoreImpl.java} (58%) create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapConverter.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapConverter.java rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{NoSQLObjectConverter.java => MongoEntityConverter.java} (59%) delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java rename model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/{NoSQLTransaction.java => MongoKeycloakTransaction.java} (93%) delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java rename model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/{data/ApplicationData.java => entities/ApplicationEntity.java} (58%) create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java rename model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/{data/SocialLinkData.java => entities/SocialLinkEntity.java} (74%) rename model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/{data/UserData.java => entities/UserEntity.java} (54%) create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoConfiguration.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/utils/SystemPropertiesConfigurationProvider.java create mode 100644 services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java delete mode 100755 services/src/main/java/org/keycloak/services/utils/PropertiesManager.java delete mode 100755 services/src/test/java/org/keycloak/test/common/JpaSessionFactoryTestContext.java delete mode 100755 services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java delete mode 100644 services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java diff --git a/model/api/pom.xml b/model/api/pom.xml index 2c64294f4a..a5260d49d5 100755 --- a/model/api/pom.xml +++ b/model/api/pom.xml @@ -17,6 +17,17 @@ net.iharder base64 + + org.bouncycastle + bcprov-jdk16 + provided + + + org.keycloak + keycloak-core + ${project.version} + provided + junit junit diff --git a/model/api/src/main/java/org/keycloak/models/IdGenerator.java b/model/api/src/main/java/org/keycloak/models/IdGenerator.java deleted file mode 100755 index 3a1b028200..0000000000 --- a/model/api/src/main/java/org/keycloak/models/IdGenerator.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.keycloak.models; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class IdGenerator { - private static AtomicLong counter = new AtomicLong(1); - public static String generateId() { - return counter.getAndIncrement() + "-" + System.currentTimeMillis(); - } - -} diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java new file mode 100644 index 0000000000..4582a03274 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -0,0 +1,86 @@ +package org.keycloak.models.utils; + +import java.io.IOException; +import java.io.StringWriter; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import org.bouncycastle.openssl.PEMWriter; +import org.keycloak.models.RoleModel; +import org.keycloak.util.PemUtils; + +/** + * Set of helper methods, which are useful in various model implementations. + * + * @author Marek Posolda + */ +public final class KeycloakModelUtils { + + private KeycloakModelUtils() { + } + + private static AtomicLong counter = new AtomicLong(1); + + public static String generateId() { + return counter.getAndIncrement() + "-" + System.currentTimeMillis(); + } + + public static PublicKey getPublicKey(String publicKeyPem) { + if (publicKeyPem != null) { + try { + return PemUtils.decodePublicKey(publicKeyPem); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + return null; + } + } + + public static PrivateKey getPrivateKey(String privateKeyPem) { + if (privateKeyPem != null) { + try { + return PemUtils.decodePrivateKey(privateKeyPem); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return null; + } + + public static String getPemFromKey(Key key) { + StringWriter writer = new StringWriter(); + PEMWriter pemWriter = new PEMWriter(writer); + try { + pemWriter.writeObject(key); + pemWriter.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + String s = writer.toString(); + return PemUtils.removeBeginEnd(s); + } + + /** + * Deep search if given role is descendant of composite role + * + * @param role role to check + * @param composite composite role + * @param visited set of already visited roles (used for recursion) + * @return true if "role" is descendant of "composite" + */ + public static boolean searchFor(RoleModel role, RoleModel composite, Set visited) { + if (visited.contains(composite)) return false; + visited.add(composite); + Set composites = composite.getComposites(); + if (composites.contains(role)) return true; + for (RoleModel contained : composites) { + if (!contained.isComposite()) continue; + if (searchFor(role, contained, visited)) return true; + } + return false; + } +} 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 deleted file mode 100644 index 57285db004..0000000000 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.keycloak.models.utils; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * @author Marek Posolda - */ -public final class KeycloakSessionUtils { - - private KeycloakSessionUtils() { - } - - private static AtomicLong counter = new AtomicLong(1); - - public static String generateId() { - return counter.getAndIncrement() + "-" + System.currentTimeMillis(); - } -} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java index 7b7db28237..a072c75cc6 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java @@ -2,7 +2,7 @@ package org.keycloak.models.jpa; import org.keycloak.models.*; import org.keycloak.models.jpa.entities.*; -import org.keycloak.models.utils.KeycloakSessionUtils; +import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -28,7 +28,7 @@ public class JpaKeycloakSession implements KeycloakSession { @Override public RealmModel createRealm(String name) { - return createRealm(KeycloakSessionUtils.generateId(), name); + return createRealm(KeycloakModelUtils.generateId(), name); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 965cbbee4f..467953ca54 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -13,6 +13,7 @@ import org.keycloak.models.jpa.entities.SocialLinkEntity; import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.jpa.entities.UserRoleMappingEntity; import org.keycloak.models.jpa.entities.UserScopeMappingEntity; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.Pbkdf2PasswordEncoder; import org.keycloak.util.PemUtils; import org.keycloak.models.ApplicationModel; @@ -187,59 +188,29 @@ public class RealmAdapter implements RealmModel { @Override public PublicKey getPublicKey() { if (publicKey != null) return publicKey; - String pem = getPublicKeyPem(); - if (pem != null) { - try { - publicKey = PemUtils.decodePublicKey(pem); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem()); return publicKey; } @Override public void setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; - StringWriter writer = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(writer); - try { - pemWriter.writeObject(publicKey); - pemWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - String s = writer.toString(); - setPublicKeyPem(PemUtils.removeBeginEnd(s)); + String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey); + setPublicKeyPem(publicKeyPem); } @Override public PrivateKey getPrivateKey() { if (privateKey != null) return privateKey; - String pem = getPrivateKeyPem(); - if (pem != null) { - try { - privateKey = PemUtils.decodePrivateKey(pem); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem()); return privateKey; } @Override public void setPrivateKey(PrivateKey privateKey) { this.privateKey = privateKey; - StringWriter writer = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(writer); - try { - pemWriter.writeObject(privateKey); - pemWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - String s = writer.toString(); - setPrivateKeyPem(PemUtils.removeBeginEnd(s)); + String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey); + setPrivateKeyPem(privateKeyPem); } protected RequiredCredentialModel initRequiredCredentialModel(String type) { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java index 2c9a8c95a9..e418441de2 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java @@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.jpa.entities.ApplicationRoleEntity; import org.keycloak.models.jpa.entities.RealmRoleEntity; import org.keycloak.models.jpa.entities.RoleEntity; +import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import java.util.HashSet; @@ -94,27 +95,13 @@ public class RoleAdapter implements RoleModel { return set; } - public static boolean searchFor(RoleModel role, RoleModel composite, Set visited) { - if (visited.contains(composite)) return false; - visited.add(composite); - Set composites = composite.getComposites(); - if (composites.contains(role)) return true; - for (RoleModel contained : composites) { - if (!contained.isComposite()) continue; - if (searchFor(role, contained, visited)) return true; - } - return false; - } - - - @Override public boolean hasRole(RoleModel role) { if (this.equals(role)) return true; if (!isComposite()) return false; Set visited = new HashSet(); - return searchFor(role, this, visited); + return KeycloakModelUtils.searchFor(role, this, visited); } @Override diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml index 537112a618..d1b5952dd4 100755 --- a/model/mongo/pom.xml +++ b/model/mongo/pom.xml @@ -5,7 +5,7 @@ keycloak-parent org.keycloak - 1.0-alpha-1 + 1.0-alpha-2-SNAPSHOT ../../pom.xml 4.0.0 @@ -41,11 +41,6 @@ picketlink-common provided - - org.picketlink - picketlink-idm-api - provided - org.mongodb mongo-java-driver @@ -54,7 +49,7 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - test + provided junit diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java deleted file mode 100755 index 1b00fa5b54..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoDBSessionFactoryTestContext.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.keycloak.test.common; - -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; -import de.flapdoodle.embed.process.runtime.Network; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.utils.PropertiesManager; - -/** - * @author Marek Posolda - */ -public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext { - - protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class); - private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS; - - private MongodExecutable mongodExe; - private MongodProcess mongod; - - @Override - public void beforeTestClass() { - logger.info("Bootstrapping MongoDB on localhost, port " + PORT); - try { - mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, PORT, Network.localhostIsIPv6())); - mongod = mongodExe.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } - logger.info("MongoDB bootstrapped successfully"); - } - - @Override - public void afterTestClass() { - if (mongodExe != null) { - if (mongod != null) { - mongod.stop(); - } - mongodExe.stop(); - } - logger.info("MongoDB stopped successfully"); - - // Reset this, so other tests are not affected - PropertiesManager.setDefaultSessionFactoryType(); - } - - @Override - public void initEnvironment() { - PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO); - PropertiesManager.setMongoHost("localhost"); - PropertiesManager.setMongoPort(PORT); - PropertiesManager.setMongoDbName("keycloakTest"); - PropertiesManager.setDropDatabaseOnStartup(true); - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java b/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java deleted file mode 100755 index a1f67883c8..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/MongoRunnerListener.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.keycloak.services.listeners; - -import de.flapdoodle.embed.mongo.MongodExecutable; -import de.flapdoodle.embed.mongo.MongodProcess; -import de.flapdoodle.embed.mongo.MongodStarter; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; -import de.flapdoodle.embed.process.runtime.Network; -import org.jboss.resteasy.logging.Logger; -import org.keycloak.services.utils.PropertiesManager; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -/** - * @author 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/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java b/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java deleted file mode 100755 index 9e897ae238..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/PropertiesManager.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.keycloak.models.mongo; - -/** - * @author Marek Posolda - */ -public class PropertiesManager { - - private static final String MONGO_HOST = "keycloak.mongodb.host"; - private static final String MONGO_PORT = "keycloak.mongodb.port"; - private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName"; - private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup"; - private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit"; - - // Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then - private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017; - - // Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false) - private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017; - - // Port where unit tests will start embedded MongoDB instance - public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777; - - public static String getMongoHost() { - return System.getProperty(MONGO_HOST, "localhost"); - } - - public static void setMongoHost(String mongoHost) { - System.setProperty(MONGO_HOST, mongoHost); - } - - public static int getMongoPort() { - return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED))); - } - - public static void setMongoPort(int mongoPort) { - System.setProperty(MONGO_PORT, String.valueOf(mongoPort)); - } - - public static String getMongoDbName() { - return System.getProperty(MONGO_DB_NAME, "keycloak"); - } - - public static void setMongoDbName(String mongoMongoDbName) { - System.setProperty(MONGO_DB_NAME, mongoMongoDbName); - } - - public static boolean dropDatabaseOnStartup() { - return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true")); - } - - public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) { - System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup)); - } - - public static boolean bootstrapEmbeddedMongoAtContextInit() { - return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true")); - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java deleted file mode 100644 index 81546ba469..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractAttributedNoSQLObject.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.keycloak.models.mongo.api; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject 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/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java similarity index 58% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java index 837e5e4644..aaaffabe07 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractNoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java @@ -3,10 +3,10 @@ package org.keycloak.models.mongo.api; /** * @author Marek Posolda */ -public abstract class AbstractNoSQLObject implements NoSQLObject { +public abstract class AbstractMongoEntity implements MongoEntity { @Override - public void afterRemove(NoSQL noSQL) { + public void afterRemove(MongoStore mongoStore) { // Empty by default } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java deleted file mode 100644 index 45accd1c1c..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AttributedNoSQLObject.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.keycloak.models.mongo.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/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java similarity index 92% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java index 80b63326f0..8695d12393 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLCollection.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoCollection.java @@ -15,7 +15,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Retention(RUNTIME) @Inherited -public @interface NoSQLCollection { +public @interface MongoCollection { String collectionName(); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java similarity index 62% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java index 0242243936..6af6bbc0ea 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLObject.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java @@ -1,16 +1,16 @@ package org.keycloak.models.mongo.api; /** - * Base interface for object, which is persisted in NoSQL database + * Base interface for object, which is persisted in Mongo * * @author Marek Posolda */ -public interface NoSQLObject { +public interface MongoEntity { /** - * Lifecycle callback, which is called after removal of this object from NoSQL database. + * Lifecycle callback, which is called after removal of this object from Mongo. * It may be useful for triggering removal of wired objects. */ - void afterRemove(NoSQL noSQL); + void afterRemove(MongoStore mongoStore); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java similarity index 94% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java index 3af69a7135..4f19f83e7d 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLField.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoField.java @@ -14,7 +14,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({METHOD, FIELD}) @Documented @Retention(RUNTIME) -public @interface NoSQLField { +public @interface MongoField { // TODO: fieldName add lazy loading? } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java similarity index 94% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java index 06ed01e655..6799635cc5 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQLId.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java @@ -14,5 +14,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({METHOD, FIELD}) @Documented @Retention(RUNTIME) -public @interface NoSQLId { +public @interface MongoId { } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java new file mode 100755 index 0000000000..89e4bb744c --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java @@ -0,0 +1,43 @@ +package org.keycloak.models.mongo.api; + +import com.mongodb.DBObject; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public interface MongoStore { + + /** + * Insert new object + * + * @param object to update + */ + void insertObject(MongoEntity object); + + /** + * Update existing object + * + * @param object to update + */ + void updateObject(MongoEntity object); + + + T loadObject(Class type, String oid); + + T loadSingleObject(Class type, DBObject query); + + List loadObjects(Class type, DBObject query); + + // Object must have filled oid + boolean removeObject(MongoEntity object); + + boolean removeObject(Class type, String oid); + + boolean removeObjects(Class type, DBObject query); + + boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent); + + void pullItemFromList(MongoEntity object, String listPropertyName, S itemToPull); +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java deleted file mode 100755 index 0a63606e2d..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/NoSQL.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.keycloak.models.mongo.api; - -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; - -import java.util.List; - -/** - * @author 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); - - 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, NoSQLQuery query); - - NoSQLQueryBuilder createQueryBuilder(); - - void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush); - - void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull); -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java deleted file mode 100644 index 29cc0f31ab..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQuery.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.keycloak.models.mongo.api.query; - -import java.util.Collections; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public class NoSQLQuery { - - private final Map queryAttributes; - - NoSQLQuery(Map queryAttributes) { - this.queryAttributes = queryAttributes; - }; - - public Map getQueryAttributes() { - return Collections.unmodifiableMap(queryAttributes); - } - - @Override - public String toString() { - return "NoSQLQuery [" + queryAttributes + "]"; - } - -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java deleted file mode 100644 index dcdb5752a3..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/query/NoSQLQueryBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.keycloak.models.mongo.api.query; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author Marek Posolda - */ -public abstract class NoSQLQueryBuilder { - - private Map queryAttributes = new HashMap(); - - protected NoSQLQueryBuilder() {}; - - public NoSQLQuery build() { - return new NoSQLQuery(queryAttributes); - } - - public NoSQLQueryBuilder andCondition(String name, Object value) { - this.put(name, value); - return this; - } - - public abstract NoSQLQueryBuilder inCondition(String name, List values); - - protected void put(String name, Object value) { - queryAttributes.put(name, value); - } - -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java index a6b6c869e6..9c963c1464 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java @@ -8,7 +8,13 @@ package org.keycloak.models.mongo.api.types; */ public interface Converter { - S convertObject(T objectToConvert); + /** + * Convert object from one type to expected type + * + * @param converterContext Encapsulates reference to converted object and other things, which might be helpful in conversion + * @return converted object + */ + S convertObject(ConverterContext converterContext); Class getConverterObjectType(); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/ConverterContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/ConverterContext.java new file mode 100644 index 0000000000..aecb8c09ef --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/ConverterContext.java @@ -0,0 +1,36 @@ +package org.keycloak.models.mongo.api.types; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public class ConverterContext { + + // object to convert + private final T objectToConvert; + + // expected return type, which could be useful information in some converters, so they are able to dynamically instantiate types + private final Class expectedReturnType; + + // in case that expected return type is generic type (like "List"), then genericTypes could contain list of expected generic arguments + private final List> genericTypes; + + public ConverterContext(T objectToConvert, Class expectedReturnType, List> genericTypes) { + this.objectToConvert = objectToConvert; + this.expectedReturnType = expectedReturnType; + this.genericTypes = genericTypes; + } + + public T getObjectToConvert() { + return objectToConvert; + } + + public Class getExpectedReturnType() { + return expectedReturnType; + } + + public List> getGenericTypes() { + return genericTypes; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java index e097930774..05ad4d1b57 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java @@ -46,19 +46,22 @@ public class TypeConverter { } - public S convertDBObjectToApplicationObject(Object dbObject, Class expectedApplicationObjectType) { + public Object convertDBObjectToApplicationObject(ConverterContext context) { + Object dbObject = context.getObjectToConvert(); + Class expectedApplicationObjectType = context.getExpectedReturnType(); + Class dbObjectType = dbObject.getClass(); - Converter converter; + 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(); + converter = (Converter)appObjects.values().iterator().next(); } else { // Try to find converter for requested application type - converter = (Converter)getAppConverterForType(expectedApplicationObjectType, appObjects); + converter = (Converter)getAppConverterForType(context.getExpectedReturnType(), appObjects); } } @@ -70,7 +73,7 @@ public class TypeConverter { " but we need type " + expectedApplicationObjectType); } */ - return converter.convertObject(dbObject); + return converter.convertObject(context); } @@ -84,7 +87,7 @@ public class TypeConverter { throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + " but we need type " + expectedDBObjectType); } - return converter.convertObject(applicationObject); + return converter.convertObject(new ConverterContext(applicationObject, expectedDBObjectType, null)); } // Try to find converter for given type or all it's supertypes diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java deleted file mode 100755 index 2d1f61de6a..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBQueryBuilder.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.keycloak.models.mongo.impl; - -import com.mongodb.BasicDBObject; -import org.bson.types.ObjectId; -import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * @author Marek Posolda - */ -public class MongoDBQueryBuilder extends NoSQLQueryBuilder { - - protected MongoDBQueryBuilder() {}; - - @Override - public NoSQLQueryBuilder inCondition(String name, List values) { - if (values == null) { - values = new LinkedList(); - } - - if ("_id".equals(name)) { - // we need to convert Strings to ObjectID - List objIds = new ArrayList(); - for (Object object : values) { - ObjectId objectId = new ObjectId(object.toString()); - objIds.add(objectId); - } - values = objIds; - } - - BasicDBObject inObject = new BasicDBObject("$in", values); - put(name, inObject); - return this; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java similarity index 58% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java index 6bacedb288..0c2d5610d1 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoDBImpl.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java @@ -8,20 +8,21 @@ import com.mongodb.DBCursor; import com.mongodb.DBObject; import org.bson.types.ObjectId; import org.jboss.logging.Logger; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; import org.keycloak.models.mongo.impl.types.BasicDBListConverter; import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter; +import org.keycloak.models.mongo.impl.types.BasicDBObjectToMapConverter; import org.keycloak.models.mongo.impl.types.EnumToStringConverter; import org.keycloak.models.mongo.impl.types.ListConverter; -import org.keycloak.models.mongo.impl.types.NoSQLObjectConverter; +import org.keycloak.models.mongo.impl.types.MapConverter; +import org.keycloak.models.mongo.impl.types.MongoEntityConverter; import org.keycloak.models.mongo.impl.types.SimpleConverter; import org.keycloak.models.mongo.impl.types.StringToEnumConverter; import org.picketlink.common.properties.Property; @@ -30,6 +31,7 @@ import org.picketlink.common.properties.query.PropertyQueries; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -38,19 +40,19 @@ import java.util.concurrent.ConcurrentMap; /** * @author Marek Posolda */ -public class MongoDBImpl implements NoSQL { +public class MongoStoreImpl implements MongoStore { - private static final Class[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class }; + private static final Class[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class, byte[].class }; private final DB database; - private static final Logger logger = Logger.getLogger(MongoDBImpl.class); + private static final Logger logger = Logger.getLogger(MongoStoreImpl.class); private final TypeConverter typeConverter; - private ConcurrentMap, ObjectInfo> objectInfoCache = - new ConcurrentHashMap, ObjectInfo>(); + private ConcurrentMap, ObjectInfo> objectInfoCache = + new ConcurrentHashMap, ObjectInfo>(); - public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class[] managedDataTypes) { + public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class[] managedEntityTypes) { this.database = database; typeConverter = new TypeConverter(); @@ -66,26 +68,45 @@ public class MongoDBImpl implements NoSQL { typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class)); typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter)); + typeConverter.addAppObjectConverter(new MapConverter(HashMap.class)); + typeConverter.addAppObjectConverter(new MapConverter(Map.class)); + typeConverter.addDBObjectConverter(new BasicDBObjectToMapConverter()); + // Enum converters typeConverter.addAppObjectConverter(new EnumToStringConverter()); typeConverter.addDBObjectConverter(new StringToEnumConverter()); - for (Class type : managedDataTypes) { + for (Class type : managedEntityTypes) { getObjectInfo(type); - typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type)); + typeConverter.addAppObjectConverter(new MongoEntityConverter(this, typeConverter, type)); typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type)); } - if (dropDatabaseOnStartup) { - this.database.dropDatabase(); - logger.info("Database " + this.database.getName() + " dropped in MongoDB"); + if (clearCollectionsOnStartup) { + // dropDatabase(); + clearManagedCollections(managedEntityTypes); } } + protected void dropDatabase() { + this.database.dropDatabase(); + logger.info("Database " + this.database.getName() + " dropped in MongoDB"); + } + + // Don't drop database, but just clear all data in managed collections (useful for development) + protected void clearManagedCollections(Class[] managedEntityTypes) { + for (Class clazz : managedEntityTypes) { + DBCollection dbCollection = getDBCollectionForType(clazz); + if (dbCollection != null) { + dbCollection.remove(new BasicDBObject()); + logger.debug("Collection " + dbCollection.getName() + " cleared from " + this.database.getName()); + } + } + } @Override - public void saveObject(NoSQLObject object) { - Class clazz = object.getClass(); + public void insertObject(MongoEntity object) { + Class clazz = object.getClass(); // Find annotations for ID, for all the properties and for the name of the collection. ObjectInfo objectInfo = getObjectInfo(clazz); @@ -95,36 +116,57 @@ public class MongoDBImpl implements NoSQL { DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); - // Decide if we should insert or update (based on presence of oid property in original object) Property oidProperty = objectInfo.getOidProperty(); String currentId = oidProperty == null ? null : oidProperty.getValue(object); - if (currentId == null) { - dbCollection.insert(dbObject); - // Add oid to value of given object - if (oidProperty != null) { - oidProperty.setValue(object, dbObject.getString("_id")); - } + // Inserting object, which already has oid property set. So we need to set "_id" + if (currentId != null) { + dbObject.put("_id", getObjectId(currentId)); + } + + dbCollection.insert(dbObject); + + // Add oid to value of given object + if (currentId == null && oidProperty != null) { + oidProperty.setValue(object, dbObject.getString("_id")); + } + } + + @Override + public void updateObject(MongoEntity object) { + Class clazz = object.getClass(); + ObjectInfo objectInfo = getObjectInfo(clazz); + BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class); + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + Property oidProperty = objectInfo.getOidProperty(); + String currentId = oidProperty == null ? null : oidProperty.getValue(object); + + if (currentId == null) { + throw new IllegalStateException("Can't update object without id: " + object); } else { - BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId)); + BasicDBObject query = new BasicDBObject("_id", getObjectId(currentId)); dbCollection.update(query, dbObject); } } @Override - public T loadObject(Class type, String oid) { + public T loadObject(Class type, String oid) { DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid)); + BasicDBObject idQuery = new BasicDBObject("_id", getObjectId(oid)); DBObject dbObject = dbCollection.findOne(idQuery); - return typeConverter.convertDBObjectToApplicationObject(dbObject, type); + if (dbObject == null) return null; + + ConverterContext converterContext = new ConverterContext(dbObject, type, null); + return (T)typeConverter.convertDBObjectToApplicationObject(converterContext); } @Override - public T loadSingleObject(Class type, NoSQLQuery query) { + public T loadSingleObject(Class type, DBObject 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"); @@ -138,71 +180,64 @@ public class MongoDBImpl implements NoSQL { @Override - public List loadObjects(Class type, NoSQLQuery query) { + public List loadObjects(Class type, DBObject query) { DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject dbQuery = getDBQueryFromQuery(query); - DBCursor cursor = dbCollection.find(dbQuery); + DBCursor cursor = dbCollection.find(query); return convertCursor(type, cursor); } @Override - public void removeObject(NoSQLObject object) { - Class type = object.getClass(); + public boolean removeObject(MongoEntity object) { + Class type = object.getClass(); ObjectInfo objectInfo = getObjectInfo(type); Property idProperty = objectInfo.getOidProperty(); String oid = idProperty.getValue(object); - removeObject(type, oid); + return removeObject(type, oid); } @Override - public void removeObject(Class type, String oid) { - NoSQLObject found = loadObject(type, oid); + public boolean removeObject(Class type, String oid) { + MongoEntity found = loadObject(type, oid); if (found == null) { - logger.warn("Object of type: " + type + ", oid: " + oid + " doesn't exist in MongoDB. Skip removal"); + return false; } else { DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid)); + BasicDBObject dbQuery = new BasicDBObject("_id", getObjectId(oid)); dbCollection.remove(dbQuery); logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB."); found.afterRemove(this); + return true; } } @Override - public void removeObjects(Class type, NoSQLQuery query) { - List foundObjects = loadObjects(type, query); + public boolean removeObjects(Class type, DBObject query) { + List foundObjects = loadObjects(type, query); if (foundObjects.size() == 0) { - logger.info("Not found any objects of type: " + type + ", query: " + query); + return false; } else { DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject dbQuery = getDBQueryFromQuery(query); - dbCollection.remove(dbQuery); + dbCollection.remove(query); logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query); - for (NoSQLObject found : foundObjects) { + for (MongoEntity found : foundObjects) { found.afterRemove(this); } + return true; } } - @Override - public NoSQLQueryBuilder createQueryBuilder() { - return new MongoDBQueryBuilder(); - } - - - @Override - public void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush) { - Class type = object.getClass(); + public boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent) { + Class type = object.getClass(); ObjectInfo objectInfo = getObjectInfo(type); Property oidProperty = getObjectInfo(type).getOidProperty(); @@ -221,21 +256,28 @@ public class MongoDBImpl implements NoSQL { list = new ArrayList(); listProperty.setValue(object, list); } + + // Return if item is already in list + if (skipIfAlreadyPresent && list.contains(itemToPush)) { + return false; + } + list.add(itemToPush); - // Push item to DB. We always convert whole list, so it's not so optimal... + // Push item to DB. We always convert whole list, so it's not so optimal...TODO: use $push if possible BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(list, BasicDBList.class); - BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object))); + BasicDBObject query = new BasicDBObject("_id", getObjectId(oidProperty.getValue(object))); BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList); BasicDBObject setCommand = new BasicDBObject("$set", listObject); getDBCollectionForType(type).update(query, setCommand); + return true; } @Override - public void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull) { - Class type = object.getClass(); + public void pullItemFromList(MongoEntity object, String listPropertyName, S itemToPull) { + Class type = object.getClass(); ObjectInfo objectInfo = getObjectInfo(type); Property oidProperty = getObjectInfo(type).getOidProperty(); @@ -256,7 +298,7 @@ public class MongoDBImpl implements NoSQL { // Pull item from DB Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class); - BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object))); + BasicDBObject query = new BasicDBObject("_id", getObjectId(oidProperty.getValue(object))); BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull); BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject); getDBCollectionForType(type).update(query, pullCommand); @@ -272,14 +314,14 @@ public class MongoDBImpl implements NoSQL { typeConverter.addDBObjectConverter(converter); } - public ObjectInfo getObjectInfo(Class 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(); + Property idProperty = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoId.class)).getFirstResult(); - List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList(); + List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList(); - NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class); + MongoCollection classAnnotation = objectClass.getAnnotation(MongoCollection.class); String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName(); objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties); @@ -293,12 +335,13 @@ public class MongoDBImpl implements NoSQL { return objectInfo; } - private List convertCursor(Class type, DBCursor cursor) { + private List convertCursor(Class type, DBCursor cursor) { List result = new ArrayList(); try { for (DBObject dbObject : cursor) { - T converted = typeConverter.convertDBObjectToApplicationObject(dbObject, type); + ConverterContext converterContext = new ConverterContext(dbObject, type, null); + T converted = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); result.add(converted); } } finally { @@ -308,17 +351,18 @@ public class MongoDBImpl implements NoSQL { return result; } - private DBCollection getDBCollectionForType(Class type) { + private DBCollection getDBCollectionForType(Class type) { ObjectInfo objectInfo = getObjectInfo(type); - return database.getCollection(objectInfo.getDbCollectionName()); + String dbCollectionName = objectInfo.getDbCollectionName(); + return dbCollectionName==null ? null : database.getCollection(objectInfo.getDbCollectionName()); } - private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) { - Map queryAttributes = query.getQueryAttributes(); - BasicDBObject dbQuery = new BasicDBObject(); - for (Map.Entry queryAttr : queryAttributes.entrySet()) { - dbQuery.append(queryAttr.getKey(), queryAttr.getValue()); + // We allow ObjectId to be both "ObjectId" or "String". + private Object getObjectId(String idAsString) { + if (ObjectId.isValid(idAsString)) { + return new ObjectId(idAsString); + } else { + return idAsString; } - return dbQuery; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java index b511626d9d..0662ad6aaf 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java @@ -1,6 +1,6 @@ package org.keycloak.models.mongo.impl; -import org.keycloak.models.mongo.api.NoSQLObject; +import org.keycloak.models.mongo.api.MongoEntity; import org.picketlink.common.properties.Property; import java.util.Collection; @@ -14,7 +14,7 @@ import java.util.Map; */ public class ObjectInfo { - private final Class objectClass; + private final Class objectClass; private final String dbCollectionName; @@ -22,7 +22,7 @@ public class ObjectInfo { private final Map> 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; @@ -34,7 +34,7 @@ public class ObjectInfo { this.properties = Collections.unmodifiableMap(props); } - public Class getObjectClass() { + public Class getObjectClass() { return objectClass; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java index 04824ba5cb..544e1f8210 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java @@ -1,16 +1,17 @@ package org.keycloak.models.mongo.impl.types; import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; import java.util.ArrayList; +import java.util.List; /** * @author Marek Posolda */ -public class BasicDBListConverter implements Converter { +public class BasicDBListConverter implements Converter { private final TypeConverter typeConverter; @@ -19,16 +20,14 @@ public class BasicDBListConverter implements Converter { } @Override - public ArrayList convertObject(BasicDBList dbList) { + public List convertObject(ConverterContext context) { + BasicDBList dbList = context.getObjectToConvert(); ArrayList appObjects = new ArrayList(); - Class expectedListElementType = null; + Class expectedListElementType = context.getGenericTypes().get(0); + for (Object dbObject : dbList) { - - if (expectedListElementType == null) { - expectedListElementType = findExpectedListElementType(dbObject); - } - - appObjects.add(typeConverter.convertDBObjectToApplicationObject(dbObject, expectedListElementType)); + ConverterContext newContext = new ConverterContext(dbObject, expectedListElementType, null); + appObjects.add(typeConverter.convertDBObjectToApplicationObject(newContext)); } return appObjects; } @@ -39,35 +38,7 @@ public class BasicDBListConverter implements Converter { } @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 { - // Special case (if we have String like "org.keycloak.Gender###MALE" we expect that substring before ### is className - if (String.class.equals(dbObject.getClass())) { - String dbObjString = (String)dbObject; - if (dbObjString.contains(ClassCache.SPLIT)) { - String className = dbObjString.substring(0, dbObjString.indexOf(ClassCache.SPLIT)); - return ClassCache.getInstance().getOrLoadClass(className); - } - } - - return dbObject.getClass(); - } + public Class getExpectedReturnType() { + return List.class; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java index a423652b38..62d0a821d0 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java @@ -1,12 +1,17 @@ package org.keycloak.models.mongo.impl.types; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + import com.mongodb.BasicDBObject; import org.jboss.logging.Logger; -import org.keycloak.models.mongo.api.AttributedNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLObject; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; -import org.keycloak.models.mongo.impl.MongoDBImpl; +import org.keycloak.models.mongo.impl.MongoStoreImpl; import org.keycloak.models.mongo.impl.ObjectInfo; import org.picketlink.common.properties.Property; import org.picketlink.common.reflection.Types; @@ -14,31 +19,32 @@ import org.picketlink.common.reflection.Types; /** * @author Marek Posolda */ -public class BasicDBObjectConverter implements Converter { +public class BasicDBObjectConverter implements Converter { private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class); - private final MongoDBImpl mongoDBImpl; + private final MongoStoreImpl mongoStoreImpl; private final TypeConverter typeConverter; - private final Class expectedNoSQLObjectType; + private final Class expectedObjectType; - public BasicDBObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { - this.mongoDBImpl = mongoDBImpl; + public BasicDBObjectConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class expectedObjectType) { + this.mongoStoreImpl = mongoStoreImpl; this.typeConverter = typeConverter; - this.expectedNoSQLObjectType = expectedNoSQLObjectType; + this.expectedObjectType = expectedObjectType; } @Override - public S convertObject(BasicDBObject dbObject) { + public S convertObject(ConverterContext context) { + BasicDBObject dbObject = context.getObjectToConvert(); if (dbObject == null) { return null; } - ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType); + ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(expectedObjectType); S object; try { - object = expectedNoSQLObjectType.newInstance(); + object = expectedObjectType.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } @@ -58,34 +64,50 @@ public class BasicDBObjectConverter implements Converter< // It's declared property with @DBField annotation setPropertyValue(object, value, property); - } else if (object instanceof AttributedNoSQLObject) { - // It's attributed object and property is not declared, so we will call setAttribute - ((AttributedNoSQLObject)object).setAttribute(key, value.toString()); - } else { // Show warning if it's unknown - logger.warn("Property with key " + key + " not known for type " + expectedNoSQLObjectType); + logger.warn("Property with key " + key + " not known for type " + expectedObjectType); } } return object; } - private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) { + private void setPropertyValue(MongoEntity object, Object valueFromDB, Property property) { if (valueFromDB == null) { property.setValue(object, null); return; } - Class expectedReturnType = property.getJavaClass(); - // handle primitives - expectedReturnType = Types.boxedClass(expectedReturnType); + ConverterContext context; + + Type type = property.getBaseType(); + + // This can be the case when we have parameterized type (like "List") + if (type instanceof ParameterizedType) { + ParameterizedType parameterized = (ParameterizedType) type; + Type[] genericTypeArguments = parameterized.getActualTypeArguments(); + + List> genericTypes = new ArrayList>(); + for (Type genericType : genericTypeArguments) { + genericTypes.add((Class)genericType); + } + + Class expectedReturnType = (Class)parameterized.getRawType(); + context = new ConverterContext(valueFromDB, expectedReturnType, genericTypes); + } else { + Class expectedReturnType = (Class)type; + // handle primitives + expectedReturnType = Types.boxedClass(expectedReturnType); + context = new ConverterContext(valueFromDB, expectedReturnType, null); + } + + Object appObject = typeConverter.convertDBObjectToApplicationObject(context); - Object appObject = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedReturnType); if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) { property.setValue(object, appObject); } else { - throw new IllegalStateException("Converted object " + appObject + " is not of type " + expectedReturnType + + throw new IllegalStateException("Converted object " + appObject + " is not of type " + context.getExpectedReturnType() + ". So can't be assigned as property " + property.getName() + " of " + object.getClass()); } } @@ -97,6 +119,6 @@ public class BasicDBObjectConverter implements Converter< @Override public Class getExpectedReturnType() { - return expectedNoSQLObjectType; + return expectedObjectType; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapConverter.java new file mode 100644 index 0000000000..4314ade21c --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapConverter.java @@ -0,0 +1,44 @@ +package org.keycloak.models.mongo.impl.types; + +import java.util.HashMap; +import java.util.Map; + +import com.mongodb.BasicDBObject; +import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; + +/** + * For now, we support just convert to Map + * + * @author Marek Posolda + */ +public class BasicDBObjectToMapConverter implements Converter { + + @Override + public Map convertObject(ConverterContext context) { + BasicDBObject objectToConvert = context.getObjectToConvert(); + + HashMap result = new HashMap(); + for (Map.Entry entry : objectToConvert.entrySet()) { + String key = entry.getKey(); + String value = (String)entry.getValue(); + + if (key.contains(MapConverter.DOT_PLACEHOLDER)) { + key = key.replaceAll(MapConverter.DOT_PLACEHOLDER, "."); + } + + result.put(key, value); + } + return result; + } + + @Override + public Class getConverterObjectType() { + return BasicDBObject.class; + } + + @Override + public Class getExpectedReturnType() { + return Map.class; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java deleted file mode 100644 index 891ccdd0e5..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ClassCache.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.keycloak.models.mongo.impl.types; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Helper class for caching of classNames to actual classes (Should help a bit to avoid expensive reflection calls) - * - * @author 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/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java index 2a800df46b..ffb758fdac 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java @@ -1,6 +1,7 @@ package org.keycloak.models.mongo.impl.types; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; /** * @author Marek Posolda @@ -9,9 +10,10 @@ 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(); + public String convertObject(ConverterContext context) { + Enum objectToConvert = context.getObjectToConvert(); + + return objectToConvert.toString(); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java index 49fb627f7a..739ff9ae57 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java @@ -3,6 +3,7 @@ package org.keycloak.models.mongo.impl.types; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; import java.util.List; @@ -12,9 +13,6 @@ import java.util.List; */ public class ListConverter 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; @@ -24,17 +22,13 @@ public class ListConverter implements Converter } @Override - public BasicDBList convertObject(T appObjectsList) { + public BasicDBList convertObject(ConverterContext context) { + T appObjectsList = context.getObjectToConvert(); + BasicDBList dbObjects = new BasicDBList(); for (Object appObject : appObjectsList) { Object dbObject = typeConverter.convertApplicationObjectToDBObject(appObject, Object.class); - // We need to add OBJECT_TYPE key to object, so we can retrieve correct Java type of object during load of this list - if (dbObject instanceof BasicDBObject) { - BasicDBObject basicDBObject = (BasicDBObject)dbObject; - basicDBObject.put(OBJECT_TYPE, appObject.getClass().getName()); - } - dbObjects.add(dbObject); } return dbObjects; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapConverter.java new file mode 100644 index 0000000000..0ac8051b69 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapConverter.java @@ -0,0 +1,52 @@ +package org.keycloak.models.mongo.impl.types; + +import java.util.Map; +import java.util.Set; + +import com.mongodb.BasicDBObject; +import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; + +/** + * @author Marek Posolda + */ +public class MapConverter implements Converter { + + // Just some dummy way of encoding . character as it's not allowed by mongo in key fields + static final String DOT_PLACEHOLDER = "###"; + + private final Class mapType; + + public MapConverter(Class mapType) { + this.mapType = mapType; + } + + @Override + public BasicDBObject convertObject(ConverterContext context) { + T objectToConvert = context.getObjectToConvert(); + + BasicDBObject dbObject = new BasicDBObject(); + Set entries = objectToConvert.entrySet(); + for (Map.Entry entry : entries) { + String key = (String)entry.getKey(); + String value = (String)entry.getValue(); + + if (key.contains(".")) { + key = key.replaceAll("\\.", DOT_PLACEHOLDER); + } + + dbObject.put(key, value); + } + return dbObject; + } + + @Override + public Class getConverterObjectType() { + return mapType; + } + + @Override + public Class getExpectedReturnType() { + return BasicDBObject.class; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java similarity index 59% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java index 35596a9671..b910908bc4 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/NoSQLObjectConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java @@ -1,35 +1,36 @@ package org.keycloak.models.mongo.impl.types; import com.mongodb.BasicDBObject; -import org.keycloak.models.mongo.api.AttributedNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLObject; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; -import org.keycloak.models.mongo.impl.MongoDBImpl; +import org.keycloak.models.mongo.impl.MongoStoreImpl; import org.keycloak.models.mongo.impl.ObjectInfo; import org.picketlink.common.properties.Property; import java.util.Collection; -import java.util.Map; /** * @author Marek Posolda */ -public class NoSQLObjectConverter implements Converter { +public class MongoEntityConverter implements Converter { - private final MongoDBImpl mongoDBImpl; + private final MongoStoreImpl mongoStoreImpl; private final TypeConverter typeConverter; private final Class expectedNoSQLObjectType; - public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { - this.mongoDBImpl = mongoDBImpl; + public MongoEntityConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { + this.mongoStoreImpl = mongoStoreImpl; this.typeConverter = typeConverter; this.expectedNoSQLObjectType = expectedNoSQLObjectType; } @Override - public BasicDBObject convertObject(T applicationObject) { - ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(applicationObject.getClass()); + public BasicDBObject convertObject(ConverterContext context) { + T applicationObject = context.getObjectToConvert(); + + ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(applicationObject.getClass()); // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped) BasicDBObject dbObject = new BasicDBObject(); @@ -42,15 +43,6 @@ public class NoSQLObjectConverter implements Converter attributes = attributedObject.getAttributes(); - for (Map.Entry attribute : attributes.entrySet()) { - dbObject.append(attribute.getKey(), attribute.getValue()); - } - } - return dbObject; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java index 5ba1de5498..eca7c1a67e 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java @@ -1,8 +1,11 @@ package org.keycloak.models.mongo.impl.types; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; /** + * Just returns input + * * @author Marek Posolda */ public class SimpleConverter implements Converter { @@ -14,7 +17,8 @@ public class SimpleConverter implements Converter { } @Override - public T convertObject(T objectToConvert) { + public T convertObject(ConverterContext context) { + T objectToConvert = context.getObjectToConvert(); return objectToConvert; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java index 0c948eccb4..5bb84a8126 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java @@ -1,6 +1,7 @@ package org.keycloak.models.mongo.impl.types; import org.keycloak.models.mongo.api.types.Converter; +import org.keycloak.models.mongo.api.types.ConverterContext; /** * @author Marek Posolda @@ -8,15 +9,10 @@ import org.keycloak.models.mongo.api.types.Converter; 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); - } + public Enum convertObject(ConverterContext context) { + String enumValue = context.getObjectToConvert(); - String className = objectToConvert.substring(0, index); - String enumValue = objectToConvert.substring(index + 3); - Class clazz = (Class)ClassCache.getInstance().getOrLoadClass(className); + Class clazz = (Class)context.getExpectedReturnType(); return Enum.valueOf(clazz, enumValue); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/MongoModelProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/MongoModelProvider.java index c035b5655d..910adde85a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/MongoModelProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/MongoModelProvider.java @@ -2,6 +2,9 @@ package org.keycloak.models.mongo.keycloak; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.ModelProvider; +import org.keycloak.models.mongo.keycloak.adapters.MongoKeycloakSessionFactory; +import org.keycloak.models.mongo.utils.MongoConfiguration; +import org.keycloak.models.mongo.utils.SystemPropertiesConfigurationProvider; import java.lang.Override; @@ -18,16 +21,7 @@ public class MongoModelProvider implements ModelProvider { @Override public KeycloakSessionFactory createFactory() { - String host = PropertiesManager.getMongoHost(); - int port = PropertiesManager.getMongoPort(); - String dbName = PropertiesManager.getMongoDbName(); - boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup(); - - // Create MongoDBSessionFactory via reflection now - try { - return new MongoDBSessionFactory(host, port, dbName, dropDatabaseOnStartup); - } catch (Exception e) { - throw new RuntimeException(e); - } + MongoConfiguration config = SystemPropertiesConfigurationProvider.createConfiguration(); + return new MongoKeycloakSessionFactory(config); } } 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 c1a4dd2f6c..835d18d165 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java @@ -1,13 +1,15 @@ package org.keycloak.models.mongo.keycloak.adapters; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; import org.keycloak.models.ApplicationModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.keycloak.data.ApplicationData; -import org.keycloak.models.mongo.keycloak.data.RoleData; -import org.keycloak.models.mongo.keycloak.data.UserData; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.utils.MongoModelUtils; import java.util.ArrayList; import java.util.HashSet; @@ -19,29 +21,38 @@ import java.util.Set; */ public class ApplicationAdapter implements ApplicationModel { - private final ApplicationData application; - private final NoSQL noSQL; + private final ApplicationEntity application; + private final MongoStore mongoStore; - private UserData resourceUser; + private UserAdapter resourceUser; - public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) { - this.application = applicationData; - this.noSQL = noSQL; + public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStore mongoStore) { + this(applicationEntity, null, mongoStore); + } + + public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStore mongoStore) { + this.application = applicationEntity; + this.resourceUser = resourceUser; + this.mongoStore = mongoStore; } @Override public void updateApplication() { - noSQL.saveObject(application); + mongoStore.updateObject(application); } @Override - public UserModel getApplicationUser() { + public UserAdapter getApplicationUser() { // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object if (resourceUser == null) { - resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId()); + UserEntity userEntity = mongoStore.loadObject(UserEntity.class, application.getResourceUserId()); + if (userEntity == null) { + throw new IllegalStateException("User " + application.getResourceUserId() + " not found"); + } + resourceUser = new UserAdapter(userEntity, mongoStore); } - return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null; + return resourceUser; } @Override @@ -101,182 +112,90 @@ public class ApplicationAdapter implements ApplicationModel { @Override public RoleAdapter getRole(String name) { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("name", name) - .andCondition("applicationId", getId()) - .build(); - RoleData role = noSQL.loadSingleObject(RoleData.class, query); + DBObject query = new QueryBuilder() + .and("name").is(name) + .and("applicationId").is(getId()) + .get(); + RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query); if (role == null) { return null; } else { - return new RoleAdapter(role, noSQL); + return new RoleAdapter(role, this, mongoStore); } } @Override public RoleModel getRoleById(String id) { - RoleData role = noSQL.loadObject(RoleData.class, id); + RoleEntity role = mongoStore.loadObject(RoleEntity.class, id); if (role == null) { return null; } else { - return new RoleAdapter(role, noSQL); + return new RoleAdapter(role, this, mongoStore); } } - @Override - public void grantRole(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pushItemToList(userData, "roleIds", role.getId()); - } - - @Override - public boolean hasRole(UserModel user, String role) { - RoleModel roleModel = getRole(role); - return hasRole(user, roleModel); - } - - @Override - public boolean hasRole(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - - List roleIds = userData.getRoleIds(); - String roleId = role.getId(); - if (roleIds != null) { - for (String currentId : roleIds) { - if (roleId.equals(currentId)) { - return true; - } - } - } - return false; - } - @Override public RoleAdapter addRole(String name) { - if (getRole(name) != null) { - throw new IllegalArgumentException("Role " + name + " already exists"); + RoleAdapter existing = getRole(name); + if (existing != null) { + return existing; } - RoleData roleData = new RoleData(); - roleData.setName(name); - roleData.setApplicationId(getId()); + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setName(name); + roleEntity.setApplicationId(getId()); - noSQL.saveObject(roleData); - return new RoleAdapter(roleData, noSQL); + mongoStore.insertObject(roleEntity); + return new RoleAdapter(roleEntity, this, mongoStore); } @Override - public List getRoles() { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("applicationId", getId()) - .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + public boolean removeRoleById(String id) { + return mongoStore.removeObject(RoleEntity.class ,id); + } - List result = new ArrayList(); - for (RoleData role : roles) { - result.add(new RoleAdapter(role, noSQL)); + @Override + public Set getRoles() { + DBObject query = new QueryBuilder() + .and("applicationId").is(getId()) + .get(); + List roles = mongoStore.loadObjects(RoleEntity.class, query); + + Set result = new HashSet(); + for (RoleEntity role : roles) { + result.add(new RoleAdapter(role, this, mongoStore)); } return result; } - // Static so that it can be used from RealmAdapter as well - static List getAllRolesOfUser(UserModel user, NoSQL noSQL) { - UserData userData = ((UserAdapter)user).getUser(); - List roleIds = userData.getRoleIds(); - - NoSQLQuery query = noSQL.createQueryBuilder() - .inCondition("_id", roleIds) - .build(); - 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) { + public Set getApplicationRoleMappings(UserModel user) { + Set result = new HashSet(); + List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore); + + for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(role.getName()); + result.add(new RoleAdapter(role, this, mongoStore)); } } return result; } @Override - public List 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) { + public void addScope(RoleModel role) { + UserAdapter appUser = getApplicationUser(); + mongoStore.pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true); + } + + @Override + public Set getApplicationScopeMappings(UserModel user) { + Set result = new HashSet(); + List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore); + + for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(new RoleAdapter(role, noSQL)); - } - } - return result; - } - - @Override - public void deleteRoleMapping(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pullItemFromList(userData, "roleIds", role.getId()); - } - - @Override - public void addScopeMapping(UserModel agent, String roleName) { - RoleAdapter role = getRole(roleName); - if (role == null) { - throw new RuntimeException("Role not found"); - } - - addScopeMapping(agent, role); - } - - @Override - public void addScopeMapping(UserModel agent, RoleModel role) { - UserData userData = ((UserAdapter)agent).getUser(); - noSQL.pushItemToList(userData, "scopeIds", role.getId()); - } - - @Override - public void deleteScopeMapping(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pullItemFromList(userData, "scopeIds", role.getId()); - } - - // Static so that it can be used from RealmAdapter as well - static List getAllScopesOfUser(UserModel user, NoSQL noSQL) { - UserData userData = ((UserAdapter)user).getUser(); - List roleIds = userData.getScopeIds(); - - NoSQLQuery query = noSQL.createQueryBuilder() - .inCondition("_id", roleIds) - .build(); - 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())) { - result.add(role.getName()); - } - } - 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)); + result.add(new RoleAdapter(role, this, mongoStore)); } } return result; @@ -284,16 +203,45 @@ public class ApplicationAdapter implements ApplicationModel { @Override public List getDefaultRoles() { - return null; //To change body of implemented methods use File | Settings | File Templates. + return application.getDefaultRoles(); } @Override public void addDefaultRole(String name) { - //To change body of implemented methods use File | Settings | File Templates. + RoleModel role = getRole(name); + if (role == null) { + addRole(name); + } + + mongoStore.pushItemToList(application, "defaultRoles", name, true); } @Override public void updateDefaultRoles(String[] defaultRoles) { - //To change body of implemented methods use File | Settings | File Templates. + List roleNames = new ArrayList(); + for (String roleName : defaultRoles) { + RoleModel role = getRole(roleName); + if (role == null) { + addRole(roleName); + } + + roleNames.add(roleName); + } + + application.setDefaultRoles(roleNames); + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (o == this) return true; + if (!(o instanceof ApplicationAdapter)) return false; + ApplicationAdapter app = (ApplicationAdapter)o; + return app.getId().equals(getId()); + } + + @Override + public int hashCode() { + return getId().hashCode(); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java deleted file mode 100755 index f964605cb8..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoDBSessionFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.keycloak.models.mongo.keycloak.adapters; - -import com.mongodb.DB; -import com.mongodb.MongoClient; -import org.jboss.logging.Logger; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.impl.MongoDBImpl; -import org.keycloak.models.mongo.keycloak.data.ApplicationData; -import org.keycloak.models.mongo.keycloak.data.OAuthClientData; -import org.keycloak.models.mongo.keycloak.data.RealmData; -import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData; -import org.keycloak.models.mongo.keycloak.data.RoleData; -import org.keycloak.models.mongo.keycloak.data.SocialLinkData; -import org.keycloak.models.mongo.keycloak.data.UserData; -import org.keycloak.models.mongo.keycloak.data.credentials.OTPData; -import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData; - -import java.net.UnknownHostException; - -/** - * NoSQL implementation based on MongoDB - * - * @author Marek Posolda - */ -public class MongoDBSessionFactory implements KeycloakSessionFactory { - protected static final Logger logger = Logger.getLogger(MongoDBSessionFactory.class); - - private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { - RealmData.class, - UserData.class, - RoleData.class, - RequiredCredentialData.class, - PasswordData.class, - OTPData.class, - SocialLinkData.class, - ApplicationData.class, - OAuthClientData.class - }; - - private final MongoClient mongoClient; - private final NoSQL mongoDB; - - public MongoDBSessionFactory(String host, int port, String dbName, boolean dropDatabaseOnStartup) { - logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, dropDatabaseOnStartup)); - try { - // TODO: authentication support - mongoClient = new MongoClient(host, port); - - DB db = mongoClient.getDB(dbName); - mongoDB = new MongoDBImpl(db, dropDatabaseOnStartup, MANAGED_DATA_TYPES); - - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } - - @Override - public KeycloakSession createSession() { - return new NoSQLSession(mongoDB); - } - - @Override - public void close() { - logger.info("Closing MongoDB client"); - mongoClient.close(); - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java new file mode 100755 index 0000000000..6fd1cfc3fa --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java @@ -0,0 +1,92 @@ +package org.keycloak.models.mongo.keycloak.adapters; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakTransaction; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.RealmEntity; +import org.keycloak.models.utils.KeycloakModelUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Marek Posolda + */ +public class MongoKeycloakSession implements KeycloakSession { + + private static final MongoKeycloakTransaction PLACEHOLDER = new MongoKeycloakTransaction(); + private final MongoStore mongoStore; + + public MongoKeycloakSession(MongoStore mongoStore) { + this.mongoStore = mongoStore; + } + + @Override + public KeycloakTransaction getTransaction() { + return PLACEHOLDER; + } + + @Override + public void close() { + } + + @Override + public RealmModel createRealm(String name) { + return createRealm(KeycloakModelUtils.generateId(), name); + } + + @Override + public RealmModel createRealm(String id, String name) { + if (getRealm(id) != null) { + throw new IllegalStateException("Realm with id '" + id + "' already exists"); + } + + RealmEntity newRealm = new RealmEntity(); + newRealm.setId(id); + newRealm.setName(name); + + mongoStore.insertObject(newRealm); + + RealmAdapter realm = new RealmAdapter(newRealm, mongoStore); + return realm; + } + + @Override + public RealmModel getRealm(String id) { + RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, id); + return realmEntity != null ? new RealmAdapter(realmEntity, mongoStore) : null; + } + + @Override + public List getRealms(UserModel admin) { + DBObject query = new BasicDBObject(); + List realms = mongoStore.loadObjects(RealmEntity.class, query); + + List results = new ArrayList(); + for (RealmEntity realmEntity : realms) { + results.add(new RealmAdapter(realmEntity, mongoStore)); + } + return results; + } + + @Override + public RealmModel getRealmByName(String name) { + DBObject query = new QueryBuilder() + .and("name").is(name) + .get(); + RealmEntity realm = mongoStore.loadSingleObject(RealmEntity.class, query); + + if (realm == null) return null; + return new RealmAdapter(realm, mongoStore); + } + + @Override + public boolean removeRealm(String id) { + return mongoStore.removeObject(RealmEntity.class, id); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java new file mode 100755 index 0000000000..6c2af86742 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java @@ -0,0 +1,83 @@ +package org.keycloak.models.mongo.keycloak.adapters; + +import com.mongodb.DB; +import com.mongodb.MongoClient; +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.impl.MongoStoreImpl; +import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; +import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.RealmEntity; +import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity; +import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.utils.EmbeddedMongo; +import org.keycloak.models.mongo.utils.MongoConfiguration; + +import java.net.UnknownHostException; + +/** + * KeycloakSessionFactory implementation based on MongoDB + * + * @author Marek Posolda + */ +public class MongoKeycloakSessionFactory implements KeycloakSessionFactory { + protected static final Logger logger = Logger.getLogger(MongoKeycloakSessionFactory.class); + + private static final Class[] MANAGED_ENTITY_TYPES = (Class[])new Class[] { + RealmEntity.class, + UserEntity.class, + RoleEntity.class, + RequiredCredentialEntity.class, + CredentialEntity.class, + SocialLinkEntity.class, + ApplicationEntity.class, + OAuthClientEntity.class + }; + + private final EmbeddedMongo embeddedMongo; + private final MongoClient mongoClient; + private final MongoStore mongoStore; + + public MongoKeycloakSessionFactory(MongoConfiguration config) { + logger.info(String.format("Configuring MongoStore with: " + config)); + + if (config.isStartEmbedded()) { + embeddedMongo = new EmbeddedMongo(); + embeddedMongo.startEmbeddedMongo(config.getPort()); + } else { + embeddedMongo = null; + } + + try { + // TODO: authentication support + mongoClient = new MongoClient(config.getHost(), config.getPort()); + + DB db = mongoClient.getDB(config.getDbName()); + mongoStore = new MongoStoreImpl(db, config.isClearCollectionsOnStartup(), MANAGED_ENTITY_TYPES); + + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + @Override + public KeycloakSession createSession() { + return new MongoKeycloakSession(mongoStore); + } + + @Override + public void close() { + logger.info("Closing MongoDB client"); + mongoClient.close(); + + if (embeddedMongo != null) { + embeddedMongo.stopEmbeddedMongo(); + } + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java similarity index 93% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java index 3d166357ae..db2de6f982 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLTransaction.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java @@ -5,7 +5,7 @@ import org.keycloak.models.KeycloakTransaction; /** * @author Marek Posolda */ -public class NoSQLTransaction implements KeycloakTransaction { +public class MongoKeycloakTransaction implements KeycloakTransaction { @Override public void begin() { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java deleted file mode 100755 index bbd5ea4e61..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/NoSQLSession.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.keycloak.models.mongo.keycloak.adapters; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.keycloak.data.RealmData; -import org.keycloak.models.utils.KeycloakSessionUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author 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(KeycloakSessionUtils.generateId(), name); - } - - @Override - public RealmModel createRealm(String id, String name) { - if (getRealm(id) != null) { - throw new IllegalStateException("Realm with id '" + id + "' already exists"); - } - - RealmData newRealm = new RealmData(); - newRealm.setId(id); - newRealm.setName(name); - - noSQL.saveObject(newRealm); - - RealmAdapter realm = new RealmAdapter(newRealm, noSQL); - return realm; - } - - @Override - public RealmModel getRealm(String id) { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("id", id) - .build(); - RealmData realmData = noSQL.loadSingleObject(RealmData.class, query); - return realmData != null ? new RealmAdapter(realmData, noSQL) : null; - } - - @Override - public List getRealms(UserModel admin) { - String userId = ((UserAdapter)admin).getUser().getId(); - NoSQLQuery query = noSQL.createQueryBuilder() - .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/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 d522db9b9c..e4b866d5d7 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java @@ -2,28 +2,27 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.keycloak.data.OAuthClientData; -import org.keycloak.models.mongo.keycloak.data.UserData; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; /** * @author Marek Posolda */ public class OAuthClientAdapter implements OAuthClientModel { - private final OAuthClientData delegate; + private final OAuthClientEntity delegate; private UserAdapter oauthAgent; - private final NoSQL noSQL; + private final MongoStore mongoStore; - public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) { - this.delegate = oauthClientData; + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStore mongoStore) { + this.delegate = oauthClientEntity; this.oauthAgent = oauthAgent; - this.noSQL = noSQL; + this.mongoStore = mongoStore; } - public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) { - this.delegate = oauthClientData; - this.noSQL = noSQL; + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStore mongoStore) { + this(oauthClientEntity, null, mongoStore); } @Override @@ -35,8 +34,8 @@ public class OAuthClientAdapter implements OAuthClientModel { public UserModel getOAuthAgent() { // This is not thread-safe. Assumption is that OAuthClientAdapter instance is per-client object if (oauthAgent == null) { - UserData user = noSQL.loadObject(UserData.class, delegate.getOauthAgentId()); - oauthAgent = user!=null ? new UserAdapter(user, noSQL) : null; + UserEntity user = mongoStore.loadObject(UserEntity.class, delegate.getOauthAgentId()); + oauthAgent = user!=null ? new UserAdapter(user, mongoStore) : null; } return oauthAgent; } 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 7d4aa72690..48ab5774a3 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1,63 +1,60 @@ package org.keycloak.models.mongo.keycloak.adapters; -import org.bouncycastle.openssl.PEMWriter; -import org.keycloak.PemUtils; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.jboss.logging.Logger; import org.keycloak.models.ApplicationModel; import org.keycloak.models.OAuthClientModel; +import org.keycloak.models.PasswordPolicy; 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.NoSQL; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder; -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.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.representations.idm.CredentialRepresentation; -import org.picketlink.idm.credential.Credentials; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; +import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; +import org.keycloak.models.mongo.keycloak.entities.RealmEntity; +import org.keycloak.models.mongo.keycloak.entities.RequiredCredentialEntity; +import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.SocialLinkEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import org.keycloak.models.mongo.utils.MongoModelUtils; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.Pbkdf2PasswordEncoder; +import org.keycloak.models.utils.TimeBasedOTP; -import java.io.IOException; -import java.io.StringWriter; import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; /** * @author Marek Posolda */ public class RealmAdapter implements RealmModel { - private final RealmData realm; - private final NoSQL noSQL; + private static final Logger logger = Logger.getLogger(RealmAdapter.class); + + private final RealmEntity realm; + private final MongoStore mongoStore; protected volatile transient PublicKey publicKey; protected volatile transient PrivateKey privateKey; - // 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()); + private volatile transient PasswordPolicy passwordPolicy; - public RealmAdapter(RealmData realmData, NoSQL noSQL) { - this.realm = realmData; - this.noSQL = noSQL; - } - - protected String getOid() { - return realm.getOid(); + public RealmAdapter(RealmEntity realmEntity, MongoStore mongoStore) { + this.realm = realmEntity; + this.mongoStore = mongoStore; } @Override @@ -87,28 +84,6 @@ public class RealmAdapter implements RealmModel { 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(); @@ -120,17 +95,6 @@ public class RealmAdapter implements RealmModel { 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(); @@ -164,6 +128,43 @@ public class RealmAdapter implements RealmModel { updateRealm(); } + @Override + public boolean isSocial() { + return realm.isSocial(); + } + + @Override + public void setSocial(boolean social) { + realm.setSocial(social); + updateRealm(); + } + + @Override + public boolean isUpdateProfileOnInitialSocialLogin() { + return realm.isUpdateProfileOnInitialSocialLogin(); + } + + @Override + public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) { + realm.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin); + updateRealm(); + } + + @Override + public PasswordPolicy getPasswordPolicy() { + if (passwordPolicy == null) { + passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy()); + } + return passwordPolicy; + } + + @Override + public void setPasswordPolicy(PasswordPolicy policy) { + this.passwordPolicy = policy; + realm.setPasswordPolicy(policy.toString()); + updateRealm(); + } + @Override public int getTokenLifespan() { return realm.getTokenLifespan(); @@ -224,199 +225,219 @@ public class RealmAdapter implements RealmModel { @Override public PublicKey getPublicKey() { if (publicKey != null) return publicKey; - String pem = getPublicKeyPem(); - if (pem != null) { - try { - publicKey = PemUtils.decodePublicKey(pem); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem()); return publicKey; } @Override public void setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; - StringWriter writer = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(writer); - try { - pemWriter.writeObject(publicKey); - pemWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - String s = writer.toString(); - setPublicKeyPem(PemUtils.removeBeginEnd(s)); + String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey); + setPublicKeyPem(publicKeyPem); } @Override public PrivateKey getPrivateKey() { if (privateKey != null) return privateKey; - String pem = getPrivateKeyPem(); - if (pem != null) { - try { - privateKey = PemUtils.decodePrivateKey(pem); - } catch (Exception e) { - throw new RuntimeException(e); - } - } + privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem()); return privateKey; } @Override public void setPrivateKey(PrivateKey privateKey) { this.privateKey = privateKey; - StringWriter writer = new StringWriter(); - PEMWriter pemWriter = new PEMWriter(writer); - try { - pemWriter.writeObject(privateKey); - pemWriter.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - String s = writer.toString(); - setPrivateKeyPem(PemUtils.removeBeginEnd(s)); + String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey); + setPrivateKeyPem(privateKeyPem); } @Override public UserAdapter getUser(String name) { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("loginName", name) - .andCondition("realmId", getOid()) - .build(); - UserData user = noSQL.loadSingleObject(UserData.class, query); + DBObject query = new QueryBuilder() + .and("loginName").is(name) + .and("realmId").is(getId()) + .get(); + UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query); if (user == null) { return null; } else { - return new UserAdapter(user, noSQL); + return new UserAdapter(user, mongoStore); + } + } + + @Override + public UserModel getUserByEmail(String email) { + DBObject query = new QueryBuilder() + .and("email").is(email) + .and("realmId").is(getId()) + .get(); + UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query); + + if (user == null) { + return null; + } else { + return new UserAdapter(user, mongoStore); } } @Override public UserAdapter addUser(String username) { + UserAdapter userModel = addUserEntity(username); + + for (String r : getDefaultRoles()) { + grantRole(userModel, getRole(r)); + } + + for (ApplicationModel application : getApplications()) { + for (String r : application.getDefaultRoles()) { + grantRole(userModel, application.getRole(r)); + } + } + + return userModel; + } + + // Add just user entity without defaultRoles + protected UserAdapter addUserEntity(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()); + UserEntity userEntity = new UserEntity(); + userEntity.setLoginName(username); + userEntity.setEnabled(true); + userEntity.setRealmId(getId()); - noSQL.saveObject(userData); - return new UserAdapter(userData, noSQL); + mongoStore.insertObject(userEntity); + return new UserAdapter(userEntity, mongoStore); } - // 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 boolean removeUser(String name) { + DBObject query = new QueryBuilder() + .and("loginName").is(name) + .and("realmId").is(getId()) + .get(); + return mongoStore.removeObjects(UserEntity.class, query); } @Override public RoleAdapter getRole(String name) { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("name", name) - .andCondition("realmId", getOid()) - .build(); - RoleData role = noSQL.loadSingleObject(RoleData.class, query); + DBObject query = new QueryBuilder() + .and("name").is(name) + .and("realmId").is(getId()) + .get(); + RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query); if (role == null) { return null; } else { - return new RoleAdapter(role, noSQL); + return new RoleAdapter(role, this, mongoStore); } } @Override public RoleModel addRole(String name) { - if (getRole(name) != null) { - throw new IllegalArgumentException("Role " + name + " already exists"); + RoleAdapter role = getRole(name); + if (role != null) { + // Compatibility with JPA model + return role; + // throw new IllegalArgumentException("Role " + name + " already exists"); } - RoleData roleData = new RoleData(); - roleData.setName(name); - roleData.setRealmId(getOid()); + RoleEntity roleEntity = new RoleEntity(); + roleEntity.setName(name); + roleEntity.setRealmId(getId()); - noSQL.saveObject(roleData); - return new RoleAdapter(roleData, noSQL); + mongoStore.insertObject(roleEntity); + return new RoleAdapter(roleEntity, this, mongoStore); } @Override - public List getRoles() { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("realmId", getOid()) - .build(); - List roles = noSQL.loadObjects(RoleData.class, query); + public boolean removeRoleById(String id) { + return mongoStore.removeObject(RoleEntity.class ,id); + } - List result = new ArrayList(); - for (RoleData role : roles) { - result.add(new RoleAdapter(role, noSQL)); + @Override + public Set getRoles() { + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + List roles = mongoStore.loadObjects(RoleEntity.class, query); + + Set result = new HashSet(); + + if (roles == null) return result; + for (RoleEntity role : roles) { + result.add(new RoleAdapter(role, this, mongoStore)); } return result; } @Override - public List getDefaultRoles() { - List defaultRoles = realm.getDefaultRoles(); - - NoSQLQuery query = noSQL.createQueryBuilder() - .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)); + public RoleModel getRoleById(String id) { + RoleEntity role = mongoStore.loadObject(RoleEntity.class, id); + if (role == null) { + return null; + } else { + return new RoleAdapter(role, this, mongoStore); } - return defaultRoleModels; + } + + @Override + public List getDefaultRoles() { + return realm.getDefaultRoles(); } @Override public void addDefaultRole(String name) { RoleModel role = getRole(name); if (role == null) { - role = addRole(name); + addRole(name); } - noSQL.pushItemToList(realm, "defaultRoles", role.getId()); + mongoStore.pushItemToList(realm, "defaultRoles", name, true); } @Override public void updateDefaultRoles(String[] defaultRoles) { - // defaultRoles is array with names of roles. So we need to convert to array of ids - List roleIds = new ArrayList(); + List roleNames = new ArrayList(); for (String roleName : defaultRoles) { RoleModel role = getRole(roleName); if (role == null) { - role = addRole(roleName); + addRole(roleName); } - roleIds.add(role.getId()); + roleNames.add(roleName); } - realm.setDefaultRoles(roleIds); + realm.setDefaultRoles(roleNames); updateRealm(); } @Override public ApplicationModel getApplicationById(String id) { - ApplicationData appData = noSQL.loadObject(ApplicationData.class, id); + ApplicationEntity appData = mongoStore.loadObject(ApplicationEntity.class, id); // Check if application belongs to this realm - if (appData == null || !getOid().equals(appData.getRealmId())) { + if (appData == null || !getId().equals(appData.getRealmId())) { return null; } - ApplicationModel model = new ApplicationAdapter(appData, noSQL); + ApplicationModel model = new ApplicationAdapter(appData, mongoStore); return model; } + @Override + public ApplicationModel getApplicationByName(String name) { + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .and("name").is(name) + .get(); + ApplicationEntity appEntity = mongoStore.loadSingleObject(ApplicationEntity.class, query); + return appEntity==null ? null : new ApplicationAdapter(appEntity, mongoStore); + } + @Override public Map getApplicationNameMap() { Map resourceMap = new HashMap(); @@ -428,356 +449,364 @@ public class RealmAdapter implements RealmModel { @Override public List getApplications() { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("realmId", getOid()) - .build(); - List appDatas = noSQL.loadObjects(ApplicationData.class, query); + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + List appDatas = mongoStore.loadObjects(ApplicationEntity.class, query); List result = new ArrayList(); - for (ApplicationData appData : appDatas) { - result.add(new ApplicationAdapter(appData, noSQL)); + for (ApplicationEntity appData : appDatas) { + result.add(new ApplicationAdapter(appData, mongoStore)); } return result; } @Override public ApplicationModel addApplication(String name) { - UserAdapter resourceUser = addUser(name); + UserAdapter resourceUser = addUserEntity(name); - ApplicationData appData = new ApplicationData(); + ApplicationEntity appData = new ApplicationEntity(); appData.setName(name); - appData.setRealmId(getOid()); + appData.setRealmId(getId()); + appData.setEnabled(true); appData.setResourceUserId(resourceUser.getUser().getId()); - noSQL.saveObject(appData); + mongoStore.insertObject(appData); - ApplicationModel resource = new ApplicationAdapter(appData, noSQL); + ApplicationModel resource = new ApplicationAdapter(appData, resourceUser, mongoStore); return resource; } @Override - public boolean hasRole(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); + public boolean removeApplication(String id) { + return mongoStore.removeObject(ApplicationEntity.class, id); + } - List roleIds = userData.getRoleIds(); - String roleId = role.getId(); - if (roleIds != null) { - for (String currentId : roleIds) { - if (roleId.equals(currentId)) { - return true; - } - } + @Override + public boolean hasRole(UserModel user, RoleModel role) { + Set roles = getRoleMappings(user); + if (roles.contains(role)) return true; + + for (RoleModel mapping : roles) { + if (mapping.hasRole(role)) return true; } return false; } @Override public void grantRole(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pushItemToList(userData, "roleIds", role.getId()); + UserEntity userEntity = ((UserAdapter)user).getUser(); + mongoStore.pushItemToList(userEntity, "roleIds", role.getId(), true); } @Override - 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)); + public Set getRoleMappings(UserModel user) { + Set result = new HashSet(); + List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore); + + for (RoleEntity role : roles) { + if (getId().equals(role.getRealmId())) { + result.add(new RoleAdapter(role, this, mongoStore)); + } else { + // Likely applicationRole, but we don't have this application yet + result.add(new RoleAdapter(role, mongoStore)); } } return result; } @Override - public Set getRoleMappingValues(UserModel user) { - Set result = new HashSet(); - 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(role.getName()); + public Set getRealmRoleMappings(UserModel user) { + Set allRoles = getRoleMappings(user); + + // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? + Set realmRoles = new HashSet(); + for (RoleModel role : allRoles) { + RoleEntity roleEntity = ((RoleAdapter)role).getRole(); + + if (getId().equals(roleEntity.getRealmId())) { + realmRoles.add(role); } } - return result; + return realmRoles; } @Override public void deleteRoleMapping(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pullItemFromList(userData, "roleIds", role.getId()); + UserEntity userEntity = ((UserAdapter)user).getUser(); + mongoStore.pullItemFromList(userEntity, "roleIds", role.getId()); } @Override - public void addScopeMapping(UserModel agent, String roleName) { - RoleAdapter role = getRole(roleName); - if (role == null) { - throw new RuntimeException("Role not found"); - } + public Set getScopeMappings(UserModel user) { + Set result = new HashSet(); + List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore); - addScopeMapping(agent, role); + for (RoleEntity role : roles) { + if (getId().equals(role.getRealmId())) { + result.add(new RoleAdapter(role, this, mongoStore)); + } else { + // Likely applicationRole, but we don't have this application yet + result.add(new RoleAdapter(role, mongoStore)); + } + } + return result; + } + + @Override + public Set getRealmScopeMappings(UserModel user) { + Set allScopes = getScopeMappings(user); + + // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? + Set realmRoles = new HashSet(); + for (RoleModel role : allScopes) { + RoleEntity roleEntity = ((RoleAdapter)role).getRole(); + + if (getId().equals(roleEntity.getRealmId())) { + realmRoles.add(role); + } + } + return realmRoles; } @Override public void addScopeMapping(UserModel agent, RoleModel role) { - UserData userData = ((UserAdapter)agent).getUser(); - noSQL.pushItemToList(userData, "scopeIds", role.getId()); + UserEntity userEntity = ((UserAdapter)agent).getUser(); + mongoStore.pushItemToList(userEntity, "scopeIds", role.getId(), true); } @Override public void deleteScopeMapping(UserModel user, RoleModel role) { - UserData userData = ((UserAdapter)user).getUser(); - noSQL.pullItemFromList(userData, "scopeIds", role.getId()); + UserEntity userEntity = ((UserAdapter)user).getUser(); + mongoStore.pullItemFromList(userEntity, "scopeIds", role.getId()); } @Override public OAuthClientModel addOAuthClient(String name) { - UserAdapter oauthAgent = addUser(name); + UserAdapter oauthAgent = addUserEntity(name); - OAuthClientData oauthClient = new OAuthClientData(); + OAuthClientEntity oauthClient = new OAuthClientEntity(); oauthClient.setOauthAgentId(oauthAgent.getUser().getId()); - oauthClient.setRealmId(getOid()); - noSQL.saveObject(oauthClient); + oauthClient.setRealmId(getId()); + oauthClient.setName(name); + mongoStore.insertObject(oauthClient); - return new OAuthClientAdapter(oauthClient, oauthAgent, noSQL); + return new OAuthClientAdapter(oauthClient, oauthAgent, mongoStore); + } + + @Override + public boolean removeOAuthClient(String id) { + return mongoStore.removeObject(OAuthClientEntity.class, id); } @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); + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .and("oauthAgentId").is(user.getUser().getId()) + .get(); + OAuthClientEntity oauthClient = mongoStore.loadSingleObject(OAuthClientEntity.class, query); + return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, mongoStore); + } + + @Override + public OAuthClientModel getOAuthClientById(String id) { + OAuthClientEntity clientEntity = mongoStore.loadObject(OAuthClientEntity.class, id); + if (clientEntity == null) return null; + return new OAuthClientAdapter(clientEntity, mongoStore); } @Override public List getOAuthClients() { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("realmId", getOid()) - .build(); - List results = noSQL.loadObjects(OAuthClientData.class, query); + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + List results = mongoStore.loadObjects(OAuthClientEntity.class, query); List list = new ArrayList(); - for (OAuthClientData data : results) { - list.add(new OAuthClientAdapter(data, noSQL)); + for (OAuthClientEntity data : results) { + list.add(new OAuthClientAdapter(data, mongoStore)); } 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())) { - result.add(role.getName()); - } - } - return result; - } - - @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); + public void addRequiredCredential(String type) { + RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); + addRequiredCredential(credentialModel, realm.getRequiredCredentials()); } @Override public void addRequiredResourceCredential(String type) { RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); - addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_RESOURCE); + addRequiredCredential(credentialModel, realm.getRequiredApplicationCredentials()); } @Override public void addRequiredOAuthClientCredential(String type) { RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); - addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); + addRequiredCredential(credentialModel, realm.getRequiredOAuthClientCredentials()); } - 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()); + protected void addRequiredCredential(RequiredCredentialModel credentialModel, List persistentCollection) { + RequiredCredentialEntity credEntity = new RequiredCredentialEntity(); + credEntity.setType(credentialModel.getType()); + credEntity.setFormLabel(credentialModel.getFormLabel()); + credEntity.setInput(credentialModel.isInput()); + credEntity.setSecret(credentialModel.isSecret()); - credData.setRealmId(getOid()); - credData.setClientType(clientType); + persistentCollection.add(credEntity); - noSQL.saveObject(credData); + updateRealm(); } @Override public void updateRequiredCredentials(Set creds) { - List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_USER); - updateRequiredCredentials(creds, credsData); + updateRequiredCredentials(creds, realm.getRequiredCredentials()); } @Override public void updateRequiredApplicationCredentials(Set creds) { - List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_RESOURCE); - updateRequiredCredentials(creds, credsData); + updateRequiredCredentials(creds, realm.getRequiredApplicationCredentials()); } @Override public void updateRequiredOAuthClientCredentials(Set creds) { - List credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); - updateRequiredCredentials(creds, credsData); + updateRequiredCredentials(creds, realm.getRequiredOAuthClientCredentials()); } - protected void updateRequiredCredentials(Set creds, List credsData) { + protected void updateRequiredCredentials(Set creds, List credsEntities) { Set already = new HashSet(); - for (RequiredCredentialData data : credsData) { - if (!creds.contains(data.getType())) { - noSQL.removeObject(data); + Set toRemove = new HashSet(); + for (RequiredCredentialEntity entity : credsEntities) { + if (!creds.contains(entity.getType())) { + toRemove.add(entity); } else { - already.add(data.getType()); + already.add(entity.getType()); } } + for (RequiredCredentialEntity entity : toRemove) { + creds.remove(entity); + } for (String cred : creds) { - // TODO - System.out.println("updating cred: " + cred); - // logger.info("updating cred: " + cred); + logger.info("updating cred: " + cred); if (!already.contains(cred)) { - addRequiredCredential(cred); + RequiredCredentialModel credentialModel = initRequiredCredentialModel(cred); + addRequiredCredential(credentialModel, credsEntities); } } } @Override public List getRequiredCredentials() { - return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_USER); + return convertRequiredCredentialEntities(realm.getRequiredCredentials()); } @Override public List getRequiredApplicationCredentials() { - return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_RESOURCE); + return convertRequiredCredentialEntities(realm.getRequiredApplicationCredentials()); } @Override public List getRequiredOAuthClientCredentials() { - return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE); + return convertRequiredCredentialEntities(realm.getRequiredOAuthClientCredentials()); } - protected List getRequiredCredentials(int credentialType) { - List credsData = getRequiredCredentialsData(credentialType); + protected List convertRequiredCredentialEntities(Collection credEntities) { List result = new ArrayList(); - for (RequiredCredentialData data : credsData) { + for (RequiredCredentialEntity entity : credEntities) { RequiredCredentialModel model = new RequiredCredentialModel(); - model.setFormLabel(data.getFormLabel()); - model.setInput(data.isInput()); - model.setSecret(data.isSecret()); - model.setType(data.getType()); + model.setFormLabel(entity.getFormLabel()); + model.setInput(entity.isInput()); + model.setSecret(entity.isSecret()); + model.setType(entity.getType()); result.add(model); } return result; } - protected List getRequiredCredentialsData(int credentialType) { - NoSQLQuery query = noSQL.createQueryBuilder() - .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; + for (CredentialEntity cred : ((UserAdapter)user).getUser().getCredentials()) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + return new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue()); + } + } + return false; } @Override public boolean validateTOTP(UserModel user, String password, String token) { - Credentials.Status status = totpCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password, token, null); - return status == Credentials.Status.VALID; + if (!validatePassword(user, password)) return false; + for (CredentialEntity cred : ((UserAdapter)user).getUser().getCredentials()) { + if (cred.getType().equals(UserCredentialModel.TOTP)) { + return new TimeBasedOTP().validate(token, cred.getValue().getBytes()); + } + } + return false; } @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)) { - totpCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), cred.getDevice(), null, null); - } 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); + CredentialEntity credentialEntity = null; + UserEntity userEntity = ((UserAdapter) user).getUser(); + for (CredentialEntity entity : userEntity.getCredentials()) { + if (entity.getType().equals(cred.getType())) { + credentialEntity = entity; + } } + + if (credentialEntity == null) { + credentialEntity = new CredentialEntity(); + credentialEntity.setType(cred.getType()); + credentialEntity.setDevice(cred.getDevice()); + userEntity.getCredentials().add(credentialEntity); + } + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + byte[] salt = Pbkdf2PasswordEncoder.getSalt(); + credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue())); + credentialEntity.setSalt(salt); + } else { + credentialEntity.setValue(cred.getValue()); + } + credentialEntity.setDevice(cred.getDevice()); + + mongoStore.updateObject(userEntity); } @Override 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); + DBObject query = new QueryBuilder() + .and("socialProvider").is(socialLink.getSocialProvider()) + .and("socialUsername").is(socialLink.getSocialUsername()) + .and("realmId").is(getId()) + .get(); + SocialLinkEntity socialLinkEntity = mongoStore.loadSingleObject(SocialLinkEntity.class, query); - if (socialLinkData == null) { + if (socialLinkEntity == 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); + UserEntity userEntity = mongoStore.loadObject(UserEntity.class, socialLinkEntity.getUserId()); + // TODO: Programmatically remove binding if userEntity doesn't exists? (There are more similar places where this should be handled) + return userEntity==null ? null : new UserAdapter(userEntity, mongoStore); } } @Override public Set getSocialLinks(UserModel user) { - UserData userData = ((UserAdapter)user).getUser(); - String userId = userData.getId(); + UserEntity userEntity = ((UserAdapter)user).getUser(); + String userId = userEntity.getId(); - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("userId", userId) - .build(); - List dbSocialLinks = noSQL.loadObjects(SocialLinkData.class, query); + DBObject query = new QueryBuilder() + .and("userId").is(userId) + .get(); + List dbSocialLinks = mongoStore.loadObjects(SocialLinkEntity.class, query); Set result = new HashSet(); - for (SocialLinkData socialLinkData : dbSocialLinks) { - SocialLinkModel model = new SocialLinkModel(socialLinkData.getSocialProvider(), socialLinkData.getSocialUsername()); + for (SocialLinkEntity socialLinkEntity : dbSocialLinks) { + SocialLinkModel model = new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUsername()); result.add(model); } return result; @@ -785,30 +814,30 @@ public class RealmAdapter implements RealmModel { @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()); - socialLinkData.setRealmId(getOid()); + UserEntity userEntity = ((UserAdapter)user).getUser(); + SocialLinkEntity socialLinkEntity = new SocialLinkEntity(); + socialLinkEntity.setSocialProvider(socialLink.getSocialProvider()); + socialLinkEntity.setSocialUsername(socialLink.getSocialUsername()); + socialLinkEntity.setUserId(userEntity.getId()); + socialLinkEntity.setRealmId(getId()); - noSQL.saveObject(socialLinkData); + mongoStore.insertObject(socialLinkEntity); } @Override public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { - UserData userData = ((UserAdapter)user).getUser(); - String userId = userData.getId(); - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("socialProvider", socialLink.getSocialProvider()) - .andCondition("socialUsername", socialLink.getSocialUsername()) - .andCondition("userId", userId) - .build(); - noSQL.removeObjects(SocialLinkData.class, query); + UserEntity userEntity = ((UserAdapter)user).getUser(); + String userId = userEntity.getId(); + DBObject query = new QueryBuilder() + .and("socialProvider").is(socialLink.getSocialProvider()) + .and("socialUsername").is(socialLink.getSocialUsername()) + .and("userId").is(userId) + .get(); + mongoStore.removeObjects(SocialLinkEntity.class, query); } protected void updateRealm() { - noSQL.saveObject(realm); + mongoStore.updateObject(realm); } protected RequiredCredentialModel initRequiredCredentialModel(String type) { @@ -819,47 +848,117 @@ public class RealmAdapter implements RealmModel { return model; } + @Override + public List getUsers() { + DBObject query = new QueryBuilder() + .and("realmId").is(getId()) + .get(); + List users = mongoStore.loadObjects(UserEntity.class, query); + return convertUserEntities(users); + } + + @Override + public List searchForUser(String search) { + search = search.trim(); + Pattern caseInsensitivePattern = Pattern.compile("(?i:" + search + ")"); + + QueryBuilder nameBuilder; + int spaceInd = search.lastIndexOf(" "); + + // Case when we have search string like "ohn Bow". Then firstName must end with "ohn" AND lastName must start with "bow" (everything case-insensitive) + if (spaceInd != -1) { + String firstName = search.substring(0, spaceInd); + String lastName = search.substring(spaceInd + 1); + Pattern firstNamePattern = Pattern.compile("(?i:" + firstName + "$)"); + Pattern lastNamePattern = Pattern.compile("(?i:^" + lastName + ")"); + nameBuilder = new QueryBuilder().and( + new QueryBuilder().put("firstName").regex(firstNamePattern).get(), + new QueryBuilder().put("lastName").regex(lastNamePattern).get() + ); + } else { + // Case when we have search without spaces like "foo". The firstName OR lastName could be "foo" (everything case-insensitive) + nameBuilder = new QueryBuilder().or( + new QueryBuilder().put("firstName").regex(caseInsensitivePattern).get(), + new QueryBuilder().put("lastName").regex(caseInsensitivePattern).get() + ); + } + + QueryBuilder builder = new QueryBuilder().and( + new QueryBuilder().and("realmId").is(getId()).get(), + new QueryBuilder().or( + new QueryBuilder().put("loginName").regex(caseInsensitivePattern).get(), + new QueryBuilder().put("email").regex(caseInsensitivePattern).get(), + nameBuilder.get() + + ).get() + ); + + List users = mongoStore.loadObjects(UserEntity.class, builder.get()); + return convertUserEntities(users); + } + @Override public List searchForUserByAttributes(Map attributes) { - NoSQLQueryBuilder queryBuilder = noSQL.createQueryBuilder(); + QueryBuilder queryBuilder = new QueryBuilder() + .and("realmId").is(getId()); + for (Map.Entry entry : attributes.entrySet()) { if (entry.getKey().equals(UserModel.LOGIN_NAME)) { - queryBuilder.andCondition("loginName", entry.getValue()); + queryBuilder.and("loginName").regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) { - queryBuilder.andCondition(UserModel.FIRST_NAME, entry.getValue()); + queryBuilder.and(UserModel.FIRST_NAME).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) { - queryBuilder.andCondition(UserModel.LAST_NAME, entry.getValue()); + queryBuilder.and(UserModel.LAST_NAME).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) { - queryBuilder.andCondition(UserModel.EMAIL, entry.getValue()); + queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } } - List users = noSQL.loadObjects(UserData.class, queryBuilder.build()); + List users = mongoStore.loadObjects(UserEntity.class, queryBuilder.get()); + return convertUserEntities(users); + } + + protected List convertUserEntities(List userEntities) { List userModels = new ArrayList(); - for (UserData user : users) { - userModels.add(new UserAdapter(user, noSQL)); + for (UserEntity user : userEntities) { + userModels.add(new UserAdapter(user, mongoStore)); } return userModels; } @Override public Map getSmtpConfig() { - throw new RuntimeException("Not implemented"); + return realm.getSmtpConfig(); } @Override public void setSmtpConfig(Map smtpConfig) { - throw new RuntimeException("Not implemented"); + realm.setSmtpConfig(smtpConfig); + updateRealm(); } @Override public Map getSocialConfig() { - throw new RuntimeException("Not implemented"); + return realm.getSocialConfig(); } @Override public void setSocialConfig(Map socialConfig) { - throw new RuntimeException("Not implemented"); + realm.setSocialConfig(socialConfig); + updateRealm(); + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (!(o instanceof RealmAdapter)) return false; + RealmAdapter r = (RealmAdapter)o; + return r.getId().equals(getId()); + } + + @Override + public int hashCode() { + return getId().hashCode(); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java index 7b2692f42c..21bdc9ef6b 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java @@ -1,8 +1,20 @@ package org.keycloak.models.mongo.keycloak.adapters; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.keycloak.data.RoleData; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; +import org.keycloak.models.mongo.keycloak.entities.RealmEntity; +import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.utils.MongoModelUtils; +import org.keycloak.models.utils.KeycloakModelUtils; /** * Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl) @@ -11,12 +23,23 @@ import org.keycloak.models.mongo.keycloak.data.RoleData; */ public class RoleAdapter implements RoleModel { - private final RoleData role; - private final NoSQL noSQL; + private final RoleEntity role; + private RoleContainerModel roleContainer; + private final MongoStore mongoStore; - public RoleAdapter(RoleData roleData, NoSQL noSQL) { - this.role = roleData; - this.noSQL = noSQL; + public RoleAdapter(RoleEntity roleEntity, MongoStore mongoStore) { + this(roleEntity, null, mongoStore); + } + + public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStore mongoStore) { + this.role = roleEntity; + this.roleContainer = roleContainer; + this.mongoStore = mongoStore; + } + + @Override + public String getId() { + return role.getId(); } @Override @@ -24,6 +47,12 @@ public class RoleAdapter implements RoleModel { return role.getName(); } + @Override + public void setName(String name) { + role.setName(name); + updateRole(); + } + @Override public String getDescription() { return role.getDescription(); @@ -32,21 +61,102 @@ public class RoleAdapter implements RoleModel { @Override public void setDescription(String description) { role.setDescription(description); - noSQL.saveObject(role); + updateRole(); } @Override - public String getId() { - return role.getId(); + public boolean isComposite() { + return role.isComposite(); } @Override - public void setName(String name) { - role.setName(name); - noSQL.saveObject(role); + public void setComposite(boolean flag) { + role.setComposite(flag); + updateRole(); } - public RoleData getRole() { + protected void updateRole() { + mongoStore.updateObject(role); + } + + @Override + public void addCompositeRole(RoleModel childRole) { + mongoStore.pushItemToList(role, "compositeRoleIds", childRole.getId(), true); + } + + @Override + public void removeCompositeRole(RoleModel childRole) { + mongoStore.pullItemFromList(role, "compositeRoleIds", childRole.getId()); + } + + @Override + public Set getComposites() { + if (role.getCompositeRoleIds() == null || role.getCompositeRoleIds().isEmpty()) { + return Collections.EMPTY_SET; + } + + DBObject query = new QueryBuilder() + .and("_id").in(MongoModelUtils.convertStringsToObjectIds(role.getCompositeRoleIds())) + .get(); + List childRoles = mongoStore.loadObjects(RoleEntity.class, query); + + Set set = new HashSet(); + for (RoleEntity childRole : childRoles) { + set.add(new RoleAdapter(childRole, roleContainer, mongoStore)); + } + return set; + } + + @Override + public RoleContainerModel getContainer() { + if (roleContainer == null) { + // Compute it + if (role.getRealmId() != null) { + RealmEntity realm = mongoStore.loadObject(RealmEntity.class, role.getRealmId()); + if (realm == null) { + throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists"); + } + roleContainer = new RealmAdapter(realm, mongoStore); + } else if (role.getApplicationId() != null) { + ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, role.getApplicationId()); + if (appEntity == null) { + throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists"); + } + roleContainer = new ApplicationAdapter(appEntity, mongoStore); + } else { + throw new IllegalStateException("Both realmId and applicationId are null for role: " + this); + } + } + return roleContainer; + } + + @Override + public boolean hasRole(RoleModel role) { + if (this.equals(role)) return true; + if (!isComposite()) return false; + + Set visited = new HashSet(); + return KeycloakModelUtils.searchFor(role, this, visited); + } + + public RoleEntity getRole() { return role; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RoleAdapter that = (RoleAdapter) o; + + if (!getId().equals(that.getId())) return false; + + return true; + } + + @Override + public int hashCode() { + return getId().hashCode(); + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 3b208484bc..d3fe02f46a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -1,10 +1,13 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.keycloak.data.UserData; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -17,12 +20,12 @@ import java.util.Set; */ public class UserAdapter implements UserModel { - private final UserData user; - private final NoSQL noSQL; + private final UserEntity user; + private final MongoStore mongoStore; - public UserAdapter(UserData userData, NoSQL noSQL) { - this.user = userData; - this.noSQL = noSQL; + public UserAdapter(UserEntity userEntity, MongoStore mongoStore) { + this.user = userEntity; + this.mongoStore = mongoStore; } @Override @@ -38,7 +41,7 @@ public class UserAdapter implements UserModel { @Override public void setEnabled(boolean enabled) { user.setEnabled(enabled); - noSQL.saveObject(user); + updateUser(); } @Override @@ -49,7 +52,7 @@ public class UserAdapter implements UserModel { @Override public void setFirstName(String firstName) { user.setFirstName(firstName); - noSQL.saveObject(user); + updateUser(); } @Override @@ -60,7 +63,7 @@ public class UserAdapter implements UserModel { @Override public void setLastName(String lastName) { user.setLastName(lastName); - noSQL.saveObject(user); + updateUser(); } @Override @@ -71,7 +74,7 @@ public class UserAdapter implements UserModel { @Override public void setEmail(String email) { user.setEmail(email); - noSQL.saveObject(user); + updateUser(); } @Override @@ -82,61 +85,112 @@ public class UserAdapter implements UserModel { @Override public void setEmailVerified(boolean verified) { user.setEmailVerified(verified); - noSQL.saveObject(user); + updateUser(); } @Override public void setAttribute(String name, String value) { - user.setAttribute(name, value); + if (user.getAttributes() == null) { + user.setAttributes(new HashMap()); + } + + user.getAttributes().put(name, value); + updateUser(); } @Override public void removeAttribute(String name) { - user.removeAttribute(name); - noSQL.saveObject(user); + if (user.getAttributes() == null) return; + + user.getAttributes().remove(name); + updateUser(); } @Override public String getAttribute(String name) { - return user.getAttribute(name); + return user.getAttributes()==null ? null : user.getAttributes().get(name); } @Override public Map getAttributes() { - return user.getAttributes(); + return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes()); } - public UserData getUser() { + public UserEntity getUser() { return user; } @Override - public Set getRequiredActions() { - List actions = user.getRequiredActions(); - - // Compatibility with picketlink impl - if (actions == null) { - return Collections.emptySet(); - } else { - Set s = new HashSet(); - for (RequiredAction a : actions) { - s.add(a); - } - return Collections.unmodifiableSet(s); + public Set getWebOrigins() { + Set result = new HashSet(); + if (user.getWebOrigins() != null) { + result.addAll(user.getWebOrigins()); } + return result; + } + + @Override + public void setWebOrigins(Set webOrigins) { + List result = new ArrayList(); + result.addAll(webOrigins); + user.setWebOrigins(result); + updateUser(); + } + + @Override + public void addWebOrigin(String webOrigin) { + mongoStore.pushItemToList(user, "webOrigins", webOrigin, true); + } + + @Override + public void removeWebOrigin(String webOrigin) { + mongoStore.pullItemFromList(user, "webOrigins", webOrigin); + } + + @Override + public Set getRedirectUris() { + Set result = new HashSet(); + if (user.getRedirectUris() != null) { + result.addAll(user.getRedirectUris()); + } + return result; + } + + @Override + public void setRedirectUris(Set redirectUris) { + List result = new ArrayList(); + result.addAll(redirectUris); + user.setRedirectUris(result); + updateUser(); + } + + @Override + public void addRedirectUri(String redirectUri) { + mongoStore.pushItemToList(user, "redirectUris", redirectUri, true); + } + + @Override + public void removeRedirectUri(String redirectUri) { + mongoStore.pullItemFromList(user, "redirectUris", redirectUri); + } + + @Override + public Set getRequiredActions() { + Set result = new HashSet(); + if (user.getRequiredActions() != null) { + result.addAll(user.getRequiredActions()); + } + return result; } @Override public void addRequiredAction(RequiredAction action) { - // Push action only if it's not already here - if (user.getRequiredActions() == null || !user.getRequiredActions().contains(action)) { - noSQL.pushItemToList(user, "requiredActions", action); - } + mongoStore.pushItemToList(user, "requiredActions", action, true); } @Override public void removeRequiredAction(RequiredAction action) { - noSQL.pullItemFromList(user, "requiredActions", action); + mongoStore.pullItemFromList(user, "requiredActions", action); } @Override @@ -147,46 +201,10 @@ public class UserAdapter implements UserModel { @Override public void setTotp(boolean totp) { user.setTotp(totp); - noSQL.saveObject(user); + updateUser(); } - @Override - public Set getWebOrigins() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setWebOrigins(Set webOrigins) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addWebOrigin(String webOrigin) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void removeWebOrigin(String webOrigin) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public Set getRedirectUris() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setRedirectUris(Set redirectUris) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void addRedirectUri(String redirectUri) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void removeRedirectUri(String redirectUri) { - //To change body of implemented methods use File | Settings | File Templates. + protected void updateUser() { + mongoStore.updateObject(user); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java deleted file mode 100755 index b6a3eb1171..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/PasswordCredentialHandler.java +++ /dev/null @@ -1,154 +0,0 @@ -package org.keycloak.models.mongo.keycloak.credentials; - -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.keycloak.data.UserData; -import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData; -import org.picketlink.idm.credential.Credentials; -import org.picketlink.idm.credential.encoder.PasswordEncoder; -import org.picketlink.idm.credential.encoder.SHAPasswordEncoder; - -import java.util.Date; -import java.util.Map; -import java.util.UUID; - -/** - * Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler} - * - * @author 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 PasswordCredentialHandler(Map options) { - setup(options); - } - - private 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 = noSQL.createQueryBuilder() - .andCondition("userId", user.getId()) - .build(); - PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query); - - // If the stored hash is null we automatically fail validation - if (passwordData != null) { - // TODO: Status.INVALID should have bigger priority than Status.EXPIRED? - if (!isCredentialExpired(passwordData.getExpiryDate())) { - - boolean matches = this.passwordEncoder.verify(saltPassword(passwordToValidate, passwordData.getSalt()), passwordData.getEncodedHash()); - - if (matches) { - status = Credentials.Status.VALID; - } - } else { - status = Credentials.Status.EXPIRED; - } - } - } else { - status = Credentials.Status.ACCOUNT_DISABLED; - } - } - - return status; - } - - public void update(NoSQL noSQL, UserData user, String password, - Date effectiveDate, Date expiryDate) { - - // Delete existing password of user - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("userId", user.getId()) - .build(); - noSQL.removeObjects(PasswordData.class, query); - - PasswordData passwordData = new PasswordData(); - - String passwordSalt = generateSalt(); - - passwordData.setSalt(passwordSalt); - passwordData.setEncodedHash(this.passwordEncoder.encode(saltPassword(password, passwordSalt))); - - if (effectiveDate != null) { - passwordData.setEffectiveDate(effectiveDate); - } - - passwordData.setExpiryDate(expiryDate); - - passwordData.setUserId(user.getId()); - - noSQL.saveObject(passwordData); - } - - /** - *

- * 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/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java deleted file mode 100755 index 45a76f757c..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/credentials/TOTPCredentialHandler.java +++ /dev/null @@ -1,135 +0,0 @@ -package org.keycloak.models.mongo.keycloak.credentials; - -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.keycloak.data.UserData; -import org.keycloak.models.mongo.keycloak.data.credentials.OTPData; -import org.picketlink.idm.credential.Credentials; -import org.picketlink.idm.credential.util.TimeBasedOTP; - -import java.util.Date; -import java.util.Map; - -import static org.picketlink.common.util.StringUtil.isNullOrEmpty; -import static org.picketlink.idm.credential.util.TimeBasedOTP.*; - -/** - * Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler} - * - * @author Marek Posolda - */ -public class TOTPCredentialHandler extends PasswordCredentialHandler { - - public static final String ALGORITHM = "ALGORITHM"; - public static final String INTERVAL_SECONDS = "INTERVAL_SECONDS"; - public static final String NUMBER_DIGITS = "NUMBER_DIGITS"; - public static final String DELAY_WINDOW = "DELAY_WINDOW"; - public static final String DEFAULT_DEVICE = "DEFAULT_DEVICE"; - - private TimeBasedOTP totp; - - public TOTPCredentialHandler(Map 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/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 deleted file mode 100644 index 67f74ee6a3..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/OAuthClientData.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data; - -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.NoSQLObject; - -/** - * @author 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/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java deleted file mode 100755 index d9aa0ae59d..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RealmData.java +++ /dev/null @@ -1,219 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data; - -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.api.query.NoSQLQuery; - -import java.util.List; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "realms") -public class RealmData implements NoSQLObject { - - private String oid; - - private String id; - private String name; - private boolean enabled; - private boolean sslNotRequired; - private boolean cookieLoginAllowed; - private boolean registrationAllowed; - private boolean verifyEmail; - private boolean resetPasswordAllowed; - private boolean social; - private boolean automaticRegistrationAfterSocialLogin; - private int tokenLifespan; - private int accessCodeLifespan; - private int accessCodeLifespanUserAction; - private String publicKeyPem; - private String privateKeyPem; - - private List defaultRoles; - private List realmAdmins; - - @NoSQLId - public String getOid() { - return oid; - } - - public void setOid(String oid) { - this.oid = oid; - } - - @NoSQLField - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @NoSQLField - public String getName() { - return name; - } - - public void setName(String realmName) { - this.name = realmName; - } - - @NoSQLField - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - @NoSQLField - public boolean isSslNotRequired() { - return sslNotRequired; - } - - public void setSslNotRequired(boolean sslNotRequired) { - this.sslNotRequired = sslNotRequired; - } - - @NoSQLField - public boolean isCookieLoginAllowed() { - return cookieLoginAllowed; - } - - public void setCookieLoginAllowed(boolean cookieLoginAllowed) { - this.cookieLoginAllowed = cookieLoginAllowed; - } - - @NoSQLField - public boolean isRegistrationAllowed() { - return registrationAllowed; - } - - public void setRegistrationAllowed(boolean registrationAllowed) { - this.registrationAllowed = registrationAllowed; - } - - @NoSQLField - public boolean isVerifyEmail() { - return verifyEmail; - } - - public void setVerifyEmail(boolean verifyEmail) { - this.verifyEmail = verifyEmail; - } - - @NoSQLField - public boolean isResetPasswordAllowed() { - return resetPasswordAllowed; - } - - public void setResetPasswordAllowed(boolean resetPasswordAllowed) { - this.resetPasswordAllowed = resetPasswordAllowed; - } - - @NoSQLField - public boolean isSocial() { - return social; - } - - public void setSocial(boolean social) { - this.social = social; - } - - @NoSQLField - public boolean isAutomaticRegistrationAfterSocialLogin() { - return automaticRegistrationAfterSocialLogin; - } - - public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) { - this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin; - } - - @NoSQLField - public int getTokenLifespan() { - return tokenLifespan; - } - - public void setTokenLifespan(int tokenLifespan) { - this.tokenLifespan = tokenLifespan; - } - - @NoSQLField - public int getAccessCodeLifespan() { - return accessCodeLifespan; - } - - public void setAccessCodeLifespan(int accessCodeLifespan) { - this.accessCodeLifespan = accessCodeLifespan; - } - - @NoSQLField - public int getAccessCodeLifespanUserAction() { - return accessCodeLifespanUserAction; - } - - public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { - this.accessCodeLifespanUserAction = accessCodeLifespanUserAction; - } - - @NoSQLField - public String getPublicKeyPem() { - return publicKeyPem; - } - - public void setPublicKeyPem(String publicKeyPem) { - this.publicKeyPem = publicKeyPem; - } - - @NoSQLField - public String getPrivateKeyPem() { - return privateKeyPem; - } - - public void setPrivateKeyPem(String privateKeyPem) { - this.privateKeyPem = privateKeyPem; - } - - @NoSQLField - public List getDefaultRoles() { - return defaultRoles; - } - - public void setDefaultRoles(List defaultRoles) { - this.defaultRoles = defaultRoles; - } - - @NoSQLField - public List getRealmAdmins() { - return realmAdmins; - } - - public void setRealmAdmins(List 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/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java deleted file mode 100644 index e46ee9fd07..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RequiredCredentialData.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data; - -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "requiredCredentials") -public class RequiredCredentialData extends AbstractNoSQLObject { - - public static final int CLIENT_TYPE_USER = 1; - public static final int CLIENT_TYPE_RESOURCE = 2; - public static final int CLIENT_TYPE_OAUTH_RESOURCE = 3; - - private String id; - - private String type; - private boolean input; - private boolean secret; - private String formLabel; - - private String realmId; - private int clientType; - - @NoSQLId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @NoSQLField - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - @NoSQLField - public boolean isInput() { - return input; - } - - public void setInput(boolean input) { - this.input = input; - } - - @NoSQLField - public boolean isSecret() { - return secret; - } - - public void setSecret(boolean secret) { - this.secret = secret; - } - - @NoSQLField - public String getFormLabel() { - return formLabel; - } - - public void setFormLabel(String formLabel) { - this.formLabel = formLabel; - } - - @NoSQLField - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; - } - - @NoSQLField - public int getClientType() { - return clientType; - } - - public void setClientType(int clientType) { - this.clientType = clientType; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java deleted file mode 100755 index 9bd14d23f1..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/RoleData.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data; - -import org.jboss.logging.Logger; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.api.query.NoSQLQuery; - -import java.util.List; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "roles") -public class RoleData implements NoSQLObject { - - private static final Logger logger = Logger.getLogger(RoleData.class); - - private String id; - private String name; - private String description; - - private String realmId; - private String applicationId; - - @NoSQLId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @NoSQLField - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @NoSQLField - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - @NoSQLField - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; - } - - @NoSQLField - public String getApplicationId() { - return applicationId; - } - - public void setApplicationId(String applicationId) { - this.applicationId = applicationId; - } - - @Override - public void afterRemove(NoSQL noSQL) { - // Remove this role from all users, which has it - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("roleIds", id) - .build(); - - List users = noSQL.loadObjects(UserData.class, query); - for (UserData user : users) { - logger.info("Removing role " + getName() + " from user " + user.getLoginName()); - noSQL.pullItemFromList(user, "roleIds", getId()); - } - - // Remove this scope from all users, which has it - query = noSQL.createQueryBuilder() - .andCondition("scopeIds", id) - .build(); - - users = noSQL.loadObjects(UserData.class, query); - for (UserData user : users) { - logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); - noSQL.pullItemFromList(user, "scopeIds", getId()); - } - - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java deleted file mode 100755 index 6983f83c3a..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/OTPData.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data.credentials; - -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; - -import java.util.Date; - -/** - * @author 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/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java deleted file mode 100755 index 6ac585f58c..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/credentials/PasswordData.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.keycloak.models.mongo.keycloak.data.credentials; - -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; - -import java.util.Date; - -/** - * @author Marek Posolda - */ -@NoSQLCollection(collectionName = "passwordCredentials") -public class PasswordData extends AbstractNoSQLObject { - - private Date effectiveDate = new Date(); - private Date expiryDate; - private String encodedHash; - private String salt; - - private String userId; - - @NoSQLField - public Date getEffectiveDate() { - return effectiveDate; - } - - public void setEffectiveDate(Date effectiveDate) { - this.effectiveDate = effectiveDate; - } - - @NoSQLField - public Date getExpiryDate() { - return expiryDate; - } - - public void setExpiryDate(Date expiryDate) { - this.expiryDate = expiryDate; - } - - @NoSQLField - public String getEncodedHash() { - return encodedHash; - } - - public void setEncodedHash(String encodedHash) { - this.encodedHash = encodedHash; - } - - @NoSQLField - public String getSalt() { - return salt; - } - - public void setSalt(String salt) { - this.salt = salt; - } - - @NoSQLField - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java similarity index 58% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java index 5ceb788dbb..5791353c98 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/ApplicationData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java @@ -1,17 +1,21 @@ -package org.keycloak.models.mongo.keycloak.data; +package org.keycloak.models.mongo.keycloak.entities; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.api.query.NoSQLQuery; +import java.util.ArrayList; +import java.util.List; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; /** * @author Marek Posolda */ -@NoSQLCollection(collectionName = "applications") -public class ApplicationData implements NoSQLObject { +@MongoCollection(collectionName = "applications") +public class ApplicationEntity implements MongoEntity { private String id; private String name; @@ -23,7 +27,10 @@ public class ApplicationData implements NoSQLObject { private String resourceUserId; private String realmId; - @NoSQLId + // We are using names of defaultRoles (not ids) + private List defaultRoles = new ArrayList(); + + @MongoId public String getId() { return id; } @@ -32,7 +39,7 @@ public class ApplicationData implements NoSQLObject { this.id = id; } - @NoSQLField + @MongoField public String getName() { return name; } @@ -41,7 +48,7 @@ public class ApplicationData implements NoSQLObject { this.name = name; } - @NoSQLField + @MongoField public boolean isEnabled() { return enabled; } @@ -50,7 +57,7 @@ public class ApplicationData implements NoSQLObject { this.enabled = enabled; } - @NoSQLField + @MongoField public boolean isSurrogateAuthRequired() { return surrogateAuthRequired; } @@ -59,7 +66,7 @@ public class ApplicationData implements NoSQLObject { this.surrogateAuthRequired = surrogateAuthRequired; } - @NoSQLField + @MongoField public String getManagementUrl() { return managementUrl; } @@ -68,7 +75,7 @@ public class ApplicationData implements NoSQLObject { this.managementUrl = managementUrl; } - @NoSQLField + @MongoField public String getBaseUrl() { return baseUrl; } @@ -77,7 +84,7 @@ public class ApplicationData implements NoSQLObject { this.baseUrl = baseUrl; } - @NoSQLField + @MongoField public String getResourceUserId() { return resourceUserId; } @@ -86,7 +93,7 @@ public class ApplicationData implements NoSQLObject { this.resourceUserId = resourceUserId; } - @NoSQLField + @MongoField public String getRealmId() { return realmId; } @@ -95,15 +102,24 @@ public class ApplicationData implements NoSQLObject { this.realmId = realmId; } + @MongoField + public List getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; + } + @Override - public void afterRemove(NoSQL noSQL) { + public void afterRemove(MongoStore mongoStore) { // Remove resourceUser of this application - noSQL.removeObject(UserData.class, resourceUserId); + mongoStore.removeObject(UserEntity.class, resourceUserId); // Remove all roles, which belongs to this application - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("applicationId", id) - .build(); - noSQL.removeObjects(RoleData.class, query); + DBObject query = new QueryBuilder() + .and("applicationId").is(id) + .get(); + mongoStore.removeObjects(RoleEntity.class, query); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java new file mode 100644 index 0000000000..f29eadcbae --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/CredentialEntity.java @@ -0,0 +1,51 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoField; + +/** + * @author Marek Posolda + */ +public class CredentialEntity extends AbstractMongoEntity { + + private String type; + private String value; + private String device; + private byte[] salt; + + @MongoField + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @MongoField + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @MongoField + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + @MongoField + public byte[] getSalt() { + return salt; + } + + public void setSalt(byte[] salt) { + this.salt = salt; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java new file mode 100644 index 0000000000..a2a902d7c4 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/OAuthClientEntity.java @@ -0,0 +1,62 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "oauthClients") +public class OAuthClientEntity implements MongoEntity { + + private String id; + private String name; + + private String oauthAgentId; + private String realmId; + + @MongoId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @MongoField + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @MongoField + public String getOauthAgentId() { + return oauthAgentId; + } + + public void setOauthAgentId(String oauthUserId) { + this.oauthAgentId = oauthUserId; + } + + @MongoField + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + @Override + public void afterRemove(MongoStore mongoStore) { + // Remove user of this oauthClient + mongoStore.removeObject(UserEntity.class, oauthAgentId); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java new file mode 100755 index 0000000000..abb2041cf1 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java @@ -0,0 +1,255 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "realms") +public class RealmEntity implements MongoEntity { + + private String id; + + private String name; + private boolean enabled; + private boolean sslNotRequired; + private boolean registrationAllowed; + private boolean verifyEmail; + private boolean resetPasswordAllowed; + private boolean social; + private boolean updateProfileOnInitialSocialLogin; + private String passwordPolicy; + + private int tokenLifespan; + private int accessCodeLifespan; + private int accessCodeLifespanUserAction; + + private String publicKeyPem; + private String privateKeyPem; + + // We are using names of defaultRoles (not ids) + private List defaultRoles = new ArrayList(); + + private List requiredCredentials = new ArrayList(); + private List requiredApplicationCredentials = new ArrayList(); + private List requiredOAuthClientCredentials = new ArrayList(); + + private Map smtpConfig = new HashMap(); + private Map socialConfig = new HashMap(); + + @MongoId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @MongoField + public String getName() { + return name; + } + + public void setName(String realmName) { + this.name = realmName; + } + + @MongoField + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @MongoField + public boolean isSslNotRequired() { + return sslNotRequired; + } + + public void setSslNotRequired(boolean sslNotRequired) { + this.sslNotRequired = sslNotRequired; + } + + @MongoField + public boolean isRegistrationAllowed() { + return registrationAllowed; + } + + public void setRegistrationAllowed(boolean registrationAllowed) { + this.registrationAllowed = registrationAllowed; + } + + @MongoField + public boolean isVerifyEmail() { + return verifyEmail; + } + + public void setVerifyEmail(boolean verifyEmail) { + this.verifyEmail = verifyEmail; + } + + @MongoField + public boolean isResetPasswordAllowed() { + return resetPasswordAllowed; + } + + public void setResetPasswordAllowed(boolean resetPasswordAllowed) { + this.resetPasswordAllowed = resetPasswordAllowed; + } + + @MongoField + public boolean isSocial() { + return social; + } + + public void setSocial(boolean social) { + this.social = social; + } + + @MongoField + public boolean isUpdateProfileOnInitialSocialLogin() { + return updateProfileOnInitialSocialLogin; + } + + public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) { + this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin; + } + + @MongoField + public String getPasswordPolicy() { + return passwordPolicy; + } + + public void setPasswordPolicy(String passwordPolicy) { + this.passwordPolicy = passwordPolicy; + } + + @MongoField + public int getTokenLifespan() { + return tokenLifespan; + } + + public void setTokenLifespan(int tokenLifespan) { + this.tokenLifespan = tokenLifespan; + } + + @MongoField + public int getAccessCodeLifespan() { + return accessCodeLifespan; + } + + public void setAccessCodeLifespan(int accessCodeLifespan) { + this.accessCodeLifespan = accessCodeLifespan; + } + + @MongoField + public int getAccessCodeLifespanUserAction() { + return accessCodeLifespanUserAction; + } + + public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { + this.accessCodeLifespanUserAction = accessCodeLifespanUserAction; + } + + @MongoField + public String getPublicKeyPem() { + return publicKeyPem; + } + + public void setPublicKeyPem(String publicKeyPem) { + this.publicKeyPem = publicKeyPem; + } + + @MongoField + public String getPrivateKeyPem() { + return privateKeyPem; + } + + public void setPrivateKeyPem(String privateKeyPem) { + this.privateKeyPem = privateKeyPem; + } + + @MongoField + public List getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(List defaultRoles) { + this.defaultRoles = defaultRoles; + } + + @MongoField + public List getRequiredCredentials() { + return requiredCredentials; + } + + public void setRequiredCredentials(List requiredCredentials) { + this.requiredCredentials = requiredCredentials; + } + + @MongoField + public List getRequiredApplicationCredentials() { + return requiredApplicationCredentials; + } + + public void setRequiredApplicationCredentials(List requiredApplicationCredentials) { + this.requiredApplicationCredentials = requiredApplicationCredentials; + } + + @MongoField + public List getRequiredOAuthClientCredentials() { + return requiredOAuthClientCredentials; + } + + public void setRequiredOAuthClientCredentials(List requiredOAuthClientCredentials) { + this.requiredOAuthClientCredentials = requiredOAuthClientCredentials; + } + + @MongoField + public Map getSmtpConfig() { + return smtpConfig; + } + + public void setSmtpConfig(Map smptConfig) { + this.smtpConfig = smptConfig; + } + + @MongoField + public Map getSocialConfig() { + return socialConfig; + } + + public void setSocialConfig(Map socialConfig) { + this.socialConfig = socialConfig; + } + + @Override + public void afterRemove(MongoStore mongoStore) { + DBObject query = new QueryBuilder() + .and("realmId").is(id) + .get(); + + // Remove all users of this realm + mongoStore.removeObjects(UserEntity.class, query); + + // Remove all roles of this realm + mongoStore.removeObjects(RoleEntity.class, query); + + // Remove all applications of this realm + mongoStore.removeObjects(ApplicationEntity.class, query); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java new file mode 100644 index 0000000000..f36f438e19 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RequiredCredentialEntity.java @@ -0,0 +1,51 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoField; + +/** + * @author Marek Posolda + */ +public class RequiredCredentialEntity extends AbstractMongoEntity { + + private String type; + private boolean input; + private boolean secret; + private String formLabel; + + @MongoField + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @MongoField + public boolean isInput() { + return input; + } + + public void setInput(boolean input) { + this.input = input; + } + + @MongoField + public boolean isSecret() { + return secret; + } + + public void setSecret(boolean secret) { + this.secret = secret; + } + + @MongoField + public String getFormLabel() { + return formLabel; + } + + public void setFormLabel(String formLabel) { + this.formLabel = formLabel; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java new file mode 100755 index 0000000000..2c367c68fc --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java @@ -0,0 +1,148 @@ +package org.keycloak.models.mongo.keycloak.entities; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.jboss.logging.Logger; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; + +import java.util.List; + +/** + * @author Marek Posolda + */ +@MongoCollection(collectionName = "roles") +public class RoleEntity implements MongoEntity { + + private static final Logger logger = Logger.getLogger(RoleEntity.class); + + private String id; + private String name; + private String description; + private boolean composite; + + private List compositeRoleIds; + + private String realmId; + private String applicationId; + + @MongoId + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @MongoField + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @MongoField + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @MongoField + public boolean isComposite() { + return composite; + } + + public void setComposite(boolean composite) { + this.composite = composite; + } + + @MongoField + public List getCompositeRoleIds() { + return compositeRoleIds; + } + + public void setCompositeRoleIds(List compositeRoleIds) { + this.compositeRoleIds = compositeRoleIds; + } + + @MongoField + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + @MongoField + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + @Override + public void afterRemove(MongoStore mongoStore) { + // Remove this role from all users, which has it + DBObject query = new QueryBuilder() + .and("roleIds").is(id) + .get(); + + List users = mongoStore.loadObjects(UserEntity.class, query); + for (UserEntity user : users) { + logger.info("Removing role " + getName() + " from user " + user.getLoginName()); + mongoStore.pullItemFromList(user, "roleIds", getId()); + } + + // Remove this scope from all users, which has it + query = new QueryBuilder() + .and("scopeIds").is(id) + .get(); + + users = mongoStore.loadObjects(UserEntity.class, query); + for (UserEntity user : users) { + logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); + mongoStore.pullItemFromList(user, "scopeIds", getId()); + } + + // Remove defaultRoles from realm + if (realmId != null) { + RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, realmId); + + // Realm might be already removed at this point + if (realmEntity != null) { + mongoStore.pullItemFromList(realmEntity, "defaultRoles", getId()); + } + } + + // Remove defaultRoles from application + if (applicationId != null) { + ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, applicationId); + + // Application might be already removed at this point + if (appEntity != null) { + mongoStore.pullItemFromList(appEntity, "defaultRoles", getId()); + } + } + + // Remove this role from others who has it as composite + query = new QueryBuilder() + .and("compositeRoleIds").is(getId()) + .get(); + List parentRoles = mongoStore.loadObjects(RoleEntity.class, query); + for (RoleEntity role : parentRoles) { + mongoStore.pullItemFromList(role, "compositeRoleIds", getId()); + } + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java similarity index 74% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java index 37ea43d44e..3a34a25f2a 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/SocialLinkData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java @@ -1,14 +1,14 @@ -package org.keycloak.models.mongo.keycloak.data; +package org.keycloak.models.mongo.keycloak.entities; -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; +import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoField; /** * @author Marek Posolda */ -@NoSQLCollection(collectionName = "socialLinks") -public class SocialLinkData extends AbstractNoSQLObject { +@MongoCollection(collectionName = "socialLinks") +public class SocialLinkEntity extends AbstractMongoEntity { private String socialUsername; private String socialProvider; @@ -17,7 +17,7 @@ public class SocialLinkData extends AbstractNoSQLObject { // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2") private String realmId; - @NoSQLField + @MongoField public String getSocialUsername() { return socialUsername; } @@ -26,7 +26,7 @@ public class SocialLinkData extends AbstractNoSQLObject { this.socialUsername = socialUsername; } - @NoSQLField + @MongoField public String getSocialProvider() { return socialProvider; } @@ -35,7 +35,7 @@ public class SocialLinkData extends AbstractNoSQLObject { this.socialProvider = socialProvider; } - @NoSQLField + @MongoField public String getUserId() { return userId; } @@ -44,7 +44,7 @@ public class SocialLinkData extends AbstractNoSQLObject { this.userId = userId; } - @NoSQLField + @MongoField public String getRealmId() { return realmId; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java similarity index 54% rename from model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java index c57bca38e9..fc90f3b150 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/data/UserData.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java @@ -1,24 +1,24 @@ -package org.keycloak.models.mongo.keycloak.data; +package org.keycloak.models.mongo.keycloak.entities; -import org.jboss.logging.Logger; +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; import org.keycloak.models.UserModel; -import org.keycloak.models.mongo.api.AbstractAttributedNoSQLObject; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import org.keycloak.models.mongo.api.MongoStore; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Marek Posolda */ -@NoSQLCollection(collectionName = "users") -public class UserData extends AbstractAttributedNoSQLObject { - - private static final Logger logger = Logger.getLogger(UserData.class); +@MongoCollection(collectionName = "users") +public class UserEntity implements MongoEntity { private String id; private String loginName; @@ -33,9 +33,14 @@ public class UserData extends AbstractAttributedNoSQLObject { private List roleIds; private List scopeIds; - private List requiredActions; - @NoSQLId + private Map attributes; + private List webOrigins; + private List redirectUris; + private List requiredActions; + private List credentials = new ArrayList(); + + @MongoId public String getId() { return id; } @@ -44,7 +49,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.id = id; } - @NoSQLField + @MongoField public String getLoginName() { return loginName; } @@ -53,7 +58,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.loginName = loginName; } - @NoSQLField + @MongoField public String getFirstName() { return firstName; } @@ -62,7 +67,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.firstName = firstName; } - @NoSQLField + @MongoField public String getLastName() { return lastName; } @@ -71,7 +76,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.lastName = lastName; } - @NoSQLField + @MongoField public String getEmail() { return email; } @@ -80,7 +85,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.email = email; } - @NoSQLField + @MongoField public boolean isEmailVerified() { return emailVerified; } @@ -89,7 +94,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.emailVerified = emailVerified; } - @NoSQLField + @MongoField public boolean isEnabled() { return enabled; } @@ -98,7 +103,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.enabled = enabled; } - @NoSQLField + @MongoField public boolean isTotp() { return totp; } @@ -107,7 +112,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.totp = totp; } - @NoSQLField + @MongoField public String getRealmId() { return realmId; } @@ -116,7 +121,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.realmId = realmId; } - @NoSQLField + @MongoField public List getRoleIds() { return roleIds; } @@ -125,7 +130,7 @@ public class UserData extends AbstractAttributedNoSQLObject { this.roleIds = roleIds; } - @NoSQLField + @MongoField public List getScopeIds() { return scopeIds; } @@ -134,7 +139,34 @@ public class UserData extends AbstractAttributedNoSQLObject { this.scopeIds = scopeIds; } - @NoSQLField + @MongoField + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @MongoField + public List getWebOrigins() { + return webOrigins; + } + + public void setWebOrigins(List webOrigins) { + this.webOrigins = webOrigins; + } + + @MongoField + public List getRedirectUris() { + return redirectUris; + } + + public void setRedirectUris(List redirectUris) { + this.redirectUris = redirectUris; + } + + @MongoField public List getRequiredActions() { return requiredActions; } @@ -143,25 +175,22 @@ public class UserData extends AbstractAttributedNoSQLObject { this.requiredActions = requiredActions; } + @MongoField + public List getCredentials() { + return credentials; + } + + public void setCredentials(List credentials) { + this.credentials = credentials; + } + @Override - public void afterRemove(NoSQL noSQL) { - NoSQLQuery query = noSQL.createQueryBuilder() - .andCondition("userId", id) - .build(); + public void afterRemove(MongoStore mongoStore) { + DBObject query = new QueryBuilder() + .and("userId").is(id) + .get(); - // 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()); - } + // Remove social links of this user + mongoStore.removeObjects(SocialLinkEntity.class, query); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java new file mode 100644 index 0000000000..db445e396a --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java @@ -0,0 +1,50 @@ +package org.keycloak.models.mongo.utils; + +import java.io.IOException; + +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodProcess; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Version; +import de.flapdoodle.embed.process.runtime.Network; +import org.jboss.logging.Logger; + +/** + * @author Marek Posolda + */ +public class EmbeddedMongo { + + protected static final Logger logger = Logger.getLogger(EmbeddedMongo.class); + + private MongodExecutable mongodExe; + private MongodProcess mongod; + + public void startEmbeddedMongo(int port) { + logger.info("Going to start embedded Mongo on port=" + port); + + try { + IMongodConfig mongodConfig = new MongodConfigBuilder() + .version(Version.Main.PRODUCTION) + .net(new Net(port, Network.localhostIsIPv6())) + .build(); + mongodExe = MongodStarter.getDefaultInstance().prepare(mongodConfig); + mongod = mongodExe.start(); + } catch (IOException e) { + logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage()); + throw new RuntimeException(e); + } + } + + public void stopEmbeddedMongo() { + if (mongodExe != null) { + if (mongod != null) { + logger.info("Going to stop embedded MongoDB."); + mongod.stop(); + } + mongodExe.stop(); + } + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoConfiguration.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoConfiguration.java new file mode 100644 index 0000000000..7a22ae6417 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoConfiguration.java @@ -0,0 +1,50 @@ +package org.keycloak.models.mongo.utils; + +/** + * Encapsulates all info about configuration of MongoDB instance + * + * @author Marek Posolda + */ +public class MongoConfiguration { + + private final String host; + private final int port; + private final String dbName; + + private final boolean clearCollectionsOnStartup; + private final boolean startEmbedded; + + public MongoConfiguration(String host, int port, String dbName, boolean clearCollectionsOnStartup, boolean startEmbedded) { + this.host = host; + this.port = port; + this.dbName = dbName; + this.clearCollectionsOnStartup = clearCollectionsOnStartup; + this.startEmbedded = startEmbedded; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getDbName() { + return dbName; + } + + public boolean isClearCollectionsOnStartup() { + return clearCollectionsOnStartup; + } + + public boolean isStartEmbedded() { + return startEmbedded; + } + + @Override + public String toString() { + return String.format("MongoConfiguration: host: %s, port: %d, dbName: %s, clearCollectionsOnStartup: %b, startEmbedded: %b", + host, port, dbName, clearCollectionsOnStartup, startEmbedded); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java new file mode 100644 index 0000000000..260d182cde --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java @@ -0,0 +1,59 @@ +package org.keycloak.models.mongo.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.mongodb.DBObject; +import com.mongodb.QueryBuilder; +import org.bson.types.ObjectId; +import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.keycloak.adapters.UserAdapter; +import org.keycloak.models.mongo.keycloak.entities.RoleEntity; +import org.keycloak.models.mongo.keycloak.entities.UserEntity; + +/** + * @author Marek Posolda + */ +public class MongoModelUtils { + + public static List convertStringsToObjectIds(Collection strings) { + List result = new ArrayList(); + for (String id : strings) { + result.add(new ObjectId(id)); + } + return result; + } + + // Get everything including both application and realm roles + public static List getAllRolesOfUser(UserModel user, MongoStore mongoStore) { + UserEntity userEntity = ((UserAdapter)user).getUser(); + List roleIds = userEntity.getRoleIds(); + + if (roleIds == null || roleIds.isEmpty()) { + return Collections.EMPTY_LIST; + } + + DBObject query = new QueryBuilder() + .and("_id").in(convertStringsToObjectIds(roleIds)) + .get(); + return mongoStore.loadObjects(RoleEntity.class, query); + } + + // Get everything including both application and realm scopes + public static List getAllScopesOfUser(UserModel user, MongoStore mongoStore) { + UserEntity userEntity = ((UserAdapter)user).getUser(); + List scopeIds = userEntity.getScopeIds(); + + if (scopeIds == null || scopeIds.isEmpty()) { + return Collections.EMPTY_LIST; + } + + DBObject query = new QueryBuilder() + .and("_id").in(convertStringsToObjectIds(scopeIds)) + .get(); + return mongoStore.loadObjects(RoleEntity.class, query); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/SystemPropertiesConfigurationProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/SystemPropertiesConfigurationProvider.java new file mode 100755 index 0000000000..d30786fefd --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/SystemPropertiesConfigurationProvider.java @@ -0,0 +1,56 @@ +package org.keycloak.models.mongo.utils; + +/** + * @author Marek Posolda + */ +public class SystemPropertiesConfigurationProvider { + + private static final String MONGO_HOST = "keycloak.mongo.host"; + private static final String MONGO_PORT = "keycloak.mongo.port"; + private static final String MONGO_DB_NAME = "keycloak.mongo.db"; + private static final String MONGO_CLEAR_COLLECTIONS_ON_STARTUP = "keycloak.mongo.clearCollectionsOnStartup"; + private static final String MONGO_START_EMBEDDED = "keycloak.mongo.startEmbedded"; + + // Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance + private static final int MONGO_DEFAULT_PORT = 27017; + + // Port where embedded MongoDB instance will be started. Same port will be used by KeycloakApplication then + public static final int MONGO_DEFAULT_PORT_EMBEDDED = 27018; + + public static String getMongoHost() { + return System.getProperty(MONGO_HOST, "localhost"); + } + + public static int getMongoPort() { + String portProp = System.getProperty(MONGO_PORT); + if (portProp != null) { + return Integer.parseInt(portProp); + } else { + // Default port is 27017 in case of non-embedded, and 27018 in case of embedded + return isStartEmbedded() ? MONGO_DEFAULT_PORT_EMBEDDED : MONGO_DEFAULT_PORT; + } + } + + public static String getMongoDbName() { + return System.getProperty(MONGO_DB_NAME, "keycloak"); + } + + public static boolean isClearCollectionsOnStartup() { + return Boolean.parseBoolean(System.getProperty(MONGO_CLEAR_COLLECTIONS_ON_STARTUP, "true")); + } + + public static boolean isStartEmbedded() { + return Boolean.parseBoolean(System.getProperty(MONGO_START_EMBEDDED, "false")); + } + + // Create configuration based on system properties + public static MongoConfiguration createConfiguration() { + return new MongoConfiguration( + getMongoHost(), + getMongoPort(), + getMongoDbName(), + isClearCollectionsOnStartup(), + isStartEmbedded() + ); + } +} diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java index 386ca31127..d68ce0d23d 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java @@ -1,20 +1,20 @@ package org.keycloak.models.mongo.test; -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLField; +import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoField; import java.util.List; /** * @author Marek Posolda */ -public class Address extends AbstractNoSQLObject { +public class Address extends AbstractMongoEntity { private String street; private int number; private List flatNumbers; - @NoSQLField + @MongoField public String getStreet() { return street; } @@ -23,7 +23,7 @@ public class Address extends AbstractNoSQLObject { this.street = street; } - @NoSQLField + @MongoField public int getNumber() { return number; } @@ -32,7 +32,7 @@ public class Address extends AbstractNoSQLObject { this.number = number; } - @NoSQLField + @MongoField public List getFlatNumbers() { return flatNumbers; } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java index 3b8651a254..88f1745dfa 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/MongoDBModelTest.java @@ -1,14 +1,16 @@ package org.keycloak.models.mongo.test; import com.mongodb.DB; +import com.mongodb.DBObject; import com.mongodb.MongoClient; +import com.mongodb.QueryBuilder; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.keycloak.models.mongo.api.NoSQL; -import org.keycloak.models.mongo.api.NoSQLObject; -import org.keycloak.models.mongo.api.query.NoSQLQuery; -import org.keycloak.models.mongo.impl.MongoDBImpl; +import org.junit.Test; +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.impl.MongoStoreImpl; import java.net.UnknownHostException; import java.util.ArrayList; @@ -20,13 +22,13 @@ import java.util.List; */ public class MongoDBModelTest { - private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { + private static final Class[] MANAGED_DATA_TYPES = (Class[])new Class[] { Person.class, Address.class, }; private MongoClient mongoClient; - private NoSQL mongoDB; + private MongoStore mongoDB; @Before public void before() throws Exception { @@ -35,7 +37,7 @@ public class MongoDBModelTest { mongoClient = new MongoClient("localhost", 27017); DB db = mongoClient.getDB("keycloakTest"); - mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES); + mongoDB = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES); } catch (UnknownHostException e) { throw new RuntimeException(e); @@ -47,7 +49,7 @@ public class MongoDBModelTest { mongoClient.close(); } - // @Test + @Test public void mongoModelTest() throws Exception { // Add some user Person john = new Person(); @@ -55,17 +57,17 @@ public class MongoDBModelTest { john.setAge(25); john.setGender(Person.Gender.MALE); - mongoDB.saveObject(john); + mongoDB.insertObject(john); // Add another user Person mary = new Person(); mary.setFirstName("mary"); - mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"})); + mary.setKids(Arrays.asList("Peter", "Paul", "Wendy")); Address addr1 = new Address(); addr1.setStreet("Elm"); addr1.setNumber(5); - addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"})); + addr1.setFlatNumbers(Arrays.asList("flat1", "flat2")); Address addr2 = new Address(); List
addresses = new ArrayList
(); addresses.add(addr1); @@ -74,12 +76,13 @@ public class MongoDBModelTest { mary.setAddresses(addresses); mary.setMainAddress(addr1); mary.setGender(Person.Gender.FEMALE); - mary.setGenders(Arrays.asList(new Person.Gender[] {Person.Gender.FEMALE})); - mongoDB.saveObject(mary); + mary.setGenders(Arrays.asList(Person.Gender.FEMALE)); - Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size()); + mongoDB.insertObject(mary); - NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build(); + Assert.assertEquals(2, mongoDB.loadObjects(Person.class, new QueryBuilder().get()).size()); + + DBObject query = new QueryBuilder().and("addresses.flatNumbers").is("flat1").get(); List persons = mongoDB.loadObjects(Person.class, query); Assert.assertEquals(1, persons.size()); mary = persons.get(0); @@ -89,13 +92,13 @@ public class MongoDBModelTest { Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass()); // Test push/pull - mongoDB.pushItemToList(mary, "kids", "Pauline"); + mongoDB.pushItemToList(mary, "kids", "Pauline", true); mongoDB.pullItemFromList(mary, "kids", "Paul"); Address addr3 = new Address(); addr3.setNumber(6); addr3.setStreet("Broadway"); - mongoDB.pushItemToList(mary, "addresses", addr3); + mongoDB.pushItemToList(mary, "addresses", addr3, true); mary = mongoDB.loadObject(Person.class, mary.getId()); Assert.assertEquals(3, mary.getKids().size()); @@ -107,5 +110,24 @@ public class MongoDBModelTest { Assert.assertEquals(5, mainAddress.getNumber()); Assert.assertEquals(Person.Gender.FEMALE, mary.getGender()); Assert.assertTrue(mary.getGenders().contains(Person.Gender.FEMALE)); + + + // Some test of Map (attributes) + mary.addAttribute("attr1", "value1"); + mary.addAttribute("attr2", "value2"); + mary.addAttribute("attr.some3", "value3"); + mongoDB.updateObject(mary); + + mary = mongoDB.loadObject(Person.class, mary.getId()); + Assert.assertEquals(3, mary.getAttributes().size()); + + mary.removeAttribute("attr2"); + mary.removeAttribute("nonExisting"); + mongoDB.updateObject(mary); + + mary = mongoDB.loadObject(Person.class, mary.getId()); + Assert.assertEquals(2, mary.getAttributes().size()); + Assert.assertEquals("value1", mary.getAttributes().get("attr1")); + Assert.assertEquals("value3", mary.getAttributes().get("attr.some3")); } } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java index 7f8d0d9c24..7298c6e5ad 100755 --- a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java +++ b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Person.java @@ -1,17 +1,19 @@ package org.keycloak.models.mongo.test; -import org.keycloak.models.mongo.api.AbstractNoSQLObject; -import org.keycloak.models.mongo.api.NoSQLCollection; -import org.keycloak.models.mongo.api.NoSQLField; -import org.keycloak.models.mongo.api.NoSQLId; +import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoField; +import org.keycloak.models.mongo.api.MongoId; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Marek Posolda */ -@NoSQLCollection(collectionName = "persons") -public class Person extends AbstractNoSQLObject { +@MongoCollection(collectionName = "persons") +public class Person extends AbstractMongoEntity { private String id; private String firstName; @@ -21,9 +23,10 @@ public class Person extends AbstractNoSQLObject { private Address mainAddress; private Gender gender; private List genders; + private Map attributes = new HashMap(); - @NoSQLId + @MongoId public String getId() { return id; } @@ -32,7 +35,7 @@ public class Person extends AbstractNoSQLObject { this.id = id; } - @NoSQLField + @MongoField public String getFirstName() { return firstName; } @@ -41,7 +44,7 @@ public class Person extends AbstractNoSQLObject { this.firstName = firstName; } - @NoSQLField + @MongoField public int getAge() { return age; } @@ -50,7 +53,7 @@ public class Person extends AbstractNoSQLObject { this.age = age; } - @NoSQLField + @MongoField public Gender getGender() { return gender; } @@ -59,7 +62,7 @@ public class Person extends AbstractNoSQLObject { this.gender = gender; } - @NoSQLField + @MongoField public List getGenders() { return genders; } @@ -68,7 +71,7 @@ public class Person extends AbstractNoSQLObject { this.genders = genders; } - @NoSQLField + @MongoField public List getKids() { return kids; } @@ -77,7 +80,7 @@ public class Person extends AbstractNoSQLObject { this.kids = kids; } - @NoSQLField + @MongoField public List
getAddresses() { return addresses; } @@ -86,7 +89,7 @@ public class Person extends AbstractNoSQLObject { this.addresses = addresses; } - @NoSQLField + @MongoField public Address getMainAddress() { return mainAddress; } @@ -95,6 +98,23 @@ public class Person extends AbstractNoSQLObject { this.mainAddress = mainAddress; } + @MongoField + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public void addAttribute(String key, String value) { + attributes.put(key, value); + } + + public void removeAttribute(String key) { + attributes.remove(key); + } + public static enum Gender { MALE, FEMALE } diff --git a/model/pom.xml b/model/pom.xml index ab454cad1e..3301b71eaa 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -37,6 +37,6 @@ api jpa - + mongo diff --git a/pom.xml b/pom.xml index b1aaefd6e8..fc58b75b2e 100755 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 3.0.6.Final 1.0.0.Beta30 2.5.0.Beta6 - 2.11.2 + 2.11.3 3.1.1.GA 1.2.0.Beta1 1.0.1.Final @@ -316,7 +316,7 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - 1.27 + 1.40 org.apache.jmeter diff --git a/services/pom.xml b/services/pom.xml index d97d6b40c0..de0ae59da7 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -72,11 +72,25 @@ --> - + org.keycloak keycloak-model-mongo ${project.version} - --> + test + + + org.mongodb + mongo-java-driver + test + + + org.picketlink + picketlink-common + test + + + 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 6c89c12d16..080e28f167 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.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.SocialRequestManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.resources.admin.AdminService; +import org.keycloak.services.utils.ModelProviderUtils; import javax.servlet.ServletContext; import javax.ws.rs.core.Application; @@ -15,7 +16,6 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import java.net.URI; import java.util.HashSet; -import java.util.ServiceLoader; import java.util.Set; /** @@ -26,9 +26,6 @@ public class KeycloakApplication extends Application { private static final Logger log = Logger.getLogger(KeycloakApplication.class); - private static final String MODEL_PROVIDER = "keycloak.model"; - private static final String DEFAULT_MODEL_PROVIDER = "jpa"; - protected Set singletons = new HashSet(); protected Set> classes = new HashSet>(); @@ -73,28 +70,7 @@ public class KeycloakApplication extends Application { public static KeycloakSessionFactory createSessionFactory() { - ServiceLoader providers = ServiceLoader.load(ModelProvider.class); - String configuredProvider = System.getProperty(MODEL_PROVIDER); - ModelProvider provider = null; - - if (configuredProvider != null) { - for (ModelProvider p : providers) { - if (p.getId().equals(configuredProvider)) { - provider = p; - } - } - } else { - for (ModelProvider p : providers) { - if (provider == null) { - provider = p; - } - - if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) { - provider = p; - break; - } - } - } + ModelProvider provider = ModelProviderUtils.getConfiguredModelProvider(); if (provider != null) { log.debug("Model provider: " + provider.getId()); diff --git a/services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java b/services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java new file mode 100644 index 0000000000..7dcfc95173 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java @@ -0,0 +1,50 @@ +package org.keycloak.services.utils; + +import java.util.ServiceLoader; + +import org.keycloak.models.ModelProvider; + +/** + * @author Marek Posolda + */ +public class ModelProviderUtils { + + public static final String MODEL_PROVIDER = "keycloak.model"; + public static final String DEFAULT_MODEL_PROVIDER = "jpa"; + + public static Iterable getRegisteredProviders() { + return ServiceLoader.load(ModelProvider.class); + } + + public static ModelProvider getConfiguredModelProvider(Iterable providers) { + String configuredProvider = System.getProperty(MODEL_PROVIDER); + ModelProvider provider = null; + + if (configuredProvider != null) { + for (ModelProvider p : providers) { + if (p.getId().equals(configuredProvider)) { + provider = p; + } + } + } else { + for (ModelProvider p : providers) { + if (provider == null) { + provider = p; + } + + if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) { + provider = p; + break; + } + } + } + + return provider; + } + + public static ModelProvider getConfiguredModelProvider() { + return getConfiguredModelProvider(getRegisteredProviders()); + } + + +} diff --git a/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java b/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java deleted file mode 100755 index 583a8c8d1f..0000000000 --- a/services/src/main/java/org/keycloak/services/utils/PropertiesManager.java +++ /dev/null @@ -1,88 +0,0 @@ -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"; - public static final String SESSION_FACTORY_JPA = "jpa"; - - private static final String MONGO_HOST = "keycloak.mongodb.host"; - private static final String MONGO_PORT = "keycloak.mongodb.port"; - private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName"; - private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup"; - private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit"; - - // Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then - private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017; - - // Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false) - private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017; - - // Port where unit tests will start embedded MongoDB instance - public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777; - - public static String getSessionFactoryType() { - return System.getProperty(SESSION_FACTORY, SESSION_FACTORY_JPA); - } - - public static void setSessionFactoryType(String sessionFactoryType) { - System.setProperty(SESSION_FACTORY, sessionFactoryType); - } - - public static void setDefaultSessionFactoryType() { - System.setProperty(SESSION_FACTORY, SESSION_FACTORY_JPA); - } - - public static boolean isMongoSessionFactory() { - return getSessionFactoryType().equals(SESSION_FACTORY_MONGO); - } - - public static boolean isPicketlinkSessionFactory() { - return getSessionFactoryType().equals(SESSION_FACTORY_PICKETLINK); - } - - public static boolean isJpaSessionFactory() { - return getSessionFactoryType().equals(SESSION_FACTORY_JPA); - } - - - public static String getMongoHost() { - return System.getProperty(MONGO_HOST, "localhost"); - } - - public static void setMongoHost(String mongoHost) { - System.setProperty(MONGO_HOST, mongoHost); - } - - public static int getMongoPort() { - return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED))); - } - - public static void setMongoPort(int mongoPort) { - System.setProperty(MONGO_PORT, String.valueOf(mongoPort)); - } - - public static String getMongoDbName() { - return System.getProperty(MONGO_DB_NAME, "keycloak"); - } - - public static void setMongoDbName(String mongoMongoDbName) { - System.setProperty(MONGO_DB_NAME, mongoMongoDbName); - } - - public static boolean dropDatabaseOnStartup() { - return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true")); - } - - public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) { - System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup)); - } - - public static boolean bootstrapEmbeddedMongoAtContextInit() { - return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true")); - } -} 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 527838ddd3..404e8f2c71 100755 --- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java +++ b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java @@ -11,7 +11,6 @@ import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus; import org.keycloak.test.common.AbstractKeycloakTest; -import org.keycloak.test.common.SessionFactoryTestContext; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; @@ -25,10 +24,9 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest { private RealmModel realm; private UserModel user; - public AuthenticationManagerTest(SessionFactoryTestContext testContext) { - super(testContext); + public AuthenticationManagerTest(String providerId) { + super(providerId); } - @Test public void authForm() { AuthenticationStatus status = am.authenticateForm(realm, user, formData); @@ -126,7 +124,7 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest { @Before public void before() throws Exception { super.before(); - realm = getRealmManager().createRealm("Test"); + realm = realmManager.createRealm("Test"); realm.setAccessCodeLifespan(100); realm.setEnabled(true); realm.setName("Test"); diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java index 9f49a98dc7..1e459146c6 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/services/src/test/java/org/keycloak/test/AdapterTest.java @@ -18,7 +18,6 @@ import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.OAuthClientManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.test.common.AbstractKeycloakTest; -import org.keycloak.test.common.SessionFactoryTestContext; import java.util.ArrayList; import java.util.Arrays; @@ -35,8 +34,8 @@ import java.util.StringTokenizer; public class AdapterTest extends AbstractKeycloakTest { private RealmModel realmModel; - public AdapterTest(SessionFactoryTestContext testContext) { - super(testContext); + public AdapterTest(String providerId) { + super(providerId); } @Test @@ -57,7 +56,7 @@ public class AdapterTest extends AbstractKeycloakTest { @Test public void test1CreateRealm() throws Exception { - realmModel = getRealmManager().createRealm("JUGGLER"); + realmModel = realmManager.createRealm("JUGGLER"); realmModel.setAccessCodeLifespan(100); realmModel.setAccessCodeLifespanUserAction(600); realmModel.setEnabled(true); @@ -69,7 +68,7 @@ public class AdapterTest extends AbstractKeycloakTest { realmModel.addDefaultRole("foo"); System.out.println(realmModel.getId()); - realmModel = getRealmManager().getRealm(realmModel.getId()); + realmModel = realmManager.getRealm(realmModel.getId()); Assert.assertNotNull(realmModel); Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100); Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction()); @@ -85,7 +84,7 @@ public class AdapterTest extends AbstractKeycloakTest { @Test public void testRealmListing() throws Exception { - realmModel = getRealmManager().createRealm("JUGGLER"); + realmModel = realmManager.createRealm("JUGGLER"); realmModel.setAccessCodeLifespan(100); realmModel.setAccessCodeLifespanUserAction(600); realmModel.setEnabled(true); @@ -97,7 +96,7 @@ public class AdapterTest extends AbstractKeycloakTest { realmModel.addDefaultRole("foo"); System.out.println(realmModel.getId()); - realmModel = getRealmManager().getRealm(realmModel.getId()); + realmModel = realmManager.getRealm(realmModel.getId()); Assert.assertNotNull(realmModel); Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100); Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction()); @@ -300,7 +299,7 @@ public class AdapterTest extends AbstractKeycloakTest { user3.setEmail("knut@redhat.com"); } - RealmManager adapter = getRealmManager(); + RealmManager adapter = realmManager; { List userModels = adapter.searchUsers("total junk query", realmModel); diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java index 1942b41f01..8c3c3f23bc 100755 --- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java +++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java @@ -1,19 +1,15 @@ package org.keycloak.test; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.keycloak.models.ApplicationModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.services.managers.ApplicationManager; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; import java.util.Iterator; import java.util.List; @@ -21,24 +17,21 @@ import java.util.List; /** * @author Stian Thorgersen */ -public class ApplicationModelTest extends AbstractKeycloakServerTest { - private KeycloakSessionFactory factory; - private KeycloakSession identitySession; - private RealmManager manager; +public class ApplicationModelTest extends AbstractKeycloakTest { private ApplicationModel application; private RealmModel realm; private ApplicationManager appManager; + public ApplicationModelTest(String providerId) { + super(providerId); + } + @Before public void before() throws Exception { - factory = KeycloakApplication.createSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - manager = new RealmManager(identitySession); + super.before(); + appManager = new ApplicationManager(realmManager); - appManager = new ApplicationManager(manager); - - realm = manager.createRealm("original"); + realm = realmManager.createRealm("original"); application = realm.addApplication("application"); application.setBaseUrl("http://base"); application.setManagementUrl("http://management"); @@ -57,16 +50,9 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest { application.updateApplication(); } - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); - } - @Test public void persist() { - RealmModel persisted = manager.getRealm(realm.getId()); + RealmModel persisted = realmManager.getRealm(realm.getId()); assertEquals(application, persisted.getApplicationNameMap().get("app-name")); } @@ -75,7 +61,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest { public void json() { ApplicationRepresentation representation = appManager.toRepresentation(application); - RealmModel realm = manager.createRealm("copy"); + RealmModel realm = realmManager.createRealm("copy"); ApplicationModel copy = appManager.createApplication(realm, representation); assertEquals(application, copy); diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java index d7ad50cd8d..8b7287cae6 100755 --- a/services/src/test/java/org/keycloak/test/ImportTest.java +++ b/services/src/test/java/org/keycloak/test/ImportTest.java @@ -13,7 +13,6 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.test.common.AbstractKeycloakTest; -import org.keycloak.test.common.SessionFactoryTestContext; import java.util.List; import java.util.Set; @@ -25,13 +24,13 @@ import java.util.Set; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class ImportTest extends AbstractKeycloakTest { - public ImportTest(SessionFactoryTestContext testContext) { - super(testContext); + public ImportTest(String providerId) { + super(providerId); } @Test public void install() throws Exception { - RealmManager manager = getRealmManager(); + RealmManager manager = realmManager; RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm.json"); RealmModel realm = manager.createRealm("demo", rep.getRealm()); manager.importRealm(rep, realm); @@ -91,7 +90,7 @@ public class ImportTest extends AbstractKeycloakTest { @Test public void install2() throws Exception { - RealmManager manager = getRealmManager(); + RealmManager manager = realmManager; RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm-demo.json"); RealmModel realm = manager.createRealm("demo", rep.getRealm()); manager.importRealm(rep, realm); diff --git a/services/src/test/java/org/keycloak/test/ModelTest.java b/services/src/test/java/org/keycloak/test/ModelTest.java index cdec0040be..1c9b5d50b2 100755 --- a/services/src/test/java/org/keycloak/test/ModelTest.java +++ b/services/src/test/java/org/keycloak/test/ModelTest.java @@ -1,46 +1,27 @@ package org.keycloak.test; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.ModelToRepresentation; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; import java.util.HashMap; import java.util.Iterator; import java.util.List; -public class ModelTest extends AbstractKeycloakServerTest { - private KeycloakSessionFactory factory; - private KeycloakSession identitySession; - private RealmManager manager; +public class ModelTest extends AbstractKeycloakTest { - @Before - public void before() throws Exception { - factory = KeycloakApplication.createSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - manager = new RealmManager(identitySession); - } - - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); + public ModelTest(String providerId) { + super(providerId); } @Test public void importExportRealm() { - RealmModel realm = manager.createRealm("original"); + RealmModel realm = realmManager.createRealm("original"); realm.setRegistrationAllowed(true); realm.setResetPasswordAllowed(true); realm.setSocial(true); @@ -62,10 +43,10 @@ public class ModelTest extends AbstractKeycloakServerTest { HashMap social = new HashMap(); social.put("google.key", "1234"); social.put("google.secret", "5678"); - realm.setSmtpConfig(social); + realm.setSocialConfig(social); - RealmModel peristed = manager.getRealm(realm.getId()); - assertEquals(realm, peristed); + RealmModel persisted = realmManager.getRealm(realm.getId()); + assertEquals(realm, persisted); RealmModel copy = importExport(realm, "copy"); assertEquals(realm, copy); @@ -103,9 +84,9 @@ public class ModelTest extends AbstractKeycloakServerTest { private RealmModel importExport(RealmModel src, String copyName) { RealmRepresentation representation = ModelToRepresentation.toRepresentation(src); - RealmModel copy = manager.createRealm(copyName); - manager.importRealm(representation, copy); - return manager.getRealm(copy.getId()); + RealmModel copy = realmManager.createRealm(copyName); + realmManager.importRealm(representation, copy); + return realmManager.getRealm(copy.getId()); } } diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/services/src/test/java/org/keycloak/test/UserModelTest.java index 338af77230..d808174e90 100755 --- a/services/src/test/java/org/keycloak/test/UserModelTest.java +++ b/services/src/test/java/org/keycloak/test/UserModelTest.java @@ -1,17 +1,13 @@ package org.keycloak.test; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.test.common.AbstractKeycloakTest; import java.util.Iterator; import java.util.List; @@ -19,29 +15,15 @@ import java.util.List; /** * @author Stian Thorgersen */ -public class UserModelTest extends AbstractKeycloakServerTest { - private KeycloakSessionFactory factory; - private KeycloakSession identitySession; - private RealmManager manager; +public class UserModelTest extends AbstractKeycloakTest { - @Before - public void before() throws Exception { - factory = KeycloakApplication.createSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - manager = new RealmManager(identitySession); - } - - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); + public UserModelTest(String providerId) { + super(providerId); } @Test public void persistUser() { - RealmModel realm = manager.createRealm("original"); + RealmModel realm = realmManager.createRealm("original"); UserModel user = realm.addUser("user"); user.setFirstName("first-name"); user.setLastName("last-name"); @@ -56,14 +38,14 @@ public class UserModelTest extends AbstractKeycloakServerTest { user.addWebOrigin("origin-1"); user.addWebOrigin("origin-2"); - UserModel persisted = manager.getRealm(realm.getId()).getUser("user"); + UserModel persisted = realmManager.getRealm(realm.getId()).getUser("user"); assertEquals(user, persisted); } @Test public void webOriginSetTest() { - RealmModel realm = manager.createRealm("original"); + RealmModel realm = realmManager.createRealm("original"); UserModel user = realm.addUser("user"); Assert.assertTrue(user.getWebOrigins().isEmpty()); @@ -83,7 +65,7 @@ public class UserModelTest extends AbstractKeycloakServerTest { @Test public void testUserRequiredActions() throws Exception { - RealmModel realm = manager.createRealm("original"); + RealmModel realm = realmManager.createRealm("original"); UserModel user = realm.addUser("user"); Assert.assertTrue(user.getRequiredActions().isEmpty()); @@ -91,7 +73,7 @@ public class UserModelTest extends AbstractKeycloakServerTest { user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP); String id = realm.getId(); commit(); - realm = manager.getRealm(id); + realm = realmManager.getRealm(id); user = realm.getUser("user"); Assert.assertEquals(1, user.getRequiredActions().size()); @@ -127,7 +109,7 @@ public class UserModelTest extends AbstractKeycloakServerTest { identitySession.close(); identitySession = factory.createSession(); identitySession.getTransaction().begin(); - manager = new RealmManager(identitySession); + realmManager = new RealmManager(identitySession); } public static void assertEquals(UserModel expected, UserModel actual) { 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 11512d79a5..63c411dee9 100755 --- a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java +++ b/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java @@ -1,5 +1,6 @@ package org.keycloak.test.common; +import org.jboss.resteasy.logging.Logger; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -8,11 +9,15 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.ModelProvider; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.utils.ModelProviderUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.ServiceLoader; /** * @author Marek Posolda @@ -20,58 +25,42 @@ import java.util.List; @RunWith(Parameterized.class) public abstract class AbstractKeycloakTest { - protected static final SessionFactoryTestContext[] TEST_CONTEXTS; + private static final Logger log = Logger.getLogger(AbstractKeycloakTest.class); - private final SessionFactoryTestContext testContext; protected KeycloakSessionFactory factory; protected KeycloakSession identitySession; protected RealmManager realmManager; - // STATIC METHODS - - static - { - // TODO: MongoDB disabled by default - TEST_CONTEXTS = new SessionFactoryTestContext[] { - //new PicketlinkSessionFactoryTestContext(), - new JpaSessionFactoryTestContext(), - // new MongoDBSessionFactoryTestContext() - }; - } - @Parameterized.Parameters public static Iterable parameters() { + Iterable modelProviders; + + // We will run tests with all registered models if -Dkeycloak.model=all . Otherwise just with configured provider + String configuredProvider = System.getProperty(ModelProviderUtils.MODEL_PROVIDER); + if ("all".equalsIgnoreCase(configuredProvider)) { + modelProviders = ModelProviderUtils.getRegisteredProviders(); + } else { + ModelProvider provider = ModelProviderUtils.getConfiguredModelProvider(); + modelProviders = Arrays.asList(provider); + } + + log.debug("Will use model providers: " + modelProviders); + List params = new ArrayList(); - for (SessionFactoryTestContext testContext : TEST_CONTEXTS) { - params.add(new Object[] {testContext}); + for (ModelProvider provider : modelProviders) { + params.add(new Object[] { provider.getId() }); } return params; } - @BeforeClass - public static void baseBeforeClass() { - for (SessionFactoryTestContext testContext : TEST_CONTEXTS) { - testContext.beforeTestClass(); - } - } - @AfterClass - public static void baseAfterClass() { - for (SessionFactoryTestContext testContext : TEST_CONTEXTS) { - testContext.afterTestClass(); - } - } - - // NON-STATIC METHODS - - public AbstractKeycloakTest(SessionFactoryTestContext testContext) { - this.testContext = testContext; + public AbstractKeycloakTest(String providerId) { + System.setProperty(ModelProviderUtils.MODEL_PROVIDER, providerId); } @Before public void before() throws Exception { - testContext.initEnvironment(); factory = KeycloakApplication.createSessionFactory(); identitySession = factory.createSession(); identitySession.getTransaction().begin(); @@ -85,12 +74,4 @@ public abstract class AbstractKeycloakTest { factory.close(); } - protected RealmManager getRealmManager() { - return realmManager; - } - - protected KeycloakSession getIdentitySession() { - return identitySession; - } - } diff --git a/services/src/test/java/org/keycloak/test/common/JpaSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/JpaSessionFactoryTestContext.java deleted file mode 100755 index 94b80664cc..0000000000 --- a/services/src/test/java/org/keycloak/test/common/JpaSessionFactoryTestContext.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.keycloak.test.common; - -import org.keycloak.services.utils.PropertiesManager; - -/** - * @author Marek Posolda - */ -public class JpaSessionFactoryTestContext implements SessionFactoryTestContext { - - @Override - public void beforeTestClass() { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void afterTestClass() { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void initEnvironment() { - PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_JPA); - } -} diff --git a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java b/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java deleted file mode 100755 index d152f3be34..0000000000 --- a/services/src/test/java/org/keycloak/test/common/PicketlinkSessionFactoryTestContext.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.keycloak.test.common; - -import org.keycloak.services.utils.PropertiesManager; - -/** - * @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() { - PropertiesManager.setSessionFactoryType(PropertiesManager.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 deleted file mode 100644 index a35cfd25c6..0000000000 --- a/services/src/test/java/org/keycloak/test/common/SessionFactoryTestContext.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.keycloak.test.common; - -/** - * @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(); -}