From 81ff7b0c6dfd1f340aa9c6ddc12bb31d04a6bb9d Mon Sep 17 00:00:00 2001 From: mposolda Date: Sat, 1 Feb 2014 00:08:40 +0100 Subject: [PATCH 1/3] 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(); -} From b3f1032f96fa270a15bac9ec10ead7f3c659c7e4 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 6 Feb 2014 23:25:38 +0100 Subject: [PATCH 2/3] Mongo: Refactoring. All unit tests and testsuite are passing with Mongo. --- .../models/mongo/api/AbstractMongoEntity.java | 12 - .../api/AbstractMongoIdentifiableEntity.java | 50 ++++ .../models/mongo/api/MongoEntity.java | 9 +- .../keycloak/models/mongo/api/MongoId.java | 18 -- .../mongo/api/MongoIdentifiableEntity.java | 21 ++ .../keycloak/models/mongo/api/MongoStore.java | 22 +- .../context/MongoStoreInvocationContext.java | 25 ++ .../models/mongo/api/context/MongoTask.java | 13 + .../models/mongo/impl/MongoStoreImpl.java | 224 ++++++++++------- .../models/mongo/impl/ObjectInfo.java | 9 +- .../SimpleMongoStoreInvocationContext.java | 56 +++++ ...ransactionMongoStoreInvocationContext.java | 129 ++++++++++ .../impl/types/BasicDBObjectConverter.java | 6 +- .../keycloak/adapters/AbstractAdapter.java | 38 +++ .../keycloak/adapters/ApplicationAdapter.java | 63 ++--- .../adapters/MongoKeycloakSession.java | 31 ++- .../adapters/MongoKeycloakTransaction.java | 33 ++- .../keycloak/adapters/OAuthClientAdapter.java | 21 +- .../mongo/keycloak/adapters/RealmAdapter.java | 192 ++++++++------- .../mongo/keycloak/adapters/RoleAdapter.java | 54 ++-- .../mongo/keycloak/adapters/UserAdapter.java | 29 ++- .../keycloak/entities/ApplicationEntity.java | 23 +- .../keycloak/entities/CredentialEntity.java | 4 +- .../keycloak/entities/OAuthClientEntity.java | 19 +- .../mongo/keycloak/entities/RealmEntity.java | 47 ++-- .../entities/RequiredCredentialEntity.java | 4 +- .../mongo/keycloak/entities/RoleEntity.java | 51 ++-- .../keycloak/entities/SocialLinkEntity.java | 47 ++-- .../mongo/keycloak/entities/UserEntity.java | 32 +-- .../models/mongo/utils/MongoModelUtils.java | 9 +- .../keycloak/models/mongo/test/Address.java | 4 +- .../models/mongo/test/MongoDBModelTest.java | 47 ++-- .../keycloak/models/mongo/test/Person.java | 16 +- .../managers/AuthenticationManager.java | 3 +- .../services/managers/RealmManager.java | 10 +- .../services/managers/TokenManager.java | 7 +- .../managers/AuthenticationManagerTest.java | 2 +- .../{common => }/AbstractKeycloakTest.java | 5 +- .../java/org/keycloak/test/AdapterTest.java | 1 - .../keycloak/test/ApplicationModelTest.java | 1 - .../test/CompositeRolesModelTest.java | 105 ++++++++ .../java/org/keycloak/test/ImportTest.java | 69 +++++- .../java/org/keycloak/test/ModelTest.java | 1 - .../java/org/keycloak/test/UserModelTest.java | 1 - .../src/test/resources/testcomposites.json | 231 ++++++++++++++++++ services/src/test/resources/testrealm.json | 44 ++-- testsuite/integration/pom.xml | 30 +++ .../keycloak/testutils/KeycloakServer.java | 2 +- 48 files changed, 1307 insertions(+), 563 deletions(-) delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java rename services/src/test/java/org/keycloak/test/{common => }/AbstractKeycloakTest.java (95%) create mode 100644 services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java create mode 100644 services/src/test/resources/testcomposites.json diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java deleted file mode 100644 index aaaffabe07..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.keycloak.models.mongo.api; - -/** - * @author Marek Posolda - */ -public abstract class AbstractMongoEntity implements MongoEntity { - - @Override - public void afterRemove(MongoStore mongoStore) { - // Empty by default - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java new file mode 100644 index 0000000000..d6509e05ee --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java @@ -0,0 +1,50 @@ +package org.keycloak.models.mongo.api; + +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Marek Posolda + */ +public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { + // Empty by default + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + + if (this.id == null) return false; + + if (o == null || getClass() != o.getClass()) return false; + + AbstractMongoIdentifiableEntity that = (AbstractMongoIdentifiableEntity) o; + + if (!getId().equals(that.getId())) return false; + + return true; + + } + + @Override + public int hashCode() { + return id!=null ? id.hashCode() : super.hashCode(); + } + + @Override + public String toString() { + return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId()); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java index 6af6bbc0ea..8b91583344 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoEntity.java @@ -1,16 +1,11 @@ package org.keycloak.models.mongo.api; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + /** * Base interface for object, which is persisted in Mongo * * @author Marek Posolda */ public interface MongoEntity { - - /** - * Lifecycle callback, which is called after removal of this object from Mongo. - * It may be useful for triggering removal of wired objects. - */ - void afterRemove(MongoStore mongoStore); - } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java deleted file mode 100644 index 6799635cc5..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoId.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.keycloak.models.mongo.api; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * @author Marek Posolda - */ -@Target({METHOD, FIELD}) -@Documented -@Retention(RUNTIME) -public @interface MongoId { -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java new file mode 100644 index 0000000000..45ce126709 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java @@ -0,0 +1,21 @@ +package org.keycloak.models.mongo.api; + +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * Entity with Id + * + * @author Marek Posolda + */ +public interface MongoIdentifiableEntity extends MongoEntity { + + public String getId(); + + public void setId(String id); + + /** + * Lifecycle callback, which is called after removal of this object from Mongo. + * It may be useful for triggering removal of wired objects. + */ + void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invocationContext); +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java index 89e4bb744c..17411495aa 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java @@ -1,6 +1,7 @@ package org.keycloak.models.mongo.api; import com.mongodb.DBObject; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import java.util.List; @@ -14,30 +15,29 @@ public interface MongoStore { * * @param object to update */ - void insertObject(MongoEntity object); + void insertObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); /** * Update existing object * * @param object to update */ - void updateObject(MongoEntity object); + void updateObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); - T loadObject(Class type, String oid); + T loadObject(Class type, String oid, MongoStoreInvocationContext context); - T loadSingleObject(Class type, DBObject query); + T loadSingleObject(Class type, DBObject query, MongoStoreInvocationContext context); - List loadObjects(Class type, DBObject query); + List loadObjects(Class type, DBObject query, MongoStoreInvocationContext context); - // Object must have filled oid - boolean removeObject(MongoEntity object); + boolean removeObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); - boolean removeObject(Class type, String oid); + boolean removeObject(Class type, String id, MongoStoreInvocationContext context); - boolean removeObjects(Class type, DBObject query); + boolean removeObjects(Class type, DBObject query, MongoStoreInvocationContext context); - boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent); + boolean pushItemToList(MongoIdentifiableEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context); - void pullItemFromList(MongoEntity object, String listPropertyName, S itemToPull); + boolean pullItemFromList(MongoIdentifiableEntity object, String listPropertyName, S itemToPull, MongoStoreInvocationContext context); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java new file mode 100644 index 0000000000..b6911e9e03 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java @@ -0,0 +1,25 @@ +package org.keycloak.models.mongo.api.context; + +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; + +/** + * @author Marek Posolda + */ +public interface MongoStoreInvocationContext { + + void addLoadedObject(MongoIdentifiableEntity entity); + + T getLoadedObject(Class type, String id); + + void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task); + + void addRemovedObject(MongoIdentifiableEntity entityToRemove); + + void beforeDBSearch(Class entityType); + + void begin(); + + void commit(); + + void rollback(); +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java new file mode 100644 index 0000000000..0c1d500b69 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoTask.java @@ -0,0 +1,13 @@ +package org.keycloak.models.mongo.api.context; + +import org.keycloak.models.mongo.api.MongoStore; + +/** + * @author Marek Posolda + */ +public interface MongoTask { + + void execute(); + + boolean isFullUpdate(); +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java index 0c2d5610d1..5fe1d0a281 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java @@ -11,8 +11,10 @@ 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.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.models.mongo.api.context.MongoTask; import org.keycloak.models.mongo.api.types.Converter; import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; @@ -105,7 +107,7 @@ public class MongoStoreImpl implements MongoStore { } @Override - public void insertObject(MongoEntity object) { + public void insertObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context) { Class clazz = object.getClass(); // Find annotations for ID, for all the properties and for the name of the collection. @@ -116,8 +118,7 @@ public class MongoStoreImpl implements MongoStore { DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); - Property oidProperty = objectInfo.getOidProperty(); - String currentId = oidProperty == null ? null : oidProperty.getValue(object); + String currentId = object.getId(); // Inserting object, which already has oid property set. So we need to set "_id" if (currentId != null) { @@ -126,48 +127,73 @@ public class MongoStoreImpl implements MongoStore { 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); - + // Add id to value of given object if (currentId == null) { - throw new IllegalStateException("Can't update object without id: " + object); - } else { - BasicDBObject query = new BasicDBObject("_id", getObjectId(currentId)); - dbCollection.update(query, dbObject); + object.setId(dbObject.getString("_id")); } + + // Treat object as if it is read (It is already submited to transaction) + context.addLoadedObject(object); + } + + @Override + public void updateObject(final MongoIdentifiableEntity object, MongoStoreInvocationContext context) { + MongoTask fullUpdateTask = new MongoTask() { + + @Override + public void execute() { + Class clazz = object.getClass(); + ObjectInfo objectInfo = getObjectInfo(clazz); + BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class); + DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + + String currentId = object.getId(); + + if (currentId == null) { + throw new IllegalStateException("Can't update object without id: " + object); + } else { + BasicDBObject query = new BasicDBObject("_id", getObjectId(currentId)); + dbCollection.update(query, dbObject); + } + } + + @Override + public boolean isFullUpdate() { + return true; + } + }; + + // update is just added to context and postponed + context.addUpdateTask(object, fullUpdateTask); } @Override - public T loadObject(Class type, String oid) { + public T loadObject(Class type, String id, MongoStoreInvocationContext context) { + // First look if we already read the object with this oid and type during this transaction. If yes, use it instead of DB lookup + T cached = context.getLoadedObject(type, id); + if (cached != null) return cached; + DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject idQuery = new BasicDBObject("_id", getObjectId(oid)); + BasicDBObject idQuery = new BasicDBObject("_id", getObjectId(id)); DBObject dbObject = dbCollection.findOne(idQuery); if (dbObject == null) return null; ConverterContext converterContext = new ConverterContext(dbObject, type, null); - return (T)typeConverter.convertDBObjectToApplicationObject(converterContext); + T converted = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); + + // Now add it to loaded objects + context.addLoadedObject(converted); + + return converted; } @Override - public T loadSingleObject(Class type, DBObject query) { - List result = loadObjects(type, query); + public T loadSingleObject(Class type, DBObject query, MongoStoreInvocationContext context) { + List result = loadObjects(type, query, context); if (result.size() > 1) { throw new IllegalStateException("There are " + result.size() + " results for type=" + type + ", query=" + query + ". We expect just one"); } else if (result.size() == 1) { @@ -180,47 +206,43 @@ public class MongoStoreImpl implements MongoStore { @Override - public List loadObjects(Class type, DBObject query) { - DBCollection dbCollection = getDBCollectionForType(type); + public List loadObjects(Class type, DBObject query, MongoStoreInvocationContext context) { + // First we should execute all pending tasks before searching DB + context.beforeDBSearch(type); + DBCollection dbCollection = getDBCollectionForType(type); DBCursor cursor = dbCollection.find(query); - return convertCursor(type, cursor); + return convertCursor(type, cursor, context); } @Override - public boolean removeObject(MongoEntity object) { - Class type = object.getClass(); - ObjectInfo objectInfo = getObjectInfo(type); - - Property idProperty = objectInfo.getOidProperty(); - String oid = idProperty.getValue(object); - - return removeObject(type, oid); + public boolean removeObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context) { + return removeObject(object.getClass(), object.getId(), context); } @Override - public boolean removeObject(Class type, String oid) { - MongoEntity found = loadObject(type, oid); + public boolean removeObject(Class type, String id, MongoStoreInvocationContext context) { + MongoIdentifiableEntity found = loadObject(type, id, context); if (found == null) { return false; } else { DBCollection dbCollection = getDBCollectionForType(type); - BasicDBObject dbQuery = new BasicDBObject("_id", getObjectId(oid)); + BasicDBObject dbQuery = new BasicDBObject("_id", getObjectId(id)); dbCollection.remove(dbQuery); - logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB."); + logger.info("Object of type: " + type + ", id: " + id + " removed from MongoDB."); - found.afterRemove(this); + context.addRemovedObject(found); return true; } } @Override - public boolean removeObjects(Class type, DBObject query) { - List foundObjects = loadObjects(type, query); + public boolean removeObjects(Class type, DBObject query, MongoStoreInvocationContext context) { + List foundObjects = loadObjects(type, query, context); if (foundObjects.size() == 0) { return false; } else { @@ -228,23 +250,18 @@ public class MongoStoreImpl implements MongoStore { dbCollection.remove(query); logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query); - for (MongoEntity found : foundObjects) { - found.afterRemove(this); + for (MongoIdentifiableEntity found : foundObjects) { + context.addRemovedObject(found);; } return true; } } @Override - public boolean pushItemToList(MongoEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent) { - Class type = object.getClass(); + public boolean pushItemToList(final MongoIdentifiableEntity object, final String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context) { + final Class type = object.getClass(); ObjectInfo objectInfo = getObjectInfo(type); - Property oidProperty = getObjectInfo(type).getOidProperty(); - if (oidProperty == null) { - throw new IllegalArgumentException("List pushes not supported for properties without oid"); - } - // Add item to list directly in this object Property listProperty = objectInfo.getPropertyByName(listPropertyName); if (listProperty == null) { @@ -257,34 +274,44 @@ public class MongoStoreImpl implements MongoStore { listProperty.setValue(object, list); } - // Return if item is already in list + // Skip if item is already in list if (skipIfAlreadyPresent && list.contains(itemToPush)) { return false; } + // Update java object list.add(itemToPush); - // 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); + // Add update of list to pending tasks + final List listt = list; + context.addUpdateTask(object, new MongoTask() { + + @Override + public void execute() { + // Now DB update of new list with usage of $set + BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(listt, BasicDBList.class); + + BasicDBObject query = new BasicDBObject("_id", getObjectId(object.getId())); + BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList); + BasicDBObject setCommand = new BasicDBObject("$set", listObject); + getDBCollectionForType(type).update(query, setCommand); + } + + @Override + public boolean isFullUpdate() { + return false; + } + }); - 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(MongoEntity object, String listPropertyName, S itemToPull) { - Class type = object.getClass(); + public boolean pullItemFromList(final MongoIdentifiableEntity object, final String listPropertyName, final S itemToPull, MongoStoreInvocationContext context) { + final Class type = object.getClass(); ObjectInfo objectInfo = getObjectInfo(type); - Property oidProperty = getObjectInfo(type).getOidProperty(); - if (oidProperty == null) { - throw new IllegalArgumentException("List pulls not supported for properties without oid"); - } - // Remove item from list directly in this object Property listProperty = objectInfo.getPropertyByName(listPropertyName); if (listProperty == null) { @@ -293,15 +320,33 @@ public class MongoStoreImpl implements MongoStore { List list = (List)listProperty.getValue(object); // If list is null, we skip both object and DB update - if (list != null) { + if (list == null || !list.contains(itemToPull)) { + return false; + } else { + + // Update java object list.remove(itemToPull); - // Pull item from DB - Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class); - 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); + // Add update of list to pending tasks + context.addUpdateTask(object, new MongoTask() { + + @Override + public void execute() { + // Pull item from DB + Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class); + BasicDBObject query = new BasicDBObject("_id", getObjectId(object.getId())); + BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull); + BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject); + getDBCollectionForType(type).update(query, pullCommand); + } + + @Override + public boolean isFullUpdate() { + return false; + } + }); + + return true; } } @@ -317,14 +362,12 @@ public class MongoStoreImpl implements MongoStore { public ObjectInfo getObjectInfo(Class objectClass) { ObjectInfo objectInfo = objectInfoCache.get(objectClass); if (objectInfo == null) { - Property idProperty = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoId.class)).getFirstResult(); - List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList(); MongoCollection classAnnotation = objectClass.getAnnotation(MongoCollection.class); String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName(); - objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties); + objectInfo = new ObjectInfo(objectClass, dbCollectionName, properties); ObjectInfo existing = objectInfoCache.putIfAbsent(objectClass, objectInfo); if (existing != null) { @@ -335,14 +378,23 @@ public class MongoStoreImpl implements MongoStore { return objectInfo; } - private List convertCursor(Class type, DBCursor cursor) { + protected List convertCursor(Class type, DBCursor cursor, MongoStoreInvocationContext context) { List result = new ArrayList(); try { for (DBObject dbObject : cursor) { - ConverterContext converterContext = new ConverterContext(dbObject, type, null); - T converted = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); - result.add(converted); + // First look if we already have loaded object cached. If yes, we will use cached instance + String id = dbObject.get("_id").toString(); + T object = context.getLoadedObject(type, id); + + if (object == null) { + // So convert and use fresh instance from DB + ConverterContext converterContext = new ConverterContext(dbObject, type, null); + object = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); + context.addLoadedObject(object); + } + + result.add(object); } } finally { cursor.close(); @@ -351,14 +403,14 @@ public class MongoStoreImpl implements MongoStore { return result; } - private DBCollection getDBCollectionForType(Class type) { + protected DBCollection getDBCollectionForType(Class type) { ObjectInfo objectInfo = getObjectInfo(type); String dbCollectionName = objectInfo.getDbCollectionName(); return dbCollectionName==null ? null : database.getCollection(objectInfo.getDbCollectionName()); } // We allow ObjectId to be both "ObjectId" or "String". - private Object getObjectId(String idAsString) { + protected Object getObjectId(String idAsString) { if (ObjectId.isValid(idAsString)) { return new ObjectId(idAsString); } else { 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 0662ad6aaf..797954a91d 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 @@ -18,14 +18,11 @@ public class ObjectInfo { private final String dbCollectionName; - private final Property oidProperty; - private final Map> properties; - public ObjectInfo(Class objectClass, String dbCollectionName, Property oidProperty, List> properties) { + public ObjectInfo(Class objectClass, String dbCollectionName, List> properties) { this.objectClass = objectClass; this.dbCollectionName = dbCollectionName; - this.oidProperty = oidProperty; Map> props= new HashMap>(); for (Property property : properties) { @@ -42,10 +39,6 @@ public class ObjectInfo { return dbCollectionName; } - public Property getOidProperty() { - return oidProperty; - } - public Collection> getProperties() { return properties.values(); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java new file mode 100644 index 0000000000..18c2d6a356 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java @@ -0,0 +1,56 @@ +package org.keycloak.models.mongo.impl.context; + +import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.models.mongo.api.context.MongoTask; + +/** + * Context, which is not doing any postponing of tasks and does not cache anything + * + * @author Marek Posolda + */ +public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationContext { + + private final MongoStore store; + + public SimpleMongoStoreInvocationContext(MongoStore store) { + this.store = store; + } + + @Override + public void addLoadedObject(MongoIdentifiableEntity entity) { + } + + @Override + public T getLoadedObject(Class type, String id) { + return null; + } + + @Override + public void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task) { + task.execute(); + } + + @Override + public void addRemovedObject(MongoIdentifiableEntity entityToRemove) { + entityToRemove.afterRemove(store, this); + } + + @Override + public void beforeDBSearch(Class entityType) { + } + + @Override + public void begin() { + } + + @Override + public void commit() { + } + + @Override + public void rollback() { + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java new file mode 100644 index 0000000000..1f9f66d1b8 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java @@ -0,0 +1,129 @@ +package org.keycloak.models.mongo.impl.context; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.models.mongo.api.context.MongoTask; + +/** + * Invocation context, which has some very basic support for transactions, and is able to cache loaded objects. + * It always execute all pending update tasks before start searching for other objects + * + * It's per-request object (not thread safe) + * + * @author Marek Posolda + */ +public class TransactionMongoStoreInvocationContext implements MongoStoreInvocationContext { + + // Assumption is that all objects has unique ID (unique across all the types) + private Map loadedObjects = new HashMap(); + + private Map> pendingUpdateTasks = new HashMap>(); + + private final MongoStore mongoStore; + + public TransactionMongoStoreInvocationContext(MongoStore mongoStore) { + this.mongoStore = mongoStore; + } + + @Override + public void addLoadedObject(MongoIdentifiableEntity entity) { + loadedObjects.put(entity.getId(), entity); + } + + @Override + public T getLoadedObject(Class type, String id) { + return (T)loadedObjects.get(id); + } + + @Override + public void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task) { + if (!loadedObjects.containsValue(entityToUpdate)) { + throw new IllegalStateException("Entity " + entityToUpdate + " not found in loaded objects"); + } + + Set currentObjectTasks = pendingUpdateTasks.get(entityToUpdate); + if (currentObjectTasks == null) { + currentObjectTasks = new HashSet(); + pendingUpdateTasks.put(entityToUpdate, currentObjectTasks); + } else { + // if task is full update, then remove all other tasks as we need to do full update of object anyway + if (task.isFullUpdate()) { + currentObjectTasks.clear(); + } else { + // If it already contains task for fullUpdate, then we don't need to add ours as we need to do full update of object anyway + for (MongoTask current : currentObjectTasks) { + if (current.isFullUpdate()) { + return; + } + } + } + } + + currentObjectTasks.add(task); + } + + @Override + public void addRemovedObject(MongoIdentifiableEntity entityToRemove) { + // Remove all pending tasks and object from cache + pendingUpdateTasks.remove(entityToRemove); + loadedObjects.remove(entityToRemove.getId()); + + entityToRemove.afterRemove(mongoStore, this); + } + + @Override + public void beforeDBSearch(Class entityType) { + // Now execute pending update tasks of type, which will be searched + Set toRemove = new HashSet(); + + for (MongoIdentifiableEntity currentEntity : pendingUpdateTasks.keySet()) { + if (currentEntity.getClass().equals(entityType)) { + Set mongoTasks = pendingUpdateTasks.get(currentEntity); + for (MongoTask currentTask : mongoTasks) { + currentTask.execute(); + } + + toRemove.add(currentEntity); + } + } + + // Now remove all done tasks + for (MongoIdentifiableEntity entity : toRemove) { + pendingUpdateTasks.remove(entity); + } + } + + @Override + public void begin() { + loadedObjects.clear(); + pendingUpdateTasks.clear(); + } + + @Override + public void commit() { + loadedObjects.clear(); + + // Now execute all pending update tasks + for (Set mongoTasks : pendingUpdateTasks.values()) { + for (MongoTask currentTask : mongoTasks) { + currentTask.execute(); + } + } + + // And clear it + pendingUpdateTasks.clear(); + } + + @Override + public void rollback() { + // Just clear the map without executions of tasks + loadedObjects.clear(); + pendingUpdateTasks.clear(); + } +} 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 62d0a821d0..2d130e6344 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 @@ -8,6 +8,7 @@ import java.util.List; import com.mongodb.BasicDBObject; import org.jboss.logging.Logger; import org.keycloak.models.mongo.api.MongoEntity; +import org.keycloak.models.mongo.api.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.types.Converter; import org.keycloak.models.mongo.api.types.ConverterContext; import org.keycloak.models.mongo.api.types.TypeConverter; @@ -55,9 +56,8 @@ public class BasicDBObjectConverter implements Converter< if ("_id".equals(key)) { // Current property is "id" - Property idProperty = objectInfo.getOidProperty(); - if (idProperty != null) { - idProperty.setValue(object, value.toString()); + if (object instanceof MongoIdentifiableEntity) { + ((MongoIdentifiableEntity)object).setId(value.toString()); } } else if ((property = objectInfo.getPropertyByName(key)) != null) { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java new file mode 100644 index 0000000000..cd6a1dd5a8 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java @@ -0,0 +1,38 @@ +package org.keycloak.models.mongo.keycloak.adapters; + +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; + +/** + * @author Marek Posolda + */ +public abstract class AbstractAdapter { + + protected MongoStore mongoStore; + protected MongoStoreInvocationContext invocationContext; + + public AbstractAdapter(MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { + this.mongoStore = mongoStore; + this.invocationContext = invocationContext; + } + + public abstract AbstractMongoIdentifiableEntity getMongoEntity(); + + @Override + public boolean equals(Object o) { + if (o == this) return true; + + if (o == null || getClass() != o.getClass()) return false; + + AbstractAdapter that = (AbstractAdapter) o; + + if (getMongoEntity() == null && that.getMongoEntity() == null) return true; + return getMongoEntity().equals(that.getMongoEntity()); + } + + @Override + public int hashCode() { + return getMongoEntity()!=null ? getMongoEntity().hashCode() : super.hashCode(); + } +} 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 835d18d165..e4ca675b75 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 @@ -5,7 +5,9 @@ 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.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.RoleEntity; import org.keycloak.models.mongo.keycloak.entities.UserEntity; @@ -19,37 +21,35 @@ import java.util.Set; /** * @author Marek Posolda */ -public class ApplicationAdapter implements ApplicationModel { +public class ApplicationAdapter extends AbstractAdapter implements ApplicationModel { private final ApplicationEntity application; - private final MongoStore mongoStore; - private UserAdapter resourceUser; - public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStore mongoStore) { - this(applicationEntity, null, mongoStore); + public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + this(applicationEntity, null, mongoStore, invContext); } - public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStore mongoStore) { + public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + super(mongoStore, invContext); this.application = applicationEntity; this.resourceUser = resourceUser; - this.mongoStore = mongoStore; } @Override public void updateApplication() { - mongoStore.updateObject(application); + mongoStore.updateObject(application, invocationContext); } @Override public UserAdapter getApplicationUser() { // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object if (resourceUser == null) { - UserEntity userEntity = mongoStore.loadObject(UserEntity.class, application.getResourceUserId()); + UserEntity userEntity = mongoStore.loadObject(UserEntity.class, application.getResourceUserId(), invocationContext); if (userEntity == null) { throw new IllegalStateException("User " + application.getResourceUserId() + " not found"); } - resourceUser = new UserAdapter(userEntity, mongoStore); + resourceUser = new UserAdapter(userEntity, mongoStore, invocationContext); } return resourceUser; @@ -116,21 +116,21 @@ public class ApplicationAdapter implements ApplicationModel { .and("name").is(name) .and("applicationId").is(getId()) .get(); - RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query); + RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore); + return new RoleAdapter(role, this, mongoStore, invocationContext); } } @Override public RoleModel getRoleById(String id) { - RoleEntity role = mongoStore.loadObject(RoleEntity.class, id); + RoleEntity role = mongoStore.loadObject(RoleEntity.class, id, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore); + return new RoleAdapter(role, this, mongoStore, invocationContext); } } @@ -145,13 +145,13 @@ public class ApplicationAdapter implements ApplicationModel { roleEntity.setName(name); roleEntity.setApplicationId(getId()); - mongoStore.insertObject(roleEntity); - return new RoleAdapter(roleEntity, this, mongoStore); + mongoStore.insertObject(roleEntity, invocationContext); + return new RoleAdapter(roleEntity, this, mongoStore, invocationContext); } @Override public boolean removeRoleById(String id) { - return mongoStore.removeObject(RoleEntity.class ,id); + return mongoStore.removeObject(RoleEntity.class ,id, invocationContext); } @Override @@ -159,11 +159,11 @@ public class ApplicationAdapter implements ApplicationModel { DBObject query = new QueryBuilder() .and("applicationId").is(getId()) .get(); - List roles = mongoStore.loadObjects(RoleEntity.class, query); + List roles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); Set result = new HashSet(); for (RoleEntity role : roles) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } return result; @@ -172,11 +172,11 @@ public class ApplicationAdapter implements ApplicationModel { @Override public Set getApplicationRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore); + List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } } return result; @@ -185,17 +185,17 @@ public class ApplicationAdapter implements ApplicationModel { @Override public void addScope(RoleModel role) { UserAdapter appUser = getApplicationUser(); - mongoStore.pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true); + mongoStore.pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true, invocationContext); } @Override public Set getApplicationScopeMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore); + List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } } return result; @@ -213,7 +213,7 @@ public class ApplicationAdapter implements ApplicationModel { addRole(name); } - mongoStore.pushItemToList(application, "defaultRoles", name, true); + mongoStore.pushItemToList(application, "defaultRoles", name, true, invocationContext); } @Override @@ -232,16 +232,7 @@ public class ApplicationAdapter implements ApplicationModel { } @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(); + public AbstractMongoIdentifiableEntity getMongoEntity() { + return application; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java index 6fd1cfc3fa..e612a5430f 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java @@ -8,6 +8,9 @@ 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.api.context.MongoStoreInvocationContext; +import org.keycloak.models.mongo.impl.context.SimpleMongoStoreInvocationContext; +import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.RealmEntity; import org.keycloak.models.utils.KeycloakModelUtils; @@ -19,20 +22,25 @@ import java.util.List; */ public class MongoKeycloakSession implements KeycloakSession { - private static final MongoKeycloakTransaction PLACEHOLDER = new MongoKeycloakTransaction(); + private final MongoStoreInvocationContext invocationContext; + private final MongoKeycloakTransaction transaction; private final MongoStore mongoStore; public MongoKeycloakSession(MongoStore mongoStore) { this.mongoStore = mongoStore; + // this.invocationContext = new SimpleMongoStoreInvocationContext(mongoStore); + this.invocationContext = new TransactionMongoStoreInvocationContext(mongoStore); + this.transaction = new MongoKeycloakTransaction(invocationContext); } @Override public KeycloakTransaction getTransaction() { - return PLACEHOLDER; + return transaction; } @Override public void close() { + // TODO } @Override @@ -50,26 +58,25 @@ public class MongoKeycloakSession implements KeycloakSession { newRealm.setId(id); newRealm.setName(name); - mongoStore.insertObject(newRealm); + mongoStore.insertObject(newRealm, invocationContext); - RealmAdapter realm = new RealmAdapter(newRealm, mongoStore); - return realm; + return new RealmAdapter(newRealm, mongoStore, invocationContext); } @Override public RealmModel getRealm(String id) { - RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, id); - return realmEntity != null ? new RealmAdapter(realmEntity, mongoStore) : null; + RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, id, invocationContext); + return realmEntity != null ? new RealmAdapter(realmEntity, mongoStore, invocationContext) : null; } @Override public List getRealms(UserModel admin) { DBObject query = new BasicDBObject(); - List realms = mongoStore.loadObjects(RealmEntity.class, query); + List realms = mongoStore.loadObjects(RealmEntity.class, query, invocationContext); List results = new ArrayList(); for (RealmEntity realmEntity : realms) { - results.add(new RealmAdapter(realmEntity, mongoStore)); + results.add(new RealmAdapter(realmEntity, mongoStore, invocationContext)); } return results; } @@ -79,14 +86,14 @@ public class MongoKeycloakSession implements KeycloakSession { DBObject query = new QueryBuilder() .and("name").is(name) .get(); - RealmEntity realm = mongoStore.loadSingleObject(RealmEntity.class, query); + RealmEntity realm = mongoStore.loadSingleObject(RealmEntity.class, query, invocationContext); if (realm == null) return null; - return new RealmAdapter(realm, mongoStore); + return new RealmAdapter(realm, mongoStore, invocationContext); } @Override public boolean removeRealm(String id) { - return mongoStore.removeObject(RealmEntity.class, id); + return mongoStore.removeObject(RealmEntity.class, id, invocationContext); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java index db2de6f982..c9d9d37634 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakTransaction.java @@ -1,39 +1,60 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.KeycloakTransaction; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** * @author Marek Posolda */ public class MongoKeycloakTransaction implements KeycloakTransaction { + private final MongoStoreInvocationContext invocationContext; + + private boolean started = false; + private boolean rollbackOnly = false; + + public MongoKeycloakTransaction(MongoStoreInvocationContext invocationContext) { + this.invocationContext = invocationContext; + } + @Override public void begin() { - //To change body of implemented methods use File | Settings | File Templates. + if (started) { + throw new IllegalStateException("Transaction already started"); + } + started = true; + invocationContext.begin(); } @Override public void commit() { - //To change body of implemented methods use File | Settings | File Templates. + if (!started) { + throw new IllegalStateException("Transaction not yet started"); + } + if (rollbackOnly) { + throw new IllegalStateException("Can't commit as transaction marked for rollback"); + } + + invocationContext.commit(); } @Override public void rollback() { - //To change body of implemented methods use File | Settings | File Templates. + invocationContext.rollback(); } @Override public void setRollbackOnly() { - //To change body of implemented methods use File | Settings | File Templates. + this.rollbackOnly = true; } @Override public boolean getRollbackOnly() { - return false; //To change body of implemented methods use File | Settings | File Templates. + return rollbackOnly; } @Override public boolean isActive() { - return true; + return started; } } 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 e4b866d5d7..3684872243 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,27 +2,28 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; import org.keycloak.models.mongo.keycloak.entities.UserEntity; /** * @author Marek Posolda */ -public class OAuthClientAdapter implements OAuthClientModel { +public class OAuthClientAdapter extends AbstractAdapter implements OAuthClientModel { private final OAuthClientEntity delegate; private UserAdapter oauthAgent; - private final MongoStore mongoStore; - public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStore mongoStore) { + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + super(mongoStore, invContext); this.delegate = oauthClientEntity; this.oauthAgent = oauthAgent; - this.mongoStore = mongoStore; } - public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStore mongoStore) { - this(oauthClientEntity, null, mongoStore); + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + this(oauthClientEntity, null, mongoStore, invContext); } @Override @@ -34,10 +35,14 @@ 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) { - UserEntity user = mongoStore.loadObject(UserEntity.class, delegate.getOauthAgentId()); - oauthAgent = user!=null ? new UserAdapter(user, mongoStore) : null; + UserEntity user = mongoStore.loadObject(UserEntity.class, delegate.getOauthAgentId(), invocationContext); + oauthAgent = user!=null ? new UserAdapter(user, mongoStore, invocationContext) : null; } return oauthAgent; } + @Override + public AbstractMongoIdentifiableEntity getMongoEntity() { + return delegate; + } } 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 48ab5774a3..c5c558cfaf 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 @@ -12,7 +12,9 @@ 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.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; @@ -30,6 +32,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -40,21 +43,20 @@ import java.util.regex.Pattern; /** * @author Marek Posolda */ -public class RealmAdapter implements RealmModel { +public class RealmAdapter extends AbstractAdapter implements RealmModel { 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; private volatile transient PasswordPolicy passwordPolicy; - public RealmAdapter(RealmEntity realmEntity, MongoStore mongoStore) { + public RealmAdapter(RealmEntity realmEntity, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { + super(mongoStore, invocationContext); this.realm = realmEntity; - this.mongoStore = mongoStore; } @Override @@ -250,18 +252,40 @@ public class RealmAdapter implements RealmModel { setPrivateKeyPem(privateKeyPem); } + @Override + public String getLoginTheme() { + return realm.getLoginTheme(); + } + + @Override + public void setLoginTheme(String name) { + realm.setLoginTheme(name); + updateRealm(); + } + + @Override + public String getAccountTheme() { + return realm.getAccountTheme(); + } + + @Override + public void setAccountTheme(String name) { + realm.setAccountTheme(name); + updateRealm(); + } + @Override public UserAdapter getUser(String name) { DBObject query = new QueryBuilder() .and("loginName").is(name) .and("realmId").is(getId()) .get(); - UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query); + UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); if (user == null) { return null; } else { - return new UserAdapter(user, mongoStore); + return new UserAdapter(user, mongoStore, invocationContext); } } @@ -271,12 +295,12 @@ public class RealmAdapter implements RealmModel { .and("email").is(email) .and("realmId").is(getId()) .get(); - UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query); + UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); if (user == null) { return null; } else { - return new UserAdapter(user, mongoStore); + return new UserAdapter(user, mongoStore, invocationContext); } } @@ -308,8 +332,8 @@ public class RealmAdapter implements RealmModel { userEntity.setEnabled(true); userEntity.setRealmId(getId()); - mongoStore.insertObject(userEntity); - return new UserAdapter(userEntity, mongoStore); + mongoStore.insertObject(userEntity, invocationContext); + return new UserAdapter(userEntity, mongoStore, invocationContext); } @Override @@ -318,7 +342,7 @@ public class RealmAdapter implements RealmModel { .and("loginName").is(name) .and("realmId").is(getId()) .get(); - return mongoStore.removeObjects(UserEntity.class, query); + return mongoStore.removeObjects(UserEntity.class, query, invocationContext); } @Override @@ -327,11 +351,11 @@ public class RealmAdapter implements RealmModel { .and("name").is(name) .and("realmId").is(getId()) .get(); - RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query); + RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore); + return new RoleAdapter(role, this, mongoStore, invocationContext); } } @@ -348,13 +372,13 @@ public class RealmAdapter implements RealmModel { roleEntity.setName(name); roleEntity.setRealmId(getId()); - mongoStore.insertObject(roleEntity); - return new RoleAdapter(roleEntity, this, mongoStore); + mongoStore.insertObject(roleEntity, invocationContext); + return new RoleAdapter(roleEntity, this, mongoStore, invocationContext); } @Override public boolean removeRoleById(String id) { - return mongoStore.removeObject(RoleEntity.class ,id); + return mongoStore.removeObject(RoleEntity.class ,id, invocationContext); } @Override @@ -362,13 +386,13 @@ public class RealmAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List roles = mongoStore.loadObjects(RoleEntity.class, query); + List roles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); Set result = new HashSet(); if (roles == null) return result; for (RoleEntity role : roles) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } return result; @@ -376,11 +400,11 @@ public class RealmAdapter implements RealmModel { @Override public RoleModel getRoleById(String id) { - RoleEntity role = mongoStore.loadObject(RoleEntity.class, id); + RoleEntity role = mongoStore.loadObject(RoleEntity.class, id, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore); + return new RoleAdapter(role, this, mongoStore, invocationContext); } } @@ -396,7 +420,7 @@ public class RealmAdapter implements RealmModel { addRole(name); } - mongoStore.pushItemToList(realm, "defaultRoles", name, true); + mongoStore.pushItemToList(realm, "defaultRoles", name, true, invocationContext); } @Override @@ -417,15 +441,14 @@ public class RealmAdapter implements RealmModel { @Override public ApplicationModel getApplicationById(String id) { - ApplicationEntity appData = mongoStore.loadObject(ApplicationEntity.class, id); + ApplicationEntity appData = mongoStore.loadObject(ApplicationEntity.class, id, invocationContext); // Check if application belongs to this realm if (appData == null || !getId().equals(appData.getRealmId())) { return null; } - ApplicationModel model = new ApplicationAdapter(appData, mongoStore); - return model; + return new ApplicationAdapter(appData, mongoStore, invocationContext); } @Override @@ -434,8 +457,8 @@ public class RealmAdapter implements RealmModel { .and("realmId").is(getId()) .and("name").is(name) .get(); - ApplicationEntity appEntity = mongoStore.loadSingleObject(ApplicationEntity.class, query); - return appEntity==null ? null : new ApplicationAdapter(appEntity, mongoStore); + ApplicationEntity appEntity = mongoStore.loadSingleObject(ApplicationEntity.class, query, invocationContext); + return appEntity==null ? null : new ApplicationAdapter(appEntity, mongoStore, invocationContext); } @Override @@ -452,11 +475,11 @@ public class RealmAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List appDatas = mongoStore.loadObjects(ApplicationEntity.class, query); + List appDatas = mongoStore.loadObjects(ApplicationEntity.class, query, invocationContext); List result = new ArrayList(); for (ApplicationEntity appData : appDatas) { - result.add(new ApplicationAdapter(appData, mongoStore)); + result.add(new ApplicationAdapter(appData, mongoStore, invocationContext)); } return result; } @@ -470,15 +493,14 @@ public class RealmAdapter implements RealmModel { appData.setRealmId(getId()); appData.setEnabled(true); appData.setResourceUserId(resourceUser.getUser().getId()); - mongoStore.insertObject(appData); + mongoStore.insertObject(appData, invocationContext); - ApplicationModel resource = new ApplicationAdapter(appData, resourceUser, mongoStore); - return resource; + return new ApplicationAdapter(appData, resourceUser, mongoStore, invocationContext); } @Override public boolean removeApplication(String id) { - return mongoStore.removeObject(ApplicationEntity.class, id); + return mongoStore.removeObject(ApplicationEntity.class, id, invocationContext); } @Override @@ -495,20 +517,20 @@ public class RealmAdapter implements RealmModel { @Override public void grantRole(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pushItemToList(userEntity, "roleIds", role.getId(), true); + mongoStore.pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext); } @Override public Set getRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore); + List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getRealmId())) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } else { // Likely applicationRole, but we don't have this application yet - result.add(new RoleAdapter(role, mongoStore)); + result.add(new RoleAdapter(role, mongoStore, invocationContext)); } } return result; @@ -533,20 +555,20 @@ public class RealmAdapter implements RealmModel { @Override public void deleteRoleMapping(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pullItemFromList(userEntity, "roleIds", role.getId()); + mongoStore.pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext); } @Override public Set getScopeMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore); + List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getRealmId())) { - result.add(new RoleAdapter(role, this, mongoStore)); + result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); } else { // Likely applicationRole, but we don't have this application yet - result.add(new RoleAdapter(role, mongoStore)); + result.add(new RoleAdapter(role, mongoStore, invocationContext)); } } return result; @@ -571,13 +593,13 @@ public class RealmAdapter implements RealmModel { @Override public void addScopeMapping(UserModel agent, RoleModel role) { UserEntity userEntity = ((UserAdapter)agent).getUser(); - mongoStore.pushItemToList(userEntity, "scopeIds", role.getId(), true); + mongoStore.pushItemToList(userEntity, "scopeIds", role.getId(), true, invocationContext); } @Override public void deleteScopeMapping(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pullItemFromList(userEntity, "scopeIds", role.getId()); + mongoStore.pullItemFromList(userEntity, "scopeIds", role.getId(), invocationContext); } @Override @@ -588,14 +610,14 @@ public class RealmAdapter implements RealmModel { oauthClient.setOauthAgentId(oauthAgent.getUser().getId()); oauthClient.setRealmId(getId()); oauthClient.setName(name); - mongoStore.insertObject(oauthClient); + mongoStore.insertObject(oauthClient, invocationContext); - return new OAuthClientAdapter(oauthClient, oauthAgent, mongoStore); + return new OAuthClientAdapter(oauthClient, oauthAgent, mongoStore, invocationContext); } @Override public boolean removeOAuthClient(String id) { - return mongoStore.removeObject(OAuthClientEntity.class, id); + return mongoStore.removeObject(OAuthClientEntity.class, id, invocationContext); } @Override @@ -606,15 +628,15 @@ public class RealmAdapter implements RealmModel { .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); + OAuthClientEntity oauthClient = mongoStore.loadSingleObject(OAuthClientEntity.class, query, invocationContext); + return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, mongoStore, invocationContext); } @Override public OAuthClientModel getOAuthClientById(String id) { - OAuthClientEntity clientEntity = mongoStore.loadObject(OAuthClientEntity.class, id); + OAuthClientEntity clientEntity = mongoStore.loadObject(OAuthClientEntity.class, id, invocationContext); if (clientEntity == null) return null; - return new OAuthClientAdapter(clientEntity, mongoStore); + return new OAuthClientAdapter(clientEntity, mongoStore, invocationContext); } @Override @@ -622,10 +644,10 @@ public class RealmAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List results = mongoStore.loadObjects(OAuthClientEntity.class, query); + List results = mongoStore.loadObjects(OAuthClientEntity.class, query, invocationContext); List list = new ArrayList(); for (OAuthClientEntity data : results) { - list.add(new OAuthClientAdapter(data, mongoStore)); + list.add(new OAuthClientAdapter(data, mongoStore, invocationContext)); } return list; } @@ -686,7 +708,7 @@ public class RealmAdapter implements RealmModel { } } for (RequiredCredentialEntity entity : toRemove) { - creds.remove(entity); + credsEntities.remove(entity); } for (String cred : creds) { logger.info("updating cred: " + cred); @@ -773,39 +795,31 @@ public class RealmAdapter implements RealmModel { } credentialEntity.setDevice(cred.getDevice()); - mongoStore.updateObject(userEntity); + mongoStore.updateObject(userEntity, invocationContext); } @Override public UserModel getUserBySocialLink(SocialLinkModel socialLink) { DBObject query = new QueryBuilder() - .and("socialProvider").is(socialLink.getSocialProvider()) - .and("socialUsername").is(socialLink.getSocialUsername()) + .and("socialLinks.socialProvider").is(socialLink.getSocialProvider()) + .and("socialLinks.socialUsername").is(socialLink.getSocialUsername()) .and("realmId").is(getId()) .get(); - SocialLinkEntity socialLinkEntity = mongoStore.loadSingleObject(SocialLinkEntity.class, query); - - if (socialLinkEntity == null) { - return null; - } else { - 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); - } + UserEntity userEntity = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); + return userEntity==null ? null : new UserAdapter(userEntity, mongoStore, invocationContext); } @Override public Set getSocialLinks(UserModel user) { UserEntity userEntity = ((UserAdapter)user).getUser(); - String userId = userEntity.getId(); + List linkEntities = userEntity.getSocialLinks(); - DBObject query = new QueryBuilder() - .and("userId").is(userId) - .get(); - List dbSocialLinks = mongoStore.loadObjects(SocialLinkEntity.class, query); + if (linkEntities == null) { + return Collections.EMPTY_SET; + } Set result = new HashSet(); - for (SocialLinkEntity socialLinkEntity : dbSocialLinks) { + for (SocialLinkEntity socialLinkEntity : linkEntities) { SocialLinkModel model = new SocialLinkModel(socialLinkEntity.getSocialProvider(), socialLinkEntity.getSocialUsername()); result.add(model); } @@ -818,26 +832,22 @@ public class RealmAdapter implements RealmModel { SocialLinkEntity socialLinkEntity = new SocialLinkEntity(); socialLinkEntity.setSocialProvider(socialLink.getSocialProvider()); socialLinkEntity.setSocialUsername(socialLink.getSocialUsername()); - socialLinkEntity.setUserId(userEntity.getId()); - socialLinkEntity.setRealmId(getId()); - mongoStore.insertObject(socialLinkEntity); + mongoStore.pushItemToList(userEntity, "socialLinks", socialLinkEntity, true, invocationContext); } @Override public void removeSocialLink(UserModel user, SocialLinkModel socialLink) { + SocialLinkEntity socialLinkEntity = new SocialLinkEntity(); + socialLinkEntity.setSocialProvider(socialLink.getSocialProvider()); + socialLinkEntity.setSocialUsername(socialLink.getSocialUsername()); + 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); + mongoStore.pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext); } protected void updateRealm() { - mongoStore.updateObject(realm); + mongoStore.updateObject(realm, invocationContext); } protected RequiredCredentialModel initRequiredCredentialModel(String type) { @@ -853,7 +863,7 @@ public class RealmAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List users = mongoStore.loadObjects(UserEntity.class, query); + List users = mongoStore.loadObjects(UserEntity.class, query, invocationContext); return convertUserEntities(users); } @@ -893,7 +903,7 @@ public class RealmAdapter implements RealmModel { ).get() ); - List users = mongoStore.loadObjects(UserEntity.class, builder.get()); + List users = mongoStore.loadObjects(UserEntity.class, builder.get(), invocationContext); return convertUserEntities(users); } @@ -915,14 +925,14 @@ public class RealmAdapter implements RealmModel { queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } } - List users = mongoStore.loadObjects(UserEntity.class, queryBuilder.get()); + List users = mongoStore.loadObjects(UserEntity.class, queryBuilder.get(), invocationContext); return convertUserEntities(users); } protected List convertUserEntities(List userEntities) { List userModels = new ArrayList(); for (UserEntity user : userEntities) { - userModels.add(new UserAdapter(user, mongoStore)); + userModels.add(new UserAdapter(user, mongoStore, invocationContext)); } return userModels; } @@ -950,15 +960,7 @@ public class RealmAdapter implements RealmModel { } @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(); + public AbstractMongoIdentifiableEntity getMongoEntity() { + return realm; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java index 21bdc9ef6b..b6be681a24 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 @@ -9,7 +9,9 @@ import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.RealmEntity; import org.keycloak.models.mongo.keycloak.entities.RoleEntity; @@ -21,17 +23,17 @@ import org.keycloak.models.utils.KeycloakModelUtils; * * @author Marek Posolda */ -public class RoleAdapter implements RoleModel { +public class RoleAdapter extends AbstractAdapter implements RoleModel { private final RoleEntity role; private RoleContainerModel roleContainer; - private final MongoStore mongoStore; - public RoleAdapter(RoleEntity roleEntity, MongoStore mongoStore) { - this(roleEntity, null, mongoStore); + public RoleAdapter(RoleEntity roleEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + this(roleEntity, null, mongoStore, invContext); } - public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStore mongoStore) { + public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + super(mongoStore, invContext); this.role = roleEntity; this.roleContainer = roleContainer; this.mongoStore = mongoStore; @@ -66,27 +68,21 @@ public class RoleAdapter implements RoleModel { @Override public boolean isComposite() { - return role.isComposite(); - } - - @Override - public void setComposite(boolean flag) { - role.setComposite(flag); - updateRole(); + return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0; } protected void updateRole() { - mongoStore.updateObject(role); + mongoStore.updateObject(role, invocationContext); } @Override public void addCompositeRole(RoleModel childRole) { - mongoStore.pushItemToList(role, "compositeRoleIds", childRole.getId(), true); + mongoStore.pushItemToList(role, "compositeRoleIds", childRole.getId(), true, invocationContext); } @Override public void removeCompositeRole(RoleModel childRole) { - mongoStore.pullItemFromList(role, "compositeRoleIds", childRole.getId()); + mongoStore.pullItemFromList(role, "compositeRoleIds", childRole.getId(), invocationContext); } @Override @@ -98,11 +94,11 @@ public class RoleAdapter implements RoleModel { DBObject query = new QueryBuilder() .and("_id").in(MongoModelUtils.convertStringsToObjectIds(role.getCompositeRoleIds())) .get(); - List childRoles = mongoStore.loadObjects(RoleEntity.class, query); + List childRoles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); Set set = new HashSet(); for (RoleEntity childRole : childRoles) { - set.add(new RoleAdapter(childRole, roleContainer, mongoStore)); + set.add(new RoleAdapter(childRole, mongoStore, invocationContext)); } return set; } @@ -112,17 +108,17 @@ public class RoleAdapter implements RoleModel { if (roleContainer == null) { // Compute it if (role.getRealmId() != null) { - RealmEntity realm = mongoStore.loadObject(RealmEntity.class, role.getRealmId()); + RealmEntity realm = mongoStore.loadObject(RealmEntity.class, role.getRealmId(), invocationContext); if (realm == null) { throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists"); } - roleContainer = new RealmAdapter(realm, mongoStore); + roleContainer = new RealmAdapter(realm, mongoStore, invocationContext); } else if (role.getApplicationId() != null) { - ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, role.getApplicationId()); + ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, role.getApplicationId(), invocationContext); if (appEntity == null) { throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists"); } - roleContainer = new ApplicationAdapter(appEntity, mongoStore); + roleContainer = new ApplicationAdapter(appEntity, mongoStore, invocationContext); } else { throw new IllegalStateException("Both realmId and applicationId are null for role: " + this); } @@ -144,19 +140,7 @@ public class RoleAdapter implements RoleModel { } @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(); + public AbstractMongoIdentifiableEntity getMongoEntity() { + return role; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index d3fe02f46a..c66fefaa3c 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,8 +1,9 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; -import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.UserEntity; import java.util.ArrayList; @@ -18,14 +19,13 @@ import java.util.Set; * * @author Marek Posolda */ -public class UserAdapter implements UserModel { +public class UserAdapter extends AbstractAdapter implements UserModel { private final UserEntity user; - private final MongoStore mongoStore; - public UserAdapter(UserEntity userEntity, MongoStore mongoStore) { + public UserAdapter(UserEntity userEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + super(mongoStore, invContext); this.user = userEntity; - this.mongoStore = mongoStore; } @Override @@ -139,12 +139,12 @@ public class UserAdapter implements UserModel { @Override public void addWebOrigin(String webOrigin) { - mongoStore.pushItemToList(user, "webOrigins", webOrigin, true); + mongoStore.pushItemToList(user, "webOrigins", webOrigin, true, invocationContext); } @Override public void removeWebOrigin(String webOrigin) { - mongoStore.pullItemFromList(user, "webOrigins", webOrigin); + mongoStore.pullItemFromList(user, "webOrigins", webOrigin, invocationContext); } @Override @@ -166,12 +166,12 @@ public class UserAdapter implements UserModel { @Override public void addRedirectUri(String redirectUri) { - mongoStore.pushItemToList(user, "redirectUris", redirectUri, true); + mongoStore.pushItemToList(user, "redirectUris", redirectUri, true, invocationContext); } @Override public void removeRedirectUri(String redirectUri) { - mongoStore.pullItemFromList(user, "redirectUris", redirectUri); + mongoStore.pullItemFromList(user, "redirectUris", redirectUri, invocationContext); } @Override @@ -185,12 +185,12 @@ public class UserAdapter implements UserModel { @Override public void addRequiredAction(RequiredAction action) { - mongoStore.pushItemToList(user, "requiredActions", action, true); + mongoStore.pushItemToList(user, "requiredActions", action, true, invocationContext); } @Override public void removeRequiredAction(RequiredAction action) { - mongoStore.pullItemFromList(user, "requiredActions", action); + mongoStore.pullItemFromList(user, "requiredActions", action, invocationContext); } @Override @@ -205,6 +205,11 @@ public class UserAdapter implements UserModel { } protected void updateUser() { - mongoStore.updateObject(user); + mongoStore.updateObject(user, invocationContext); + } + + @Override + public AbstractMongoIdentifiableEntity getMongoEntity() { + return user; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java index 5791353c98..17805819de 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java @@ -5,19 +5,19 @@ import java.util.List; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoId; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** * @author Marek Posolda */ @MongoCollection(collectionName = "applications") -public class ApplicationEntity implements MongoEntity { +public class ApplicationEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { - private String id; private String name; private boolean enabled; private boolean surrogateAuthRequired; @@ -30,15 +30,6 @@ public class ApplicationEntity implements MongoEntity { // We are using names of defaultRoles (not ids) private List defaultRoles = new ArrayList(); - @MongoId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - @MongoField public String getName() { return name; @@ -112,14 +103,14 @@ public class ApplicationEntity implements MongoEntity { } @Override - public void afterRemove(MongoStore mongoStore) { + public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { // Remove resourceUser of this application - mongoStore.removeObject(UserEntity.class, resourceUserId); + mongoStore.removeObject(UserEntity.class, resourceUserId, invContext); // Remove all roles, which belongs to this application DBObject query = new QueryBuilder() - .and("applicationId").is(id) + .and("applicationId").is(getId()) .get(); - mongoStore.removeObjects(RoleEntity.class, query); + mongoStore.removeObjects(RoleEntity.class, query, invContext); } } 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 index f29eadcbae..6ab322da22 100644 --- 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 @@ -1,12 +1,12 @@ package org.keycloak.models.mongo.keycloak.entities; -import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; /** * @author Marek Posolda */ -public class CredentialEntity extends AbstractMongoEntity { +public class CredentialEntity implements MongoEntity { private String type; private String value; 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 index a2a902d7c4..52dfeceb91 100644 --- 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 @@ -1,32 +1,23 @@ package org.keycloak.models.mongo.keycloak.entities; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoId; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** * @author Marek Posolda */ @MongoCollection(collectionName = "oauthClients") -public class OAuthClientEntity implements MongoEntity { +public class OAuthClientEntity extends AbstractMongoIdentifiableEntity 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; @@ -55,8 +46,8 @@ public class OAuthClientEntity implements MongoEntity { } @Override - public void afterRemove(MongoStore mongoStore) { + public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { // Remove user of this oauthClient - mongoStore.removeObject(UserEntity.class, oauthAgentId); + mongoStore.removeObject(UserEntity.class, oauthAgentId, invContext); } } 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 index abb2041cf1..34652c7695 100755 --- 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 @@ -2,11 +2,12 @@ package org.keycloak.models.mongo.keycloak.entities; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoId; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import java.util.ArrayList; import java.util.HashMap; @@ -17,9 +18,7 @@ import java.util.Map; * @author Marek Posolda */ @MongoCollection(collectionName = "realms") -public class RealmEntity implements MongoEntity { - - private String id; +public class RealmEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { private String name; private boolean enabled; @@ -38,6 +37,9 @@ public class RealmEntity implements MongoEntity { private String publicKeyPem; private String privateKeyPem; + private String loginTheme; + private String accountTheme; + // We are using names of defaultRoles (not ids) private List defaultRoles = new ArrayList(); @@ -48,15 +50,6 @@ public class RealmEntity implements MongoEntity { 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; @@ -183,6 +176,24 @@ public class RealmEntity implements MongoEntity { this.privateKeyPem = privateKeyPem; } + @MongoField + public String getLoginTheme() { + return loginTheme; + } + + public void setLoginTheme(String loginTheme) { + this.loginTheme = loginTheme; + } + + @MongoField + public String getAccountTheme() { + return accountTheme; + } + + public void setAccountTheme(String accountTheme) { + this.accountTheme = accountTheme; + } + @MongoField public List getDefaultRoles() { return defaultRoles; @@ -238,18 +249,18 @@ public class RealmEntity implements MongoEntity { } @Override - public void afterRemove(MongoStore mongoStore) { + public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { DBObject query = new QueryBuilder() - .and("realmId").is(id) + .and("realmId").is(getId()) .get(); // Remove all users of this realm - mongoStore.removeObjects(UserEntity.class, query); + mongoStore.removeObjects(UserEntity.class, query, invContext); // Remove all roles of this realm - mongoStore.removeObjects(RoleEntity.class, query); + mongoStore.removeObjects(RoleEntity.class, query, invContext); // Remove all applications of this realm - mongoStore.removeObjects(ApplicationEntity.class, query); + mongoStore.removeObjects(ApplicationEntity.class, query, invContext); } } 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 index f36f438e19..f39e327f43 100644 --- 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 @@ -1,12 +1,12 @@ package org.keycloak.models.mongo.keycloak.entities; -import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; /** * @author Marek Posolda */ -public class RequiredCredentialEntity extends AbstractMongoEntity { +public class RequiredCredentialEntity implements MongoEntity { private String type; private boolean input; 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 index 2c367c68fc..1aaeac2a1c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java @@ -3,11 +3,12 @@ package org.keycloak.models.mongo.keycloak.entities; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.jboss.logging.Logger; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.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.context.MongoStoreInvocationContext; import java.util.List; @@ -15,29 +16,18 @@ import java.util.List; * @author Marek Posolda */ @MongoCollection(collectionName = "roles") -public class RoleEntity implements MongoEntity { +public class RoleEntity extends AbstractMongoIdentifiableEntity 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; @@ -56,15 +46,6 @@ public class RoleEntity implements MongoEntity { this.description = description; } - @MongoField - public boolean isComposite() { - return composite; - } - - public void setComposite(boolean composite) { - this.composite = composite; - } - @MongoField public List getCompositeRoleIds() { return compositeRoleIds; @@ -93,46 +74,46 @@ public class RoleEntity implements MongoEntity { } @Override - public void afterRemove(MongoStore mongoStore) { + public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { // Remove this role from all users, which has it DBObject query = new QueryBuilder() - .and("roleIds").is(id) + .and("roleIds").is(getId()) .get(); - List users = mongoStore.loadObjects(UserEntity.class, query); + List users = mongoStore.loadObjects(UserEntity.class, query, invContext); for (UserEntity user : users) { logger.info("Removing role " + getName() + " from user " + user.getLoginName()); - mongoStore.pullItemFromList(user, "roleIds", getId()); + mongoStore.pullItemFromList(user, "roleIds", getId(), invContext); } // Remove this scope from all users, which has it query = new QueryBuilder() - .and("scopeIds").is(id) + .and("scopeIds").is(getId()) .get(); - users = mongoStore.loadObjects(UserEntity.class, query); + users = mongoStore.loadObjects(UserEntity.class, query, invContext); for (UserEntity user : users) { logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); - mongoStore.pullItemFromList(user, "scopeIds", getId()); + mongoStore.pullItemFromList(user, "scopeIds", getId(), invContext); } // Remove defaultRoles from realm if (realmId != null) { - RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, realmId); + RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, realmId, invContext); // Realm might be already removed at this point if (realmEntity != null) { - mongoStore.pullItemFromList(realmEntity, "defaultRoles", getId()); + mongoStore.pullItemFromList(realmEntity, "defaultRoles", getId(), invContext); } } // Remove defaultRoles from application if (applicationId != null) { - ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, applicationId); + ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, applicationId, invContext); // Application might be already removed at this point if (appEntity != null) { - mongoStore.pullItemFromList(appEntity, "defaultRoles", getId()); + mongoStore.pullItemFromList(appEntity, "defaultRoles", getId(), invContext); } } @@ -140,9 +121,9 @@ public class RoleEntity implements MongoEntity { query = new QueryBuilder() .and("compositeRoleIds").is(getId()) .get(); - List parentRoles = mongoStore.loadObjects(RoleEntity.class, query); + List parentRoles = mongoStore.loadObjects(RoleEntity.class, query, invContext); for (RoleEntity role : parentRoles) { - mongoStore.pullItemFromList(role, "compositeRoleIds", getId()); + mongoStore.pullItemFromList(role, "compositeRoleIds", getId(), invContext); } } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java index 3a34a25f2a..85ae5c0b7b 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/SocialLinkEntity.java @@ -1,21 +1,15 @@ package org.keycloak.models.mongo.keycloak.entities; -import org.keycloak.models.mongo.api.AbstractMongoEntity; -import org.keycloak.models.mongo.api.MongoCollection; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; /** * @author Marek Posolda */ -@MongoCollection(collectionName = "socialLinks") -public class SocialLinkEntity extends AbstractMongoEntity { +public class SocialLinkEntity implements MongoEntity { private String socialUsername; private String socialProvider; - private String userId; - // realmId is needed to allow searching as combination socialUsername+socialProvider may not be unique - // (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2") - private String realmId; @MongoField public String getSocialUsername() { @@ -35,21 +29,30 @@ public class SocialLinkEntity extends AbstractMongoEntity { this.socialProvider = socialProvider; } - @MongoField - public String getUserId() { - return userId; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SocialLinkEntity that = (SocialLinkEntity) o; + + if (socialProvider != null && (that.socialProvider == null || !socialProvider.equals(that.socialProvider))) return false; + if (socialUsername != null && (that.socialUsername == null || !socialUsername.equals(that.socialUsername))) return false; + if (socialProvider == null && that.socialProvider != null)return false; + if (socialUsername == null && that.socialUsername != null) return false; + + return true; } - public void setUserId(String userId) { - this.userId = userId; - } - - @MongoField - public String getRealmId() { - return realmId; - } - - public void setRealmId(String realmId) { - this.realmId = realmId; + @Override + public int hashCode() { + int code = 1; + if (socialUsername != null) { + code = code * 13; + } + if (socialProvider != null) { + code = code * 17; + } + return code; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java index fc90f3b150..fdd69a2141 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java @@ -1,16 +1,12 @@ package org.keycloak.models.mongo.keycloak.entities; -import com.mongodb.DBObject; -import com.mongodb.QueryBuilder; import org.keycloak.models.UserModel; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoId; -import org.keycloak.models.mongo.api.MongoStore; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,9 +14,8 @@ import java.util.Map; * @author Marek Posolda */ @MongoCollection(collectionName = "users") -public class UserEntity implements MongoEntity { +public class UserEntity extends AbstractMongoIdentifiableEntity implements MongoEntity { - private String id; private String loginName; private String firstName; private String lastName; @@ -39,15 +34,7 @@ public class UserEntity implements MongoEntity { private List redirectUris; private List requiredActions; private List credentials = new ArrayList(); - - @MongoId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } + private List socialLinks; @MongoField public String getLoginName() { @@ -184,13 +171,12 @@ public class UserEntity implements MongoEntity { this.credentials = credentials; } - @Override - public void afterRemove(MongoStore mongoStore) { - DBObject query = new QueryBuilder() - .and("userId").is(id) - .get(); + @MongoField + public List getSocialLinks() { + return socialLinks; + } - // Remove social links of this user - mongoStore.removeObjects(SocialLinkEntity.class, query); + public void setSocialLinks(List socialLinks) { + this.socialLinks = socialLinks; } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java index 260d182cde..fe4387fc9a 100644 --- 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 @@ -10,6 +10,7 @@ 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.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.adapters.UserAdapter; import org.keycloak.models.mongo.keycloak.entities.RoleEntity; import org.keycloak.models.mongo.keycloak.entities.UserEntity; @@ -28,7 +29,7 @@ public class MongoModelUtils { } // Get everything including both application and realm roles - public static List getAllRolesOfUser(UserModel user, MongoStore mongoStore) { + public static List getAllRolesOfUser(UserModel user, MongoStore mongoStore, MongoStoreInvocationContext invContext) { UserEntity userEntity = ((UserAdapter)user).getUser(); List roleIds = userEntity.getRoleIds(); @@ -39,11 +40,11 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(convertStringsToObjectIds(roleIds)) .get(); - return mongoStore.loadObjects(RoleEntity.class, query); + return mongoStore.loadObjects(RoleEntity.class, query, invContext); } // Get everything including both application and realm scopes - public static List getAllScopesOfUser(UserModel user, MongoStore mongoStore) { + public static List getAllScopesOfUser(UserModel user, MongoStore mongoStore, MongoStoreInvocationContext invContext) { UserEntity userEntity = ((UserAdapter)user).getUser(); List scopeIds = userEntity.getScopeIds(); @@ -54,6 +55,6 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(convertStringsToObjectIds(scopeIds)) .get(); - return mongoStore.loadObjects(RoleEntity.class, query); + return mongoStore.loadObjects(RoleEntity.class, query, invContext); } } diff --git a/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java b/model/mongo/src/test/java/org/keycloak/models/mongo/test/Address.java index d68ce0d23d..81bd7d8dce 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,6 +1,6 @@ package org.keycloak.models.mongo.test; -import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; import java.util.List; @@ -8,7 +8,7 @@ import java.util.List; /** * @author Marek Posolda */ -public class Address extends AbstractMongoEntity { +public class Address implements MongoEntity { private String street; private int number; 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 88f1745dfa..b2d093de1b 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 @@ -10,7 +10,10 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoStore; +import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.impl.MongoStoreImpl; +import org.keycloak.models.mongo.impl.context.SimpleMongoStoreInvocationContext; +import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext; import java.net.UnknownHostException; import java.util.ArrayList; @@ -28,7 +31,7 @@ public class MongoDBModelTest { }; private MongoClient mongoClient; - private MongoStore mongoDB; + private MongoStore mongoStore; @Before public void before() throws Exception { @@ -37,7 +40,7 @@ public class MongoDBModelTest { mongoClient = new MongoClient("localhost", 27017); DB db = mongoClient.getDB("keycloakTest"); - mongoDB = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES); + mongoStore = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES); } catch (UnknownHostException e) { throw new RuntimeException(e); @@ -51,23 +54,25 @@ public class MongoDBModelTest { @Test public void mongoModelTest() throws Exception { + MongoStoreInvocationContext context = new TransactionMongoStoreInvocationContext(mongoStore); + // Add some user Person john = new Person(); john.setFirstName("john"); john.setAge(25); john.setGender(Person.Gender.MALE); - mongoDB.insertObject(john); + mongoStore.insertObject(john, context); // Add another user Person mary = new Person(); mary.setFirstName("mary"); - mary.setKids(Arrays.asList("Peter", "Paul", "Wendy")); + mary.setKids(asList("Peter", "Paul", "Wendy")); Address addr1 = new Address(); addr1.setStreet("Elm"); addr1.setNumber(5); - addr1.setFlatNumbers(Arrays.asList("flat1", "flat2")); + addr1.setFlatNumbers(asList("flat1", "flat2")); Address addr2 = new Address(); List
addresses = new ArrayList
(); addresses.add(addr1); @@ -76,14 +81,14 @@ public class MongoDBModelTest { mary.setAddresses(addresses); mary.setMainAddress(addr1); mary.setGender(Person.Gender.FEMALE); - mary.setGenders(Arrays.asList(Person.Gender.FEMALE)); + mary.setGenders(asList(Person.Gender.FEMALE)); - mongoDB.insertObject(mary); + mongoStore.insertObject(mary, context); - Assert.assertEquals(2, mongoDB.loadObjects(Person.class, new QueryBuilder().get()).size()); + Assert.assertEquals(2, mongoStore.loadObjects(Person.class, new QueryBuilder().get(), context).size()); DBObject query = new QueryBuilder().and("addresses.flatNumbers").is("flat1").get(); - List persons = mongoDB.loadObjects(Person.class, query); + List persons = mongoStore.loadObjects(Person.class, query, context); Assert.assertEquals(1, persons.size()); mary = persons.get(0); Assert.assertEquals(mary.getFirstName(), "mary"); @@ -92,15 +97,15 @@ public class MongoDBModelTest { Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass()); // Test push/pull - mongoDB.pushItemToList(mary, "kids", "Pauline", true); - mongoDB.pullItemFromList(mary, "kids", "Paul"); + mongoStore.pushItemToList(mary, "kids", "Pauline", true, context); + mongoStore.pullItemFromList(mary, "kids", "Paul", context); Address addr3 = new Address(); addr3.setNumber(6); addr3.setStreet("Broadway"); - mongoDB.pushItemToList(mary, "addresses", addr3, true); + mongoStore.pushItemToList(mary, "addresses", addr3, true, context); - mary = mongoDB.loadObject(Person.class, mary.getId()); + mary = mongoStore.loadObject(Person.class, mary.getId(), context); Assert.assertEquals(3, mary.getKids().size()); Assert.assertTrue(mary.getKids().contains("Pauline")); Assert.assertFalse(mary.getKids().contains("Paul")); @@ -116,18 +121,26 @@ public class MongoDBModelTest { mary.addAttribute("attr1", "value1"); mary.addAttribute("attr2", "value2"); mary.addAttribute("attr.some3", "value3"); - mongoDB.updateObject(mary); + mongoStore.updateObject(mary, context); - mary = mongoDB.loadObject(Person.class, mary.getId()); + mary = mongoStore.loadObject(Person.class, mary.getId(), context); Assert.assertEquals(3, mary.getAttributes().size()); mary.removeAttribute("attr2"); mary.removeAttribute("nonExisting"); - mongoDB.updateObject(mary); + mongoStore.updateObject(mary, context); - mary = mongoDB.loadObject(Person.class, mary.getId()); + mary = mongoStore.loadObject(Person.class, mary.getId(), context); Assert.assertEquals(2, mary.getAttributes().size()); Assert.assertEquals("value1", mary.getAttributes().get("attr1")); Assert.assertEquals("value3", mary.getAttributes().get("attr.some3")); + + context.commit(); + } + + private List asList(T... objects) { + List list = new ArrayList(); + list.addAll(Arrays.asList(objects)); + return list; } } 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 7298c6e5ad..47ac69bdd9 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,9 +1,8 @@ package org.keycloak.models.mongo.test; -import org.keycloak.models.mongo.api.AbstractMongoEntity; +import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoId; import java.util.HashMap; import java.util.List; @@ -13,9 +12,8 @@ import java.util.Map; * @author Marek Posolda */ @MongoCollection(collectionName = "persons") -public class Person extends AbstractMongoEntity { +public class Person extends AbstractMongoIdentifiableEntity { - private String id; private String firstName; private int age; private List kids; @@ -25,16 +23,6 @@ public class Person extends AbstractMongoEntity { private List genders; private Map attributes = new HashMap(); - - @MongoId - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - @MongoField public String getFirstName() { return firstName; diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 60ee3b8cd1..bc937d7af9 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -11,6 +11,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.resources.AccountService; @@ -41,7 +42,7 @@ public class AuthenticationManager { public SkeletonKeyToken createIdentityToken(RealmModel realm, String username) { SkeletonKeyToken token = new SkeletonKeyToken(); - token.id(RealmManager.generateId()); + token.id(KeycloakModelUtils.generateId()); token.issuedNow(); token.principal(username); token.audience(realm.getName()); diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 461159983a..b9c0e64bf6 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -13,6 +13,7 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.OAuthClientRepresentation; @@ -44,11 +45,6 @@ import java.util.concurrent.atomic.AtomicLong; */ public class RealmManager { protected static final Logger logger = Logger.getLogger(RealmManager.class); - private static AtomicLong counter = new AtomicLong(1); - - public static String generateId() { - return counter.getAndIncrement() + "-" + System.currentTimeMillis(); - } protected KeycloakSession identitySession; @@ -73,7 +69,7 @@ public class RealmManager { } public RealmModel createRealm(String id, String name) { - if (id == null) id = generateId(); + if (id == null) id = KeycloakModelUtils.generateId(); RealmModel realm = identitySession.createRealm(id, name); realm.setName(name); realm.addRole(Constants.APPLICATION_ROLE); @@ -166,7 +162,7 @@ public class RealmManager { public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) { String id = rep.getId(); if (id == null) { - id = generateId(); + id = KeycloakModelUtils.generateId(); } RealmModel realm = createRealm(id, rep.getRealm()); importRealm(rep, realm); diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java index 87d4025feb..a7eeb86102 100755 --- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java +++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java @@ -3,9 +3,11 @@ package org.keycloak.services.managers; import org.jboss.resteasy.logging.Logger; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.models.ApplicationModel; +import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.SkeletonKeyScope; import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.util.Base64Url; @@ -14,6 +16,7 @@ import org.keycloak.util.JsonSerialization; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -132,7 +135,7 @@ public class TokenManager { protected SkeletonKeyToken initToken(RealmModel realm, UserModel client, UserModel user) { SkeletonKeyToken token = new SkeletonKeyToken(); - token.id(RealmManager.generateId()); + token.id(KeycloakModelUtils.generateId()); token.principal(user.getLoginName()); token.audience(realm.getName()); token.issuedNow(); @@ -219,7 +222,7 @@ public class TokenManager { public SkeletonKeyToken createAccessToken(RealmModel realm, UserModel user) { SkeletonKeyToken token = new SkeletonKeyToken(); - token.id(RealmManager.generateId()); + token.id(KeycloakModelUtils.generateId()); token.issuedNow(); token.principal(user.getLoginName()); token.audience(realm.getName()); 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 404e8f2c71..0a9197bd0f 100755 --- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java +++ b/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java @@ -10,7 +10,7 @@ import org.keycloak.models.UserModel.RequiredAction; 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.AbstractKeycloakTest; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; diff --git a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java similarity index 95% rename from services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java rename to services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java index 63c411dee9..d33ea17305 100755 --- a/services/src/test/java/org/keycloak/test/common/AbstractKeycloakTest.java +++ b/services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java @@ -1,10 +1,8 @@ -package org.keycloak.test.common; +package org.keycloak.test; import org.jboss.resteasy.logging.Logger; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.keycloak.models.KeycloakSession; @@ -17,7 +15,6 @@ 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 diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java index 1e459146c6..e729554d03 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/services/src/test/java/org/keycloak/test/AdapterTest.java @@ -17,7 +17,6 @@ import org.keycloak.representations.idm.CredentialRepresentation; 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 java.util.ArrayList; import java.util.Arrays; diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java index 8c3c3f23bc..783d9679ed 100755 --- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java +++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java @@ -9,7 +9,6 @@ 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.test.common.AbstractKeycloakTest; import java.util.Iterator; import java.util.List; diff --git a/services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java b/services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java new file mode 100644 index 0000000000..898a183e29 --- /dev/null +++ b/services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java @@ -0,0 +1,105 @@ +package org.keycloak.test; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; + +/** + * @author Marek Posolda + */ +public class CompositeRolesModelTest extends AbstractKeycloakTest { + + public CompositeRolesModelTest(String providerId) { + super(providerId); + } + + @Before + public void before() throws Exception { + super.before(); + RealmManager manager = realmManager; + RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testcomposites.json"); + RealmModel realm = manager.createRealm("Test", rep.getRealm()); + manager.importRealm(rep, realm); + } + + @Test + public void testAppComposites() { + Set requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_USER"); + Assert.assertEquals(2, requestedRoles.size()); + + RoleModel expectedRole1 = getRole("APP_ROLE_APPLICATION", "APP_ROLE_1"); + RoleModel expectedRole2 = getRole("realm", "REALM_ROLE_1"); + + assertContains(requestedRoles, expectedRole1); + assertContains(requestedRoles, expectedRole2); + } + + // TODO: more tests... + + // Same algorithm as in TokenManager.createAccessCode + private Set getRequestedRoles(String applicationName, String username) { + Set requestedRoles = new HashSet(); + + RealmModel realm = realmManager.getRealm("Test"); + UserModel user = realm.getUser(username); + ApplicationModel application = realm.getApplicationByName(applicationName); + + Set roleMappings = realm.getRoleMappings(user); + Set scopeMappings = realm.getScopeMappings(application.getApplicationUser()); + Set appRoles = application.getRoles(); + if (appRoles != null) scopeMappings.addAll(appRoles); + + for (RoleModel role : roleMappings) { + if (role.getContainer().equals(application)) requestedRoles.add(role); + for (RoleModel desiredRole : scopeMappings) { + Set visited = new HashSet(); + applyScope(role, desiredRole, visited, requestedRoles); + } + } + + return requestedRoles; + } + + private static void applyScope(RoleModel role, RoleModel scope, Set visited, Set requested) { + if (visited.contains(scope)) return; + visited.add(scope); + if (role.hasRole(scope)) { + requested.add(scope); + return; + } + if (!scope.isComposite()) return; + + for (RoleModel contained : scope.getComposites()) { + applyScope(role, contained, visited, requested); + } + } + + private RoleModel getRole(String appName, String roleName) { + RealmModel realm = realmManager.getRealm("Test"); + if ("realm".equals(appName)) { + return realm.getRole(roleName); + } else { + return realm.getApplicationByName(appName).getRole(roleName); + } + } + + private void assertContains(Set requestedRoles, RoleModel expectedRole) { + Assert.assertTrue(requestedRoles.contains(expectedRole)); + + // Check if requestedRole has correct role container + for (RoleModel role : requestedRoles) { + if (role.equals(expectedRole)) { + Assert.assertEquals(role.getContainer(), expectedRole.getContainer()); + } + } + } +} diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/services/src/test/java/org/keycloak/test/ImportTest.java index 8b7287cae6..d8977ff037 100755 --- a/services/src/test/java/org/keycloak/test/ImportTest.java +++ b/services/src/test/java/org/keycloak/test/ImportTest.java @@ -5,6 +5,7 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.models.ApplicationModel; +import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; @@ -12,9 +13,9 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; -import org.keycloak.test.common.AbstractKeycloakTest; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -56,14 +57,68 @@ public class ImportTest extends AbstractKeycloakTest { List resources = realm.getApplications(); Assert.assertEquals(3, resources.size()); - // Test scope relationship - ApplicationModel application = realm.getApplicationNameMap().get("Application"); - UserModel oauthClient = realm.getUser("oauthclient"); + // Test applications imported + ApplicationModel application = realm.getApplicationByName("Application"); + ApplicationModel otherApp = realm.getApplicationByName("OtherApp"); + ApplicationModel accountApp = realm.getApplicationByName(Constants.ACCOUNT_APPLICATION); + ApplicationModel nonExisting = realm.getApplicationByName("NonExisting"); Assert.assertNotNull(application); + Assert.assertNotNull(otherApp); + Assert.assertNull(nonExisting); + Map apps = realm.getApplicationNameMap(); + Assert.assertEquals(3, apps.size()); + Assert.assertTrue(apps.values().contains(application)); + Assert.assertTrue(apps.values().contains(otherApp)); + Assert.assertTrue(apps.values().contains(accountApp)); + realm.getApplications().containsAll(apps.values()); + + // Test finding applications by ID + Assert.assertNull(realm.getApplicationById("982734")); + Assert.assertEquals(application, realm.getApplicationById(application.getId())); + + + // Test role mappings + UserModel admin = realm.getUser("admin"); + Set allRoles = realm.getRoleMappings(admin); + Assert.assertEquals(5, allRoles.size()); + Assert.assertTrue(allRoles.contains(realm.getRole("admin"))); + Assert.assertTrue(allRoles.contains(application.getRole("app-admin"))); + Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin"))); + Assert.assertTrue(allRoles.contains(accountApp.getRole(Constants.ACCOUNT_PROFILE_ROLE))); + Assert.assertTrue(allRoles.contains(accountApp.getRole(Constants.ACCOUNT_MANAGE_ROLE))); + + UserModel wburke = realm.getUser("wburke"); + allRoles = realm.getRoleMappings(wburke); + Assert.assertEquals(4, allRoles.size()); + Assert.assertFalse(allRoles.contains(realm.getRole("admin"))); + Assert.assertTrue(allRoles.contains(application.getRole("app-user"))); + Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-user"))); + + Assert.assertEquals(0, realm.getRealmRoleMappings(wburke).size()); + + Set realmRoles = realm.getRealmRoleMappings(admin); + Assert.assertEquals(1, realmRoles.size()); + Assert.assertEquals("admin", realmRoles.iterator().next().getName()); + + Set appRoles = application.getApplicationRoleMappings(admin); + Assert.assertEquals(1, appRoles.size()); + Assert.assertEquals("app-admin", appRoles.iterator().next().getName()); + + + // Test scope relationship + UserModel oauthClient = realm.getUser("oauthclient"); Assert.assertNotNull(oauthClient); + Set allScopes = realm.getScopeMappings(oauthClient); + Assert.assertEquals(2, allScopes.size()); + Assert.assertTrue(allScopes.contains(realm.getRole("admin"))); + Assert.assertTrue(allScopes.contains(application.getRole("app-user"))); + + Set realmScopes = realm.getRealmScopeMappings(oauthClient); + Assert.assertTrue(realmScopes.contains(realm.getRole("admin"))); + Set appScopes = application.getApplicationScopeMappings(oauthClient); - RoleModel appUserRole = application.getRole("user"); - Assert.assertTrue(appScopes.contains(appUserRole)); + Assert.assertTrue(appScopes.contains(application.getRole("app-user"))); + // Test social linking UserModel socialUser = realm.getUser("mySocialUser"); @@ -86,6 +141,8 @@ public class ImportTest extends AbstractKeycloakTest { Assert.assertEquals(foundSocialUser.getLoginName(), socialUser.getLoginName()); Assert.assertNull(realm.getUserBySocialLink(new SocialLinkModel("facebook", "not-existing"))); + + } @Test diff --git a/services/src/test/java/org/keycloak/test/ModelTest.java b/services/src/test/java/org/keycloak/test/ModelTest.java index 1c9b5d50b2..b244d80c0b 100755 --- a/services/src/test/java/org/keycloak/test/ModelTest.java +++ b/services/src/test/java/org/keycloak/test/ModelTest.java @@ -7,7 +7,6 @@ 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.test.common.AbstractKeycloakTest; import java.util.HashMap; import java.util.Iterator; diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/services/src/test/java/org/keycloak/test/UserModelTest.java index d808174e90..cf504788f2 100755 --- a/services/src/test/java/org/keycloak/test/UserModelTest.java +++ b/services/src/test/java/org/keycloak/test/UserModelTest.java @@ -7,7 +7,6 @@ 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.test.common.AbstractKeycloakTest; import java.util.Iterator; import java.util.List; diff --git a/services/src/test/resources/testcomposites.json b/services/src/test/resources/testcomposites.json new file mode 100644 index 0000000000..73e4300002 --- /dev/null +++ b/services/src/test/resources/testcomposites.json @@ -0,0 +1,231 @@ +{ + "id": "Test", + "realm": "Test", + "enabled": true, + "tokenLifespan": 600, + "accessCodeLifespan": 600, + "accessCodeLifespanUserAction": 600, + "sslNotRequired": true, + "registrationAllowed": true, + "resetPasswordAllowed": true, + "requiredCredentials": [ "password" ], + "requiredApplicationCredentials": [ "password" ], + "requiredOAuthClientCredentials": [ "password" ], + "smtpServer": { + "from": "auto@keycloak.org", + "host": "localhost", + "port":"3025" + }, + "users" : [ + { + "username" : "REALM_COMPOSITE_1_USER", + "enabled": true, + "email" : "test-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + }, + { + "username" : "REALM_ROLE_1_USER", + "enabled": true, + "email" : "test-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + }, + { + "username" : "REALM_APP_COMPOSITE_USER", + "enabled": true, + "email" : "test-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + }, + { + "username" : "REALM_APP_ROLE_USER", + "enabled": true, + "email" : "test-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + }, + { + "username" : "APP_COMPOSITE_USER", + "enabled": true, + "email" : "test-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + } + ], + "oauthClients" : [ + { + "name" : "third-party", + "enabled": true, + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + } + ], + "roleMappings": [ + { + "username": "REALM_COMPOSITE_1_USER", + "roles": ["REALM_COMPOSITE_1"] + }, + { + "username": "REALM_ROLE_1_USER", + "roles": ["REALM_ROLE_1"] + }, + { + "username": "REALM_APP_COMPOSITE_USER", + "roles": ["REALM_APP_COMPOSITE_ROLE"] + }, + { + "username": "APP_COMPOSITE_USER", + "roles": ["REALM_APP_COMPOSITE_ROLE", "REALM_COMPOSITE_1"] + } + ], + "scopeMappings": [ + { + "username": "REALM_COMPOSITE_1_APPLICATION", + "roles": ["REALM_COMPOSITE_1"] + }, + { + "username": "REALM_ROLE_1_APPLICATION", + "roles": ["REALM_ROLE_1"] + } + ], + "applications": [ + { + "name": "REALM_COMPOSITE_1_APPLICATION", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "adminUrl": "http://localhost:8081/app/logout", + "credentials": [ + { + "type": "password", + "value": "password" + } + ] + }, + { + "name": "REALM_ROLE_1_APPLICATION", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "adminUrl": "http://localhost:8081/app/logout", + "credentials": [ + { + "type": "password", + "value": "password" + } + ] + }, + { + "name": "APP_ROLE_APPLICATION", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "adminUrl": "http://localhost:8081/app/logout", + "credentials": [ + { + "type": "password", + "value": "password" + } + ] + }, + { + "name": "APP_COMPOSITE_APPLICATION", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "adminUrl": "http://localhost:8081/app/logout", + "credentials": [ + { + "type": "password", + "value": "password" + } + ] + } + ], + "roles" : { + "realm" : [ + { + "name": "REALM_ROLE_1" + }, + { + "name": "REALM_ROLE_2" + }, + { + "name": "REALM_ROLE_3" + }, + { + "name": "REALM_COMPOSITE_1", + "composites": { + "realm": ["REALM_ROLE_1"] + } + }, + { + "name": "REALM_APP_COMPOSITE_ROLE", + "composites": { + "application": { + "APP_ROLE_APPLICATION" :[ + "APP_ROLE_1" + ] + } + } + } + ], + "application" : { + "APP_ROLE_APPLICATION" : [ + { + "name": "APP_ROLE_1" + }, + { + "name": "APP_ROLE_2" + } + ], + "APP_COMPOSITE_APPLICATION" : [ + { + "name": "APP_COMPOSITE_ROLE", + "composites": { + "realm" : [ + "REALM_ROLE_1", + "REALM_ROLE_2", + "REALM_ROLE_3" + ], + "application": { + "APP_ROLE_APPLICATION" :[ + "APP_ROLE_1" + ] + } + } + }, + { + "name": "APP_ROLE_2" + } + ] + } + + }, + + "applicationRoleMappings": { + "APP_ROLE_APPLICATION": [ + { + "username": "REALM_APP_ROLE_USER", + "roles": ["APP_ROLE_2"] + } + ] + }, + "applicationScopeMappings": { + "APP_ROLE_APPLICATION": [ + { + "username": "APP_COMPOSITE_APPLICATION", + "roles": ["APP_ROLE_2"] + } + ] + } +} \ No newline at end of file diff --git a/services/src/test/resources/testrealm.json b/services/src/test/resources/testrealm.json index 76f90a97c3..16ccf469d2 100755 --- a/services/src/test/resources/testrealm.json +++ b/services/src/test/resources/testrealm.json @@ -43,16 +43,6 @@ } ] }, - { - "username": "oauthclient", - "enabled": true, - "credentials": [ - { - "type": "password", - "value": "clientpassword" - } - ] - }, { "username": "mySocialUser", "enabled": true @@ -88,6 +78,16 @@ } ], + "oauthClients" : [ + { + "name" : "oauthclient", + "enabled": true, + "credentials" : [ + { "type" : "password", + "value" : "clientpassword" } + ] + } + ], "roles" : { "realm" : [ { @@ -97,18 +97,18 @@ "application" : { "Application" : [ { - "name": "admin" + "name": "app-admin" }, { - "name": "user" + "name": "app-user" } ], "OtherApp" : [ { - "name": "admin" + "name": "otherapp-admin" }, { - "name": "user" + "name": "otherapp-user" } ] } @@ -119,25 +119,31 @@ "roles": ["admin"] } ], + "scopeMappings": [ + { + "username": "oauthclient", + "roles": ["admin"] + } + ], "applicationRoleMappings": { "Application": [ { "username": "wburke", - "roles": ["user"] + "roles": ["app-user"] }, { "username": "admin", - "roles": ["admin"] + "roles": ["app-admin"] } ], "OtherApp": [ { "username": "wburke", - "roles": ["user"] + "roles": ["otherapp-user"] }, { "username": "admin", - "roles": ["admin"] + "roles": ["otherapp-admin"] } ] }, @@ -145,7 +151,7 @@ "Application": [ { "username": "oauthclient", - "roles": ["user"] + "roles": ["app-user"] } ] diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 115dc5df85..9489da1dc5 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -251,10 +251,22 @@ org.seleniumhq.selenium selenium-chrome-driver + + + + org.keycloak + keycloak-model-mongo + ${project.version} + org.mongodb mongo-java-driver + + org.picketlink + picketlink-common + + @@ -326,5 +338,23 @@ + + + mongo + + + keycloak.model + mongo + + + + + localhost + 27017 + keycloak + true + + + \ No newline at end of file diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java index 136a4003e6..571eda2bcf 100755 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java @@ -273,7 +273,7 @@ public class KeycloakServer { server.deploy(di); - factory = KeycloakApplication.createSessionFactory(); + factory = ((KeycloakApplication)deployment.getApplication()).getFactory(); setupDefaultRealm(); From b9ff73d61cd4f10dd4acc3210ba51cd4fc220c35 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 10 Feb 2014 17:36:46 +0100 Subject: [PATCH 3/3] Moved model unit tests from services to model/tests . Mongo profile in testsuite --- .../models}/utils/ModelProviderUtils.java | 2 +- model/jpa/pom.xml | 30 +++ .../test/resources/META-INF/persistence.xml | 0 model/mongo/pom.xml | 80 +++++++- .../api/AbstractMongoIdentifiableEntity.java | 2 +- .../mongo/api/MongoIdentifiableEntity.java | 2 +- .../keycloak/models/mongo/api/MongoStore.java | 28 +-- .../context/MongoStoreInvocationContext.java | 7 + .../api/types/{Converter.java => Mapper.java} | 10 +- ...nverterContext.java => MapperContext.java} | 8 +- .../mongo/api/types/MapperRegistry.java | 111 ++++++++++ .../models/mongo/api/types/TypeConverter.java | 115 ----------- .../impl/{ObjectInfo.java => EntityInfo.java} | 4 +- .../models/mongo/impl/MongoStoreImpl.java | 190 +++++++++--------- .../SimpleMongoStoreInvocationContext.java | 17 +- ...ransactionMongoStoreInvocationContext.java | 21 +- .../impl/types/BasicDBListConverter.java | 44 ---- .../mongo/impl/types/BasicDBListMapper.java | 44 ++++ ...onverter.java => BasicDBObjectMapper.java} | 34 ++-- ...ter.java => BasicDBObjectToMapMapper.java} | 14 +- ...Converter.java => EnumToStringMapper.java} | 10 +- .../mongo/impl/types/ListConverter.java | 46 ----- .../models/mongo/impl/types/ListMapper.java | 45 +++++ .../{MapConverter.java => MapMapper.java} | 14 +- ...yConverter.java => MongoEntityMapper.java} | 32 +-- ...SimpleConverter.java => SimpleMapper.java} | 12 +- .../impl/types/StringToEnumConverter.java | 28 --- .../mongo/impl/types/StringToEnumMapper.java | 28 +++ .../keycloak/adapters/AbstractAdapter.java | 8 +- .../keycloak/adapters/ApplicationAdapter.java | 45 ++--- .../adapters/MongoKeycloakSession.java | 25 +-- .../adapters/MongoKeycloakSessionFactory.java | 13 -- .../keycloak/adapters/OAuthClientAdapter.java | 13 +- .../mongo/keycloak/adapters/RealmAdapter.java | 115 ++++++----- .../mongo/keycloak/adapters/RoleAdapter.java | 28 ++- .../mongo/keycloak/adapters/UserAdapter.java | 19 +- .../keycloak/entities/ApplicationEntity.java | 7 +- .../keycloak/entities/OAuthClientEntity.java | 5 +- .../mongo/keycloak/entities/RealmEntity.java | 9 +- .../mongo/keycloak/entities/RoleEntity.java | 14 +- .../models/mongo/utils/EmbeddedMongo.java | 50 ----- .../mongo/utils/MongoConfiguration.java | 12 +- .../models/mongo/utils/MongoModelUtils.java | 9 +- ...SystemPropertiesConfigurationProvider.java | 39 ++-- .../models/mongo/test/MongoDBModelTest.java | 22 +- model/pom.xml | 1 + model/tests/pom.xml | 69 +++++++ .../model/test/AbstractModelTest.java | 55 +++++ .../org/keycloak/model}/test/AdapterTest.java | 19 +- .../model}/test/ApplicationModelTest.java | 8 +- .../test}/AuthenticationManagerTest.java | 9 +- .../model}/test/CompositeRolesModelTest.java | 51 +++-- .../org/keycloak/model}/test/ImportTest.java | 12 +- .../org/keycloak/model}/test/ModelTest.java | 8 +- .../keycloak/model}/test/UserModelTest.java | 8 +- .../src/test/resources/testcomposites.json | 0 .../src/test/resources/testrealm-demo.json | 0 .../tests}/src/test/resources/testrealm.json | 0 pom.xml | 12 +- services/pom.xml | 65 ------ .../resources/KeycloakApplication.java | 2 +- .../test/AbstractKeycloakServerTest.java | 70 ------- .../keycloak/test/AbstractKeycloakTest.java | 74 ------- testsuite/integration/README.md | 17 ++ testsuite/integration/pom.xml | 65 +++++- 65 files changed, 994 insertions(+), 962 deletions(-) rename {services/src/main/java/org/keycloak/services => model/api/src/main/java/org/keycloak/models}/utils/ModelProviderUtils.java (97%) rename {services => model/jpa}/src/test/resources/META-INF/persistence.xml (100%) rename model/mongo/src/main/java/org/keycloak/models/mongo/api/types/{Converter.java => Mapper.java} (51%) rename model/mongo/src/main/java/org/keycloak/models/mongo/api/types/{ConverterContext.java => MapperContext.java} (77%) create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/{ObjectInfo.java => EntityInfo.java} (93%) delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListMapper.java rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{BasicDBObjectConverter.java => BasicDBObjectMapper.java} (75%) rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{BasicDBObjectToMapConverter.java => BasicDBObjectToMapMapper.java} (63%) rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{EnumToStringConverter.java => EnumToStringMapper.java} (62%) delete mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java create mode 100755 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListMapper.java rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{MapConverter.java => MapMapper.java} (72%) rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{MongoEntityConverter.java => MongoEntityMapper.java} (51%) rename model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/{SimpleConverter.java => SimpleMapper.java} (59%) delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java create mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumMapper.java delete mode 100644 model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java create mode 100644 model/tests/pom.xml create mode 100644 model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/AdapterTest.java (95%) rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/ApplicationModelTest.java (94%) rename {services/src/test/java/org/keycloak/services/managers => model/tests/src/test/java/org/keycloak/model/test}/AuthenticationManagerTest.java (95%) rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/CompositeRolesModelTest.java (64%) rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/ImportTest.java (93%) rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/ModelTest.java (95%) rename {services/src/test/java/org/keycloak => model/tests/src/test/java/org/keycloak/model}/test/UserModelTest.java (96%) rename {services => model/tests}/src/test/resources/testcomposites.json (100%) rename {services => model/tests}/src/test/resources/testrealm-demo.json (100%) rename {services => model/tests}/src/test/resources/testrealm.json (100%) delete mode 100755 services/src/test/java/org/keycloak/test/AbstractKeycloakServerTest.java delete mode 100755 services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java diff --git a/services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java b/model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java similarity index 97% rename from services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java rename to model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java index 7dcfc95173..e4f64cca8e 100644 --- a/services/src/main/java/org/keycloak/services/utils/ModelProviderUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelProviderUtils.java @@ -1,4 +1,4 @@ -package org.keycloak.services.utils; +package org.keycloak.models.utils; import java.util.ServiceLoader; diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml index 7ba6691273..ecbf28940f 100755 --- a/model/jpa/pom.xml +++ b/model/jpa/pom.xml @@ -59,6 +59,20 @@ + + + org.keycloak + keycloak-model-tests + ${project.version} + tests + test + + + com.h2database + h2 + test + + @@ -70,6 +84,22 @@ 1.6 + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + + org.keycloak:keycloak-model-tests + + + + + + diff --git a/services/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml similarity index 100% rename from services/src/test/resources/META-INF/persistence.xml rename to model/jpa/src/test/resources/META-INF/persistence.xml diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml index d1b5952dd4..fadcc7f268 100755 --- a/model/mongo/pom.xml +++ b/model/mongo/pom.xml @@ -46,17 +46,23 @@ mongo-java-driver provided + - de.flapdoodle.embed - de.flapdoodle.embed.mongo - provided - - - junit - junit + org.keycloak + keycloak-model-tests + ${project.version} + tests test + + + localhost + 27018 + keycloak + true + + @@ -67,6 +73,66 @@ 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + integration-test + + test + + + + ${keycloak.mongo.host} + ${keycloak.mongo.port} + ${keycloak.mongo.db} + ${keycloak.mongo.clearOnStartup} + + + org.keycloak:keycloak-model-tests + + + + + default-test + + true + + + + + + + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + + + start-mongodb + pre-integration-test + + start + + + ${keycloak.mongo.port} + file + ${project.build.directory}/mongodb.log + + + + stop-mongodb + post-integration-test + + stop + + + + + \ No newline at end of file diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java index d6509e05ee..86e6e2e31f 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/AbstractMongoIdentifiableEntity.java @@ -18,7 +18,7 @@ public class AbstractMongoIdentifiableEntity implements MongoIdentifiableEntity } @Override - public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { + public void afterRemove(MongoStoreInvocationContext invocationContext) { // Empty by default } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java index 45ce126709..dfa553ee13 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoIdentifiableEntity.java @@ -17,5 +17,5 @@ public interface MongoIdentifiableEntity extends MongoEntity { * Lifecycle callback, which is called after removal of this object from Mongo. * It may be useful for triggering removal of wired objects. */ - void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invocationContext); + void afterRemove(MongoStoreInvocationContext invocationContext); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java index 17411495aa..9da25e5b4e 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java @@ -11,33 +11,33 @@ import java.util.List; public interface MongoStore { /** - * Insert new object + * Insert new entity * - * @param object to update + * @param entity to insert */ - void insertObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); + void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context); /** - * Update existing object + * Update existing entity * - * @param object to update + * @param entity to update */ - void updateObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); + void updateEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context); - T loadObject(Class type, String oid, MongoStoreInvocationContext context); + T loadEntity(Class type, String id, MongoStoreInvocationContext context); - T loadSingleObject(Class type, DBObject query, MongoStoreInvocationContext context); + T loadSingleEntity(Class type, DBObject query, MongoStoreInvocationContext context); - List loadObjects(Class type, DBObject query, MongoStoreInvocationContext context); + List loadEntities(Class type, DBObject query, MongoStoreInvocationContext context); - boolean removeObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context); + boolean removeEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context); - boolean removeObject(Class type, String id, MongoStoreInvocationContext context); + boolean removeEntity(Class type, String id, MongoStoreInvocationContext context); - boolean removeObjects(Class type, DBObject query, MongoStoreInvocationContext context); + boolean removeEntities(Class type, DBObject query, MongoStoreInvocationContext context); - boolean pushItemToList(MongoIdentifiableEntity object, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context); + boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context); - boolean pullItemFromList(MongoIdentifiableEntity object, String listPropertyName, S itemToPull, MongoStoreInvocationContext context); + boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context); } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java index b6911e9e03..358f445d09 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/context/MongoStoreInvocationContext.java @@ -1,12 +1,17 @@ package org.keycloak.models.mongo.api.context; import org.keycloak.models.mongo.api.MongoIdentifiableEntity; +import org.keycloak.models.mongo.api.MongoStore; /** + * Context, which provides callback methods to be invoked by MongoStore + * * @author Marek Posolda */ public interface MongoStoreInvocationContext { + void addCreatedObject(MongoIdentifiableEntity entity); + void addLoadedObject(MongoIdentifiableEntity entity); T getLoadedObject(Class type, String id); @@ -22,4 +27,6 @@ public interface MongoStoreInvocationContext { void commit(); void rollback(); + + MongoStore getMongoStore(); } 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/Mapper.java similarity index 51% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Converter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/types/Mapper.java index 9c963c1464..c93acde086 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/Mapper.java @@ -2,21 +2,21 @@ package org.keycloak.models.mongo.api.types; /** * SPI object to convert object from application type to database type and vice versa. Shouldn't be directly used by application. - * Various converters should be registered in TypeConverter, which is main entry point to be used by application + * Various mappers should be registered in TypeMapper, which is main entry point to be used by application * * @author Marek Posolda */ -public interface Converter { +public interface Mapper { /** * Convert object from one type to expected type * - * @param converterContext Encapsulates reference to converted object and other things, which might be helpful in conversion + * @param mapperContext Encapsulates reference to converted object and other things, which might be helpful in conversion * @return converted object */ - S convertObject(ConverterContext converterContext); + S convertObject(MapperContext mapperContext); - Class getConverterObjectType(); + Class getTypeOfObjectToConvert(); Class getExpectedReturnType(); } 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/MapperContext.java similarity index 77% rename from model/mongo/src/main/java/org/keycloak/models/mongo/api/types/ConverterContext.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperContext.java index aecb8c09ef..987f18b0a4 100644 --- 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/MapperContext.java @@ -5,18 +5,18 @@ import java.util.List; /** * @author Marek Posolda */ -public class ConverterContext { +public class MapperContext { // 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; + 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) { + public MapperContext(T objectToConvert, Class expectedReturnType, List> genericTypes) { this.objectToConvert = objectToConvert; this.expectedReturnType = expectedReturnType; this.genericTypes = genericTypes; @@ -26,7 +26,7 @@ public class ConverterContext { return objectToConvert; } - public Class getExpectedReturnType() { + public Class getExpectedReturnType() { return expectedReturnType; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java new file mode 100755 index 0000000000..2c1c270c9f --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/MapperRegistry.java @@ -0,0 +1,111 @@ +package org.keycloak.models.mongo.api.types; + +import java.util.HashMap; +import java.util.Map; + +/** + * Registry of mappers, which allow to convert application object to database objects. MapperRegistry is main entry point to be used by application. + * Application can create instance of MapperRegistry and then register required Mapper objects. + * + * @author Marek Posolda + */ +public class MapperRegistry { + + // TODO: Thread-safety support (maybe...) + // Mappers of Application objects to DB objects + private Map, Mapper> appObjectMappers = new HashMap, Mapper>(); + + // Mappers of DB objects to Application objects + private Map, Map, Mapper>> dbObjectMappers = new HashMap, Map, Mapper>>(); + + + /** + * Add mapper for converting application objects to DB objects + * + * @param mapper + */ + public void addAppObjectMapper(Mapper mapper) { + appObjectMappers.put(mapper.getTypeOfObjectToConvert(), mapper); + } + + + /** + * Add mapper for converting DB objects to application objects + * + * @param mapper + */ + public void addDBObjectMapper(Mapper mapper) { + Class dbObjectType = mapper.getTypeOfObjectToConvert(); + Class appObjectType = mapper.getExpectedReturnType(); + Map, Mapper> appObjects = dbObjectMappers.get(dbObjectType); + if (appObjects == null) { + appObjects = new HashMap, Mapper>(); + dbObjectMappers.put(dbObjectType, appObjects); + } + appObjects.put(appObjectType, mapper); + } + + + public S convertDBObjectToApplicationObject(MapperContext context) { + Object dbObject = context.getObjectToConvert(); + Class expectedApplicationObjectType = context.getExpectedReturnType(); + + Class dbObjectType = dbObject.getClass(); + Mapper mapper; + + Map, Mapper> appObjects = dbObjectMappers.get(dbObjectType); + if (appObjects == null) { + throw new IllegalArgumentException("Not found any mappers for type " + dbObjectType); + } else { + if (appObjects.size() == 1) { + mapper = (Mapper)appObjects.values().iterator().next(); + } else { + // Try to find converter for requested application type + mapper = (Mapper)getAppConverterForType(context.getExpectedReturnType(), appObjects); + } + } + + if (mapper == null) { + throw new IllegalArgumentException("Can't found mapper for type " + dbObjectType + " and expectedApplicationType " + expectedApplicationObjectType); + } + + return mapper.convertObject(context); + } + + + public S convertApplicationObjectToDBObject(Object applicationObject, Class expectedDBObjectType) { + Class appObjectType = applicationObject.getClass(); + Mapper mapper = (Mapper)getAppConverterForType(appObjectType, appObjectMappers); + if (mapper == null) { + throw new IllegalArgumentException("Can't found converter for type " + appObjectType + " in registered appObjectMappers"); + } + if (!expectedDBObjectType.isAssignableFrom(mapper.getExpectedReturnType())) { + throw new IllegalArgumentException("Converter " + mapper + " has return type " + mapper.getExpectedReturnType() + + " but we need type " + expectedDBObjectType); + } + return mapper.convertObject(new MapperContext(applicationObject, expectedDBObjectType, null)); + } + + // Try to find converter for given type or all it's supertypes + private static Mapper getAppConverterForType(Class appObjectType, Map, Mapper> appObjectConverters) { + Mapper mapper = (Mapper)appObjectConverters.get(appObjectType); + if (mapper != null) { + return mapper; + } else { + Class[] interfaces = appObjectType.getInterfaces(); + for (Class interface1 : interfaces) { + mapper = getAppConverterForType(interface1, appObjectConverters); + if (mapper != null) { + return mapper; + } + } + + Class superType = appObjectType.getSuperclass(); + if (superType != null) { + return getAppConverterForType(superType, appObjectConverters); + } else { + return null; + } + } + } +} 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 deleted file mode 100755 index 05ad4d1b57..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/api/types/TypeConverter.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.keycloak.models.mongo.api.types; - -import java.util.HashMap; -import java.util.Map; - -/** - * Registry of converters, which allow to convert application object to database objects. TypeConverter is main entry point to be used by application. - * Application can create instance of TypeConverter and then register required Converter objects. - * - * @author Marek Posolda - */ -public class TypeConverter { - - // TODO: Thread-safety support (maybe...) - // Converters of Application objects to DB objects - private Map, Converter> appObjectConverters = new HashMap, Converter>(); - - // Converters of DB objects to Application objects - private Map, Map, Converter>> dbObjectConverters = new HashMap, Map, Converter>>(); - - - /** - * Add converter for converting application objects to DB objects - * - * @param converter - */ - public void addAppObjectConverter(Converter converter) { - appObjectConverters.put(converter.getConverterObjectType(), converter); - } - - - /** - * Add converter for converting DB objects to application objects - * - * @param converter - */ - public void addDBObjectConverter(Converter converter) { - Class dbObjectType = converter.getConverterObjectType(); - Class appObjectType = converter.getExpectedReturnType(); - Map, Converter> appObjects = dbObjectConverters.get(dbObjectType); - if (appObjects == null) { - appObjects = new HashMap, Converter>(); - dbObjectConverters.put(dbObjectType, appObjects); - } - appObjects.put(appObjectType, converter); - } - - - public Object convertDBObjectToApplicationObject(ConverterContext context) { - Object dbObject = context.getObjectToConvert(); - Class expectedApplicationObjectType = context.getExpectedReturnType(); - - Class dbObjectType = dbObject.getClass(); - Converter converter; - - Map, Converter> appObjects = dbObjectConverters.get(dbObjectType); - if (appObjects == null) { - throw new IllegalArgumentException("Not found any converters for type " + dbObjectType); - } else { - if (appObjects.size() == 1) { - converter = (Converter)appObjects.values().iterator().next(); - } else { - // Try to find converter for requested application type - converter = (Converter)getAppConverterForType(context.getExpectedReturnType(), appObjects); - } - } - - if (converter == null) { - throw new IllegalArgumentException("Can't found converter for type " + dbObjectType + " and expectedApplicationType " + expectedApplicationObjectType); - } - /*if (!expectedApplicationObjectType.isAssignableFrom(converter.getExpectedReturnType())) { - throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + - " but we need type " + expectedApplicationObjectType); - } */ - - return converter.convertObject(context); - } - - - public S convertApplicationObjectToDBObject(Object applicationObject, Class expectedDBObjectType) { - Class appObjectType = applicationObject.getClass(); - Converter converter = (Converter)getAppConverterForType(appObjectType, appObjectConverters); - if (converter == null) { - throw new IllegalArgumentException("Can't found converter for type " + appObjectType + " in registered appObjectConverters"); - } - if (!expectedDBObjectType.isAssignableFrom(converter.getExpectedReturnType())) { - throw new IllegalArgumentException("Converter " + converter + " has return type " + converter.getExpectedReturnType() + - " but we need type " + expectedDBObjectType); - } - return converter.convertObject(new ConverterContext(applicationObject, expectedDBObjectType, null)); - } - - // Try to find converter for given type or all it's supertypes - private static Converter getAppConverterForType(Class appObjectType, Map, Converter> appObjectConverters) { - Converter converter = (Converter)appObjectConverters.get(appObjectType); - if (converter != null) { - return converter; - } else { - Class[] interfaces = appObjectType.getInterfaces(); - for (Class interface1 : interfaces) { - converter = getAppConverterForType(interface1, appObjectConverters); - if (converter != null) { - return converter; - } - } - - Class superType = appObjectType.getSuperclass(); - if (superType != null) { - return getAppConverterForType(superType, appObjectConverters); - } else { - return null; - } - } - } -} 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/EntityInfo.java similarity index 93% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/ObjectInfo.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/EntityInfo.java index 797954a91d..66eda952b7 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/EntityInfo.java @@ -12,7 +12,7 @@ import java.util.Map; /** * @author Marek Posolda */ -public class ObjectInfo { +public class EntityInfo { private final Class objectClass; @@ -20,7 +20,7 @@ public class ObjectInfo { private final Map> properties; - public ObjectInfo(Class objectClass, String dbCollectionName, List> properties) { + public EntityInfo(Class objectClass, String dbCollectionName, List> properties) { this.objectClass = objectClass; this.dbCollectionName = dbCollectionName; diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java index 5fe1d0a281..0bbc094c7b 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java @@ -15,18 +15,18 @@ import org.keycloak.models.mongo.api.MongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.api.context.MongoTask; -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.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.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; +import org.keycloak.models.mongo.api.types.MapperRegistry; +import org.keycloak.models.mongo.impl.types.BasicDBListMapper; +import org.keycloak.models.mongo.impl.types.BasicDBObjectMapper; +import org.keycloak.models.mongo.impl.types.BasicDBObjectToMapMapper; +import org.keycloak.models.mongo.impl.types.EnumToStringMapper; +import org.keycloak.models.mongo.impl.types.ListMapper; +import org.keycloak.models.mongo.impl.types.MapMapper; +import org.keycloak.models.mongo.impl.types.MongoEntityMapper; +import org.keycloak.models.mongo.impl.types.SimpleMapper; +import org.keycloak.models.mongo.impl.types.StringToEnumMapper; import org.picketlink.common.properties.Property; import org.picketlink.common.properties.query.AnnotatedPropertyCriteria; import org.picketlink.common.properties.query.PropertyQueries; @@ -49,39 +49,39 @@ public class MongoStoreImpl implements MongoStore { private final DB database; private static final Logger logger = Logger.getLogger(MongoStoreImpl.class); - private final TypeConverter typeConverter; - private ConcurrentMap, ObjectInfo> objectInfoCache = - new ConcurrentHashMap, ObjectInfo>(); + private final MapperRegistry mapperRegistry; + private ConcurrentMap, EntityInfo> entityInfoCache = + new ConcurrentHashMap, EntityInfo>(); public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class[] managedEntityTypes) { this.database = database; - typeConverter = new TypeConverter(); + mapperRegistry = new MapperRegistry(); for (Class simpleConverterClass : SIMPLE_TYPES) { - SimpleConverter converter = new SimpleConverter(simpleConverterClass); - typeConverter.addAppObjectConverter(converter); - typeConverter.addDBObjectConverter(converter); + SimpleMapper converter = new SimpleMapper(simpleConverterClass); + mapperRegistry.addAppObjectMapper(converter); + mapperRegistry.addDBObjectMapper(converter); } // Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list impl will be ArrayList) - typeConverter.addAppObjectConverter(new ListConverter(typeConverter, ArrayList.class)); - typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class)); - typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter)); + mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, ArrayList.class)); + mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class)); + mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry)); - typeConverter.addAppObjectConverter(new MapConverter(HashMap.class)); - typeConverter.addAppObjectConverter(new MapConverter(Map.class)); - typeConverter.addDBObjectConverter(new BasicDBObjectToMapConverter()); + mapperRegistry.addAppObjectMapper(new MapMapper(HashMap.class)); + mapperRegistry.addAppObjectMapper(new MapMapper(Map.class)); + mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper()); // Enum converters - typeConverter.addAppObjectConverter(new EnumToStringConverter()); - typeConverter.addDBObjectConverter(new StringToEnumConverter()); + mapperRegistry.addAppObjectMapper(new EnumToStringMapper()); + mapperRegistry.addDBObjectMapper(new StringToEnumMapper()); for (Class type : managedEntityTypes) { - getObjectInfo(type); - typeConverter.addAppObjectConverter(new MongoEntityConverter(this, typeConverter, type)); - typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type)); + getEntityInfo(type); + mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type)); + mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type)); } if (clearCollectionsOnStartup) { @@ -107,18 +107,18 @@ public class MongoStoreImpl implements MongoStore { } @Override - public void insertObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context) { - Class clazz = object.getClass(); + public void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) { + Class clazz = entity.getClass(); // Find annotations for ID, for all the properties and for the name of the collection. - ObjectInfo objectInfo = getObjectInfo(clazz); + EntityInfo entityInfo = getEntityInfo(clazz); // Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped) - BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class); + BasicDBObject dbObject = mapperRegistry.convertApplicationObjectToDBObject(entity, BasicDBObject.class); - DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + DBCollection dbCollection = database.getCollection(entityInfo.getDbCollectionName()); - String currentId = object.getId(); + String currentId = entity.getId(); // Inserting object, which already has oid property set. So we need to set "_id" if (currentId != null) { @@ -129,28 +129,28 @@ public class MongoStoreImpl implements MongoStore { // Add id to value of given object if (currentId == null) { - object.setId(dbObject.getString("_id")); + entity.setId(dbObject.getString("_id")); } // Treat object as if it is read (It is already submited to transaction) - context.addLoadedObject(object); + context.addLoadedObject(entity); } @Override - public void updateObject(final MongoIdentifiableEntity object, MongoStoreInvocationContext context) { + public void updateEntity(final MongoIdentifiableEntity entity, MongoStoreInvocationContext context) { MongoTask fullUpdateTask = new MongoTask() { @Override public void execute() { - Class clazz = object.getClass(); - ObjectInfo objectInfo = getObjectInfo(clazz); - BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class); - DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName()); + Class clazz = entity.getClass(); + EntityInfo entityInfo = getEntityInfo(clazz); + BasicDBObject dbObject = mapperRegistry.convertApplicationObjectToDBObject(entity, BasicDBObject.class); + DBCollection dbCollection = database.getCollection(entityInfo.getDbCollectionName()); - String currentId = object.getId(); + String currentId = entity.getId(); if (currentId == null) { - throw new IllegalStateException("Can't update object without id: " + object); + throw new IllegalStateException("Can't update entity without id: " + entity); } else { BasicDBObject query = new BasicDBObject("_id", getObjectId(currentId)); dbCollection.update(query, dbObject); @@ -164,12 +164,12 @@ public class MongoStoreImpl implements MongoStore { }; // update is just added to context and postponed - context.addUpdateTask(object, fullUpdateTask); + context.addUpdateTask(entity, fullUpdateTask); } @Override - public T loadObject(Class type, String id, MongoStoreInvocationContext context) { + public T loadEntity(Class type, String id, MongoStoreInvocationContext context) { // First look if we already read the object with this oid and type during this transaction. If yes, use it instead of DB lookup T cached = context.getLoadedObject(type, id); if (cached != null) return cached; @@ -181,8 +181,8 @@ public class MongoStoreImpl implements MongoStore { if (dbObject == null) return null; - ConverterContext converterContext = new ConverterContext(dbObject, type, null); - T converted = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); + MapperContext mapperContext = new MapperContext(dbObject, type, null); + T converted = mapperRegistry.convertDBObjectToApplicationObject(mapperContext); // Now add it to loaded objects context.addLoadedObject(converted); @@ -192,8 +192,8 @@ public class MongoStoreImpl implements MongoStore { @Override - public T loadSingleObject(Class type, DBObject query, MongoStoreInvocationContext context) { - List result = loadObjects(type, query, context); + public T loadSingleEntity(Class type, DBObject query, MongoStoreInvocationContext context) { + List result = loadEntities(type, query, context); if (result.size() > 1) { throw new IllegalStateException("There are " + result.size() + " results for type=" + type + ", query=" + query + ". We expect just one"); } else if (result.size() == 1) { @@ -206,7 +206,7 @@ public class MongoStoreImpl implements MongoStore { @Override - public List loadObjects(Class type, DBObject query, MongoStoreInvocationContext context) { + public List loadEntities(Class type, DBObject query, MongoStoreInvocationContext context) { // First we should execute all pending tasks before searching DB context.beforeDBSearch(type); @@ -218,21 +218,21 @@ public class MongoStoreImpl implements MongoStore { @Override - public boolean removeObject(MongoIdentifiableEntity object, MongoStoreInvocationContext context) { - return removeObject(object.getClass(), object.getId(), context); + public boolean removeEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) { + return removeEntity(entity.getClass(), entity.getId(), context); } @Override - public boolean removeObject(Class type, String id, MongoStoreInvocationContext context) { - MongoIdentifiableEntity found = loadObject(type, id, context); + public boolean removeEntity(Class type, String id, MongoStoreInvocationContext context) { + MongoIdentifiableEntity found = loadEntity(type, id, context); if (found == null) { return false; } else { DBCollection dbCollection = getDBCollectionForType(type); BasicDBObject dbQuery = new BasicDBObject("_id", getObjectId(id)); dbCollection.remove(dbQuery); - logger.info("Object of type: " + type + ", id: " + id + " removed from MongoDB."); + logger.info("Entity of type: " + type + ", id: " + id + " removed from MongoDB."); context.addRemovedObject(found); return true; @@ -241,14 +241,14 @@ public class MongoStoreImpl implements MongoStore { @Override - public boolean removeObjects(Class type, DBObject query, MongoStoreInvocationContext context) { - List foundObjects = loadObjects(type, query, context); + public boolean removeEntities(Class type, DBObject query, MongoStoreInvocationContext context) { + List foundObjects = loadEntities(type, query, context); if (foundObjects.size() == 0) { return false; } else { DBCollection dbCollection = getDBCollectionForType(type); dbCollection.remove(query); - logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query); + logger.info("Removed " + foundObjects.size() + " entities of type: " + type + ", query: " + query); for (MongoIdentifiableEntity found : foundObjects) { context.addRemovedObject(found);; @@ -258,20 +258,20 @@ public class MongoStoreImpl implements MongoStore { } @Override - public boolean pushItemToList(final MongoIdentifiableEntity object, final String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context) { - final Class type = object.getClass(); - ObjectInfo objectInfo = getObjectInfo(type); + public boolean pushItemToList(final MongoIdentifiableEntity entity, final String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context) { + final Class type = entity.getClass(); + EntityInfo entityInfo = getEntityInfo(type); // Add item to list directly in this object - Property listProperty = objectInfo.getPropertyByName(listPropertyName); + Property listProperty = entityInfo.getPropertyByName(listPropertyName); if (listProperty == null) { - throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object); + throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + entity); } - List list = (List)listProperty.getValue(object); + List list = (List)listProperty.getValue(entity); if (list == null) { list = new ArrayList(); - listProperty.setValue(object, list); + listProperty.setValue(entity, list); } // Skip if item is already in list @@ -284,14 +284,14 @@ public class MongoStoreImpl implements MongoStore { // Add update of list to pending tasks final List listt = list; - context.addUpdateTask(object, new MongoTask() { + context.addUpdateTask(entity, new MongoTask() { @Override public void execute() { // Now DB update of new list with usage of $set - BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(listt, BasicDBList.class); + BasicDBList dbList = mapperRegistry.convertApplicationObjectToDBObject(listt, BasicDBList.class); - BasicDBObject query = new BasicDBObject("_id", getObjectId(object.getId())); + BasicDBObject query = new BasicDBObject("_id", getObjectId(entity.getId())); BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList); BasicDBObject setCommand = new BasicDBObject("$set", listObject); getDBCollectionForType(type).update(query, setCommand); @@ -308,16 +308,16 @@ public class MongoStoreImpl implements MongoStore { @Override - public boolean pullItemFromList(final MongoIdentifiableEntity object, final String listPropertyName, final S itemToPull, MongoStoreInvocationContext context) { - final Class type = object.getClass(); - ObjectInfo objectInfo = getObjectInfo(type); + public boolean pullItemFromList(final MongoIdentifiableEntity entity, final String listPropertyName, final S itemToPull, MongoStoreInvocationContext context) { + final Class type = entity.getClass(); + EntityInfo entityInfo = getEntityInfo(type); // Remove item from list directly in this object - Property listProperty = objectInfo.getPropertyByName(listPropertyName); + Property listProperty = entityInfo.getPropertyByName(listPropertyName); if (listProperty == null) { - throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object); + throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + entity); } - List list = (List)listProperty.getValue(object); + List list = (List)listProperty.getValue(entity); // If list is null, we skip both object and DB update if (list == null || !list.contains(itemToPull)) { @@ -328,13 +328,13 @@ public class MongoStoreImpl implements MongoStore { list.remove(itemToPull); // Add update of list to pending tasks - context.addUpdateTask(object, new MongoTask() { + context.addUpdateTask(entity, new MongoTask() { @Override public void execute() { // Pull item from DB - Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class); - BasicDBObject query = new BasicDBObject("_id", getObjectId(object.getId())); + Object dbItemToPull = mapperRegistry.convertApplicationObjectToDBObject(itemToPull, Object.class); + BasicDBObject query = new BasicDBObject("_id", getObjectId(entity.getId())); BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull); BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject); getDBCollectionForType(type).update(query, pullCommand); @@ -351,31 +351,31 @@ public class MongoStoreImpl implements MongoStore { } // Possibility to add user-defined converters - public void addAppObjectConverter(Converter converter) { - typeConverter.addAppObjectConverter(converter); + public void addAppObjectConverter(Mapper mapper) { + mapperRegistry.addAppObjectMapper(mapper); } - public void addDBObjectConverter(Converter converter) { - typeConverter.addDBObjectConverter(converter); + public void addDBObjectConverter(Mapper mapper) { + mapperRegistry.addDBObjectMapper(mapper); } - public ObjectInfo getObjectInfo(Class objectClass) { - ObjectInfo objectInfo = objectInfoCache.get(objectClass); - if (objectInfo == null) { + public EntityInfo getEntityInfo(Class objectClass) { + EntityInfo entityInfo = entityInfoCache.get(objectClass); + if (entityInfo == null) { List> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList(); MongoCollection classAnnotation = objectClass.getAnnotation(MongoCollection.class); String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName(); - objectInfo = new ObjectInfo(objectClass, dbCollectionName, properties); + entityInfo = new EntityInfo(objectClass, dbCollectionName, properties); - ObjectInfo existing = objectInfoCache.putIfAbsent(objectClass, objectInfo); + EntityInfo existing = entityInfoCache.putIfAbsent(objectClass, entityInfo); if (existing != null) { - objectInfo = existing; + entityInfo = existing; } } - return objectInfo; + return entityInfo; } protected List convertCursor(Class type, DBCursor cursor, MongoStoreInvocationContext context) { @@ -389,8 +389,8 @@ public class MongoStoreImpl implements MongoStore { if (object == null) { // So convert and use fresh instance from DB - ConverterContext converterContext = new ConverterContext(dbObject, type, null); - object = (T)typeConverter.convertDBObjectToApplicationObject(converterContext); + MapperContext mapperContext = new MapperContext(dbObject, type, null); + object = mapperRegistry.convertDBObjectToApplicationObject(mapperContext); context.addLoadedObject(object); } @@ -404,9 +404,9 @@ public class MongoStoreImpl implements MongoStore { } protected DBCollection getDBCollectionForType(Class type) { - ObjectInfo objectInfo = getObjectInfo(type); - String dbCollectionName = objectInfo.getDbCollectionName(); - return dbCollectionName==null ? null : database.getCollection(objectInfo.getDbCollectionName()); + EntityInfo entityInfo = getEntityInfo(type); + String dbCollectionName = entityInfo.getDbCollectionName(); + return dbCollectionName==null ? null : database.getCollection(entityInfo.getDbCollectionName()); } // We allow ObjectId to be both "ObjectId" or "String". diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java index 18c2d6a356..b0c871d83e 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/SimpleMongoStoreInvocationContext.java @@ -13,10 +13,14 @@ import org.keycloak.models.mongo.api.context.MongoTask; */ public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationContext { - private final MongoStore store; + private final MongoStore mongoStore; - public SimpleMongoStoreInvocationContext(MongoStore store) { - this.store = store; + public SimpleMongoStoreInvocationContext(MongoStore mongoStore) { + this.mongoStore = mongoStore; + } + + @Override + public void addCreatedObject(MongoIdentifiableEntity entity) { } @Override @@ -35,7 +39,7 @@ public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationCo @Override public void addRemovedObject(MongoIdentifiableEntity entityToRemove) { - entityToRemove.afterRemove(store, this); + entityToRemove.afterRemove(this); } @Override @@ -53,4 +57,9 @@ public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationCo @Override public void rollback() { } + + @Override + public MongoStore getMongoStore() { + return mongoStore; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java index 1f9f66d1b8..5d5ff08276 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/context/TransactionMongoStoreInvocationContext.java @@ -2,6 +2,7 @@ package org.keycloak.models.mongo.impl.context; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -31,6 +32,12 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat this.mongoStore = mongoStore; } + @Override + public void addCreatedObject(MongoIdentifiableEntity entity) { + // For now just add it to list of loaded objects + addLoadedObject(entity); + } + @Override public void addLoadedObject(MongoIdentifiableEntity entity) { loadedObjects.put(entity.getId(), entity); @@ -49,7 +56,7 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat Set currentObjectTasks = pendingUpdateTasks.get(entityToUpdate); if (currentObjectTasks == null) { - currentObjectTasks = new HashSet(); + currentObjectTasks = new LinkedHashSet(); pendingUpdateTasks.put(entityToUpdate, currentObjectTasks); } else { // if task is full update, then remove all other tasks as we need to do full update of object anyway @@ -74,7 +81,7 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat pendingUpdateTasks.remove(entityToRemove); loadedObjects.remove(entityToRemove.getId()); - entityToRemove.afterRemove(mongoStore, this); + entityToRemove.afterRemove(this); } @Override @@ -107,8 +114,6 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat @Override public void commit() { - loadedObjects.clear(); - // Now execute all pending update tasks for (Set mongoTasks : pendingUpdateTasks.values()) { for (MongoTask currentTask : mongoTasks) { @@ -117,13 +122,19 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat } // And clear it + loadedObjects.clear(); pendingUpdateTasks.clear(); } @Override public void rollback() { - // Just clear the map without executions of tasks + // Just clear the map without executions of tasks TODO: Attempt to do complete rollback (removal of created objects, restoring of removed objects, rollback of updates) loadedObjects.clear(); pendingUpdateTasks.clear(); } + + @Override + public MongoStore getMongoStore() { + return mongoStore; + } } 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 deleted file mode 100755 index 544e1f8210..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListConverter.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.keycloak.models.mongo.impl.types; - -import com.mongodb.BasicDBList; -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 { - - private final TypeConverter typeConverter; - - public BasicDBListConverter(TypeConverter typeConverter) { - this.typeConverter = typeConverter; - } - - @Override - public List convertObject(ConverterContext context) { - BasicDBList dbList = context.getObjectToConvert(); - ArrayList appObjects = new ArrayList(); - Class expectedListElementType = context.getGenericTypes().get(0); - - for (Object dbObject : dbList) { - ConverterContext newContext = new ConverterContext(dbObject, expectedListElementType, null); - appObjects.add(typeConverter.convertDBObjectToApplicationObject(newContext)); - } - return appObjects; - } - - @Override - public Class getConverterObjectType() { - return BasicDBList.class; - } - - @Override - public Class getExpectedReturnType() { - return List.class; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListMapper.java new file mode 100755 index 0000000000..ff81604f60 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBListMapper.java @@ -0,0 +1,44 @@ +package org.keycloak.models.mongo.impl.types; + +import com.mongodb.BasicDBList; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; +import org.keycloak.models.mongo.api.types.MapperRegistry; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Marek Posolda + */ +public class BasicDBListMapper implements Mapper { + + private final MapperRegistry mapperRegistry; + + public BasicDBListMapper(MapperRegistry mapperRegistry) { + this.mapperRegistry = mapperRegistry; + } + + @Override + public List convertObject(MapperContext context) { + BasicDBList dbList = context.getObjectToConvert(); + ArrayList appObjects = new ArrayList(); + Class expectedListElementType = context.getGenericTypes().get(0); + + for (Object dbObject : dbList) { + MapperContext newContext = new MapperContext(dbObject, expectedListElementType, null); + appObjects.add(mapperRegistry.convertDBObjectToApplicationObject(newContext)); + } + return appObjects; + } + + @Override + public Class getTypeOfObjectToConvert() { + return BasicDBList.class; + } + + @Override + 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/BasicDBObjectMapper.java similarity index 75% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectMapper.java index 2d130e6344..bd423e07ad 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/BasicDBObjectMapper.java @@ -9,39 +9,39 @@ import com.mongodb.BasicDBObject; import org.jboss.logging.Logger; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoIdentifiableEntity; -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.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; +import org.keycloak.models.mongo.api.types.MapperRegistry; import org.keycloak.models.mongo.impl.MongoStoreImpl; -import org.keycloak.models.mongo.impl.ObjectInfo; +import org.keycloak.models.mongo.impl.EntityInfo; import org.picketlink.common.properties.Property; import org.picketlink.common.reflection.Types; /** * @author Marek Posolda */ -public class BasicDBObjectConverter implements Converter { +public class BasicDBObjectMapper implements Mapper { - private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class); + private static final Logger logger = Logger.getLogger(BasicDBObjectMapper.class); private final MongoStoreImpl mongoStoreImpl; - private final TypeConverter typeConverter; + private final MapperRegistry mapperRegistry; private final Class expectedObjectType; - public BasicDBObjectConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class expectedObjectType) { + public BasicDBObjectMapper(MongoStoreImpl mongoStoreImpl, MapperRegistry mapperRegistry, Class expectedObjectType) { this.mongoStoreImpl = mongoStoreImpl; - this.typeConverter = typeConverter; + this.mapperRegistry = mapperRegistry; this.expectedObjectType = expectedObjectType; } @Override - public S convertObject(ConverterContext context) { + public S convertObject(MapperContext context) { BasicDBObject dbObject = context.getObjectToConvert(); if (dbObject == null) { return null; } - ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(expectedObjectType); + EntityInfo entityInfo = mongoStoreImpl.getEntityInfo(expectedObjectType); S object; try { @@ -60,7 +60,7 @@ public class BasicDBObjectConverter implements Converter< ((MongoIdentifiableEntity)object).setId(value.toString()); } - } else if ((property = objectInfo.getPropertyByName(key)) != null) { + } else if ((property = entityInfo.getPropertyByName(key)) != null) { // It's declared property with @DBField annotation setPropertyValue(object, value, property); @@ -79,7 +79,7 @@ public class BasicDBObjectConverter implements Converter< return; } - ConverterContext context; + MapperContext context; Type type = property.getBaseType(); @@ -94,15 +94,15 @@ public class BasicDBObjectConverter implements Converter< } Class expectedReturnType = (Class)parameterized.getRawType(); - context = new ConverterContext(valueFromDB, expectedReturnType, genericTypes); + context = new MapperContext(valueFromDB, expectedReturnType, genericTypes); } else { Class expectedReturnType = (Class)type; // handle primitives expectedReturnType = Types.boxedClass(expectedReturnType); - context = new ConverterContext(valueFromDB, expectedReturnType, null); + context = new MapperContext(valueFromDB, expectedReturnType, null); } - Object appObject = typeConverter.convertDBObjectToApplicationObject(context); + Object appObject = mapperRegistry.convertDBObjectToApplicationObject(context); if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) { property.setValue(object, appObject); @@ -113,7 +113,7 @@ public class BasicDBObjectConverter implements Converter< } @Override - public Class getConverterObjectType() { + public Class getTypeOfObjectToConvert() { return BasicDBObject.class; } 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/BasicDBObjectToMapMapper.java similarity index 63% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/BasicDBObjectToMapMapper.java index 4314ade21c..47e8d3cd48 100644 --- 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/BasicDBObjectToMapMapper.java @@ -4,18 +4,18 @@ 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; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; /** * For now, we support just convert to Map * * @author Marek Posolda */ -public class BasicDBObjectToMapConverter implements Converter { +public class BasicDBObjectToMapMapper implements Mapper { @Override - public Map convertObject(ConverterContext context) { + public Map convertObject(MapperContext context) { BasicDBObject objectToConvert = context.getObjectToConvert(); HashMap result = new HashMap(); @@ -23,8 +23,8 @@ public class BasicDBObjectToMapConverter implements Converter getConverterObjectType() { + public Class getTypeOfObjectToConvert() { return BasicDBObject.class; } 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/EnumToStringMapper.java similarity index 62% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/EnumToStringMapper.java index ffb758fdac..a66633cf52 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/EnumToStringMapper.java @@ -1,23 +1,23 @@ package org.keycloak.models.mongo.impl.types; -import org.keycloak.models.mongo.api.types.Converter; -import org.keycloak.models.mongo.api.types.ConverterContext; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; /** * @author Marek Posolda */ -public class EnumToStringConverter implements Converter { +public class EnumToStringMapper implements Mapper { // 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(ConverterContext context) { + public String convertObject(MapperContext context) { Enum objectToConvert = context.getObjectToConvert(); return objectToConvert.toString(); } @Override - public Class getConverterObjectType() { + public Class getTypeOfObjectToConvert() { return Enum.class; } 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 deleted file mode 100755 index 739ff9ae57..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -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; - -/** - * @author Marek Posolda - */ -public class ListConverter implements Converter { - - private final TypeConverter typeConverter; - private final Class listType; - - public ListConverter(TypeConverter typeConverter, Class listType) { - this.typeConverter = typeConverter; - this.listType = listType; - } - - @Override - public BasicDBList convertObject(ConverterContext context) { - T appObjectsList = context.getObjectToConvert(); - - BasicDBList dbObjects = new BasicDBList(); - for (Object appObject : appObjectsList) { - Object dbObject = typeConverter.convertApplicationObjectToDBObject(appObject, Object.class); - - dbObjects.add(dbObject); - } - return dbObjects; - } - - @Override - public Class getConverterObjectType() { - return listType; - } - - @Override - public Class getExpectedReturnType() { - return BasicDBList.class; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListMapper.java new file mode 100755 index 0000000000..7ac9a593e7 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/ListMapper.java @@ -0,0 +1,45 @@ +package org.keycloak.models.mongo.impl.types; + +import com.mongodb.BasicDBList; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; +import org.keycloak.models.mongo.api.types.MapperRegistry; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public class ListMapper implements Mapper { + + private final MapperRegistry mapperRegistry; + private final Class listType; + + public ListMapper(MapperRegistry mapperRegistry, Class listType) { + this.mapperRegistry = mapperRegistry; + this.listType = listType; + } + + @Override + public BasicDBList convertObject(MapperContext context) { + T appObjectsList = context.getObjectToConvert(); + + BasicDBList dbObjects = new BasicDBList(); + for (Object appObject : appObjectsList) { + Object dbObject = mapperRegistry.convertApplicationObjectToDBObject(appObject, Object.class); + + dbObjects.add(dbObject); + } + return dbObjects; + } + + @Override + public Class getTypeOfObjectToConvert() { + return listType; + } + + @Override + public Class getExpectedReturnType() { + return BasicDBList.class; + } +} 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/MapMapper.java similarity index 72% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MapMapper.java index 0ac8051b69..4798ac1534 100644 --- 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/MapMapper.java @@ -4,25 +4,27 @@ 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; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; /** + * For now, we support just convert from Map + * * @author Marek Posolda */ -public class MapConverter implements Converter { +public class MapMapper implements Mapper { // 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) { + public MapMapper(Class mapType) { this.mapType = mapType; } @Override - public BasicDBObject convertObject(ConverterContext context) { + public BasicDBObject convertObject(MapperContext context) { T objectToConvert = context.getObjectToConvert(); BasicDBObject dbObject = new BasicDBObject(); @@ -41,7 +43,7 @@ public class MapConverter implements Converter } @Override - public Class getConverterObjectType() { + public Class getTypeOfObjectToConvert() { return mapType; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java similarity index 51% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java index b910908bc4..3514e6e6fe 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityConverter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/MongoEntityMapper.java @@ -2,11 +2,11 @@ package org.keycloak.models.mongo.impl.types; import com.mongodb.BasicDBObject; import org.keycloak.models.mongo.api.MongoEntity; -import org.keycloak.models.mongo.api.types.Converter; -import org.keycloak.models.mongo.api.types.ConverterContext; -import org.keycloak.models.mongo.api.types.TypeConverter; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; +import org.keycloak.models.mongo.api.types.MapperRegistry; import org.keycloak.models.mongo.impl.MongoStoreImpl; -import org.keycloak.models.mongo.impl.ObjectInfo; +import org.keycloak.models.mongo.impl.EntityInfo; import org.picketlink.common.properties.Property; import java.util.Collection; @@ -14,32 +14,32 @@ import java.util.Collection; /** * @author Marek Posolda */ -public class MongoEntityConverter implements Converter { +public class MongoEntityMapper implements Mapper { private final MongoStoreImpl mongoStoreImpl; - private final TypeConverter typeConverter; - private final Class expectedNoSQLObjectType; + private final MapperRegistry mapperRegistry; + private final Class expectedMongoEntityType; - public MongoEntityConverter(MongoStoreImpl mongoStoreImpl, TypeConverter typeConverter, Class expectedNoSQLObjectType) { + public MongoEntityMapper(MongoStoreImpl mongoStoreImpl, MapperRegistry mapperRegistry, Class expectedMongoEntityType) { this.mongoStoreImpl = mongoStoreImpl; - this.typeConverter = typeConverter; - this.expectedNoSQLObjectType = expectedNoSQLObjectType; + this.mapperRegistry = mapperRegistry; + this.expectedMongoEntityType = expectedMongoEntityType; } @Override - public BasicDBObject convertObject(ConverterContext context) { + public BasicDBObject convertObject(MapperContext context) { T applicationObject = context.getObjectToConvert(); - ObjectInfo objectInfo = mongoStoreImpl.getObjectInfo(applicationObject.getClass()); + EntityInfo entityInfo = mongoStoreImpl.getEntityInfo(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(); - Collection> props = objectInfo.getProperties(); + Collection> props = entityInfo.getProperties(); for (Property property : props) { String propName = property.getName(); Object propValue = property.getValue(applicationObject); - Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Object.class); + Object dbValue = propValue == null ? null : mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class); dbObject.put(propName, dbValue); } @@ -47,8 +47,8 @@ public class MongoEntityConverter implements Converter getConverterObjectType() { - return expectedNoSQLObjectType; + public Class getTypeOfObjectToConvert() { + return expectedMongoEntityType; } @Override 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/SimpleMapper.java similarity index 59% rename from model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleConverter.java rename to model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/SimpleMapper.java index eca7c1a67e..381cf6059a 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/SimpleMapper.java @@ -1,29 +1,29 @@ package org.keycloak.models.mongo.impl.types; -import org.keycloak.models.mongo.api.types.Converter; -import org.keycloak.models.mongo.api.types.ConverterContext; +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; /** * Just returns input * * @author Marek Posolda */ -public class SimpleConverter implements Converter { +public class SimpleMapper implements Mapper { private final Class expectedType; - public SimpleConverter(Class expectedType) { + public SimpleMapper(Class expectedType) { this.expectedType = expectedType; } @Override - public T convertObject(ConverterContext context) { + public T convertObject(MapperContext context) { T objectToConvert = context.getObjectToConvert(); return objectToConvert; } @Override - public Class getConverterObjectType() { + public Class getTypeOfObjectToConvert() { return expectedType; } 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 deleted file mode 100644 index 5bb84a8126..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -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 - */ -public class StringToEnumConverter implements Converter { - - @Override - public Enum convertObject(ConverterContext context) { - String enumValue = context.getObjectToConvert(); - - Class clazz = (Class)context.getExpectedReturnType(); - return Enum.valueOf(clazz, enumValue); - } - - @Override - public Class getConverterObjectType() { - return String.class; - } - - @Override - public Class getExpectedReturnType() { - return Enum.class; - } -} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumMapper.java b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumMapper.java new file mode 100644 index 0000000000..08e558a580 --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/impl/types/StringToEnumMapper.java @@ -0,0 +1,28 @@ +package org.keycloak.models.mongo.impl.types; + +import org.keycloak.models.mongo.api.types.Mapper; +import org.keycloak.models.mongo.api.types.MapperContext; + +/** + * @author Marek Posolda + */ +public class StringToEnumMapper implements Mapper { + + @Override + public Enum convertObject(MapperContext context) { + String enumValue = context.getObjectToConvert(); + + Class clazz = context.getExpectedReturnType(); + return Enum.valueOf(clazz, enumValue); + } + + @Override + public Class getTypeOfObjectToConvert() { + return String.class; + } + + @Override + public Class getExpectedReturnType() { + return Enum.class; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java index cd6a1dd5a8..9e4f945cc7 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/AbstractAdapter.java @@ -9,11 +9,9 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; */ public abstract class AbstractAdapter { - protected MongoStore mongoStore; protected MongoStoreInvocationContext invocationContext; - public AbstractAdapter(MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { - this.mongoStore = mongoStore; + public AbstractAdapter(MongoStoreInvocationContext invocationContext) { this.invocationContext = invocationContext; } @@ -35,4 +33,8 @@ public abstract class AbstractAdapter { public int hashCode() { return getMongoEntity()!=null ? getMongoEntity().hashCode() : super.hashCode(); } + + protected MongoStore getMongoStore() { + return invocationContext.getMongoStore(); + } } 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 e4ca675b75..0686f4201c 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 @@ -6,7 +6,6 @@ import org.keycloak.models.ApplicationModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.RoleEntity; @@ -26,30 +25,30 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo private final ApplicationEntity application; private UserAdapter resourceUser; - public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - this(applicationEntity, null, mongoStore, invContext); + public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) { + this(applicationEntity, null, invContext); } - public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - super(mongoStore, invContext); + public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStoreInvocationContext invContext) { + super(invContext); this.application = applicationEntity; this.resourceUser = resourceUser; } @Override public void updateApplication() { - mongoStore.updateObject(application, invocationContext); + getMongoStore().updateEntity(application, invocationContext); } @Override public UserAdapter getApplicationUser() { // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object if (resourceUser == null) { - UserEntity userEntity = mongoStore.loadObject(UserEntity.class, application.getResourceUserId(), invocationContext); + UserEntity userEntity = getMongoStore().loadEntity(UserEntity.class, application.getResourceUserId(), invocationContext); if (userEntity == null) { throw new IllegalStateException("User " + application.getResourceUserId() + " not found"); } - resourceUser = new UserAdapter(userEntity, mongoStore, invocationContext); + resourceUser = new UserAdapter(userEntity, invocationContext); } return resourceUser; @@ -116,21 +115,21 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo .and("name").is(name) .and("applicationId").is(getId()) .get(); - RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query, invocationContext); + RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore, invocationContext); + return new RoleAdapter(role, invocationContext); } } @Override public RoleModel getRoleById(String id) { - RoleEntity role = mongoStore.loadObject(RoleEntity.class, id, invocationContext); + RoleEntity role = getMongoStore().loadEntity(RoleEntity.class, id, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore, invocationContext); + return new RoleAdapter(role, this, invocationContext); } } @@ -145,13 +144,13 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo roleEntity.setName(name); roleEntity.setApplicationId(getId()); - mongoStore.insertObject(roleEntity, invocationContext); - return new RoleAdapter(roleEntity, this, mongoStore, invocationContext); + getMongoStore().insertEntity(roleEntity, invocationContext); + return new RoleAdapter(roleEntity, this, invocationContext); } @Override public boolean removeRoleById(String id) { - return mongoStore.removeObject(RoleEntity.class ,id, invocationContext); + return getMongoStore().removeEntity(RoleEntity.class, id, invocationContext); } @Override @@ -159,11 +158,11 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo DBObject query = new QueryBuilder() .and("applicationId").is(getId()) .get(); - List roles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); + List roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); Set result = new HashSet(); for (RoleEntity role : roles) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } return result; @@ -172,11 +171,11 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo @Override public Set getApplicationRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore, invocationContext); + List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } } return result; @@ -185,17 +184,17 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo @Override public void addScope(RoleModel role) { UserAdapter appUser = getApplicationUser(); - mongoStore.pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true, invocationContext); + getMongoStore().pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true, invocationContext); } @Override public Set getApplicationScopeMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore, invocationContext); + List roles = MongoModelUtils.getAllScopesOfUser(user, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getApplicationId())) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } } return result; @@ -213,7 +212,7 @@ public class ApplicationAdapter extends AbstractAdapter implements ApplicationMo addRole(name); } - mongoStore.pushItemToList(application, "defaultRoles", name, true, invocationContext); + getMongoStore().pushItemToList(application, "defaultRoles", name, true, invocationContext); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java index e612a5430f..a57916b466 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSession.java @@ -9,7 +9,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; -import org.keycloak.models.mongo.impl.context.SimpleMongoStoreInvocationContext; import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.RealmEntity; import org.keycloak.models.utils.KeycloakModelUtils; @@ -24,10 +23,8 @@ public class MongoKeycloakSession implements KeycloakSession { private final MongoStoreInvocationContext invocationContext; private final MongoKeycloakTransaction transaction; - private final MongoStore mongoStore; public MongoKeycloakSession(MongoStore mongoStore) { - this.mongoStore = mongoStore; // this.invocationContext = new SimpleMongoStoreInvocationContext(mongoStore); this.invocationContext = new TransactionMongoStoreInvocationContext(mongoStore); this.transaction = new MongoKeycloakTransaction(invocationContext); @@ -58,25 +55,25 @@ public class MongoKeycloakSession implements KeycloakSession { newRealm.setId(id); newRealm.setName(name); - mongoStore.insertObject(newRealm, invocationContext); + getMongoStore().insertEntity(newRealm, invocationContext); - return new RealmAdapter(newRealm, mongoStore, invocationContext); + return new RealmAdapter(newRealm, invocationContext); } @Override public RealmModel getRealm(String id) { - RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, id, invocationContext); - return realmEntity != null ? new RealmAdapter(realmEntity, mongoStore, invocationContext) : null; + RealmEntity realmEntity = getMongoStore().loadEntity(RealmEntity.class, id, invocationContext); + return realmEntity != null ? new RealmAdapter(realmEntity, invocationContext) : null; } @Override public List getRealms(UserModel admin) { DBObject query = new BasicDBObject(); - List realms = mongoStore.loadObjects(RealmEntity.class, query, invocationContext); + List realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext); List results = new ArrayList(); for (RealmEntity realmEntity : realms) { - results.add(new RealmAdapter(realmEntity, mongoStore, invocationContext)); + results.add(new RealmAdapter(realmEntity, invocationContext)); } return results; } @@ -86,14 +83,18 @@ public class MongoKeycloakSession implements KeycloakSession { DBObject query = new QueryBuilder() .and("name").is(name) .get(); - RealmEntity realm = mongoStore.loadSingleObject(RealmEntity.class, query, invocationContext); + RealmEntity realm = getMongoStore().loadSingleEntity(RealmEntity.class, query, invocationContext); if (realm == null) return null; - return new RealmAdapter(realm, mongoStore, invocationContext); + return new RealmAdapter(realm, invocationContext); } @Override public boolean removeRealm(String id) { - return mongoStore.removeObject(RealmEntity.class, id, invocationContext); + return getMongoStore().removeEntity(RealmEntity.class, id, invocationContext); + } + + protected MongoStore getMongoStore() { + return invocationContext.getMongoStore(); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java index 6c2af86742..7ff4ead8d2 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoKeycloakSessionFactory.java @@ -16,7 +16,6 @@ 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; @@ -40,20 +39,12 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory { 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()); @@ -75,9 +66,5 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory { 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/OAuthClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java index 3684872243..2eacc45165 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/OAuthClientAdapter.java @@ -3,7 +3,6 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity; import org.keycloak.models.mongo.keycloak.entities.UserEntity; @@ -16,14 +15,14 @@ public class OAuthClientAdapter extends AbstractAdapter implements OAuthClientMo private final OAuthClientEntity delegate; private UserAdapter oauthAgent; - public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - super(mongoStore, invContext); + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStoreInvocationContext invContext) { + super(invContext); this.delegate = oauthClientEntity; this.oauthAgent = oauthAgent; } - public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - this(oauthClientEntity, null, mongoStore, invContext); + public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) { + this(oauthClientEntity, null, invContext); } @Override @@ -35,8 +34,8 @@ public class OAuthClientAdapter extends AbstractAdapter implements OAuthClientMo public UserModel getOAuthAgent() { // This is not thread-safe. Assumption is that OAuthClientAdapter instance is per-client object if (oauthAgent == null) { - UserEntity user = mongoStore.loadObject(UserEntity.class, delegate.getOauthAgentId(), invocationContext); - oauthAgent = user!=null ? new UserAdapter(user, mongoStore, invocationContext) : null; + UserEntity user = getMongoStore().loadEntity(UserEntity.class, delegate.getOauthAgentId(), invocationContext); + oauthAgent = user!=null ? new UserAdapter(user, invocationContext) : 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 c5c558cfaf..6083eeb589 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 @@ -13,7 +13,6 @@ import org.keycloak.models.SocialLinkModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.CredentialEntity; @@ -54,8 +53,8 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { private volatile transient PasswordPolicy passwordPolicy; - public RealmAdapter(RealmEntity realmEntity, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) { - super(mongoStore, invocationContext); + public RealmAdapter(RealmEntity realmEntity, MongoStoreInvocationContext invocationContext) { + super(invocationContext); this.realm = realmEntity; } @@ -280,12 +279,12 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("loginName").is(name) .and("realmId").is(getId()) .get(); - UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); + UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); if (user == null) { return null; } else { - return new UserAdapter(user, mongoStore, invocationContext); + return new UserAdapter(user, invocationContext); } } @@ -295,12 +294,12 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("email").is(email) .and("realmId").is(getId()) .get(); - UserEntity user = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); + UserEntity user = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); if (user == null) { return null; } else { - return new UserAdapter(user, mongoStore, invocationContext); + return new UserAdapter(user, invocationContext); } } @@ -332,8 +331,8 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { userEntity.setEnabled(true); userEntity.setRealmId(getId()); - mongoStore.insertObject(userEntity, invocationContext); - return new UserAdapter(userEntity, mongoStore, invocationContext); + getMongoStore().insertEntity(userEntity, invocationContext); + return new UserAdapter(userEntity, invocationContext); } @Override @@ -342,7 +341,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("loginName").is(name) .and("realmId").is(getId()) .get(); - return mongoStore.removeObjects(UserEntity.class, query, invocationContext); + return getMongoStore().removeEntities(UserEntity.class, query, invocationContext); } @Override @@ -351,11 +350,11 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("name").is(name) .and("realmId").is(getId()) .get(); - RoleEntity role = mongoStore.loadSingleObject(RoleEntity.class, query, invocationContext); + RoleEntity role = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore, invocationContext); + return new RoleAdapter(role, this, invocationContext); } } @@ -372,13 +371,13 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { roleEntity.setName(name); roleEntity.setRealmId(getId()); - mongoStore.insertObject(roleEntity, invocationContext); - return new RoleAdapter(roleEntity, this, mongoStore, invocationContext); + getMongoStore().insertEntity(roleEntity, invocationContext); + return new RoleAdapter(roleEntity, this, invocationContext); } @Override public boolean removeRoleById(String id) { - return mongoStore.removeObject(RoleEntity.class ,id, invocationContext); + return getMongoStore().removeEntity(RoleEntity.class, id, invocationContext); } @Override @@ -386,13 +385,13 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List roles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); + List roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); Set result = new HashSet(); if (roles == null) return result; for (RoleEntity role : roles) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } return result; @@ -400,11 +399,11 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { @Override public RoleModel getRoleById(String id) { - RoleEntity role = mongoStore.loadObject(RoleEntity.class, id, invocationContext); + RoleEntity role = getMongoStore().loadEntity(RoleEntity.class, id, invocationContext); if (role == null) { return null; } else { - return new RoleAdapter(role, this, mongoStore, invocationContext); + return new RoleAdapter(role, this, invocationContext); } } @@ -420,7 +419,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { addRole(name); } - mongoStore.pushItemToList(realm, "defaultRoles", name, true, invocationContext); + getMongoStore().pushItemToList(realm, "defaultRoles", name, true, invocationContext); } @Override @@ -441,14 +440,14 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { @Override public ApplicationModel getApplicationById(String id) { - ApplicationEntity appData = mongoStore.loadObject(ApplicationEntity.class, id, invocationContext); + ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, id, invocationContext); // Check if application belongs to this realm if (appData == null || !getId().equals(appData.getRealmId())) { return null; } - return new ApplicationAdapter(appData, mongoStore, invocationContext); + return new ApplicationAdapter(appData, invocationContext); } @Override @@ -457,8 +456,8 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("realmId").is(getId()) .and("name").is(name) .get(); - ApplicationEntity appEntity = mongoStore.loadSingleObject(ApplicationEntity.class, query, invocationContext); - return appEntity==null ? null : new ApplicationAdapter(appEntity, mongoStore, invocationContext); + ApplicationEntity appEntity = getMongoStore().loadSingleEntity(ApplicationEntity.class, query, invocationContext); + return appEntity==null ? null : new ApplicationAdapter(appEntity, invocationContext); } @Override @@ -475,11 +474,11 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List appDatas = mongoStore.loadObjects(ApplicationEntity.class, query, invocationContext); + List appDatas = getMongoStore().loadEntities(ApplicationEntity.class, query, invocationContext); List result = new ArrayList(); for (ApplicationEntity appData : appDatas) { - result.add(new ApplicationAdapter(appData, mongoStore, invocationContext)); + result.add(new ApplicationAdapter(appData, invocationContext)); } return result; } @@ -493,14 +492,14 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { appData.setRealmId(getId()); appData.setEnabled(true); appData.setResourceUserId(resourceUser.getUser().getId()); - mongoStore.insertObject(appData, invocationContext); + getMongoStore().insertEntity(appData, invocationContext); - return new ApplicationAdapter(appData, resourceUser, mongoStore, invocationContext); + return new ApplicationAdapter(appData, resourceUser, invocationContext); } @Override public boolean removeApplication(String id) { - return mongoStore.removeObject(ApplicationEntity.class, id, invocationContext); + return getMongoStore().removeEntity(ApplicationEntity.class, id, invocationContext); } @Override @@ -517,20 +516,20 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { @Override public void grantRole(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext); + getMongoStore().pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext); } @Override public Set getRoleMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllRolesOfUser(user, mongoStore, invocationContext); + List roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getRealmId())) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } else { // Likely applicationRole, but we don't have this application yet - result.add(new RoleAdapter(role, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, invocationContext)); } } return result; @@ -555,20 +554,20 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { @Override public void deleteRoleMapping(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext); + getMongoStore().pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext); } @Override public Set getScopeMappings(UserModel user) { Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfUser(user, mongoStore, invocationContext); + List roles = MongoModelUtils.getAllScopesOfUser(user, invocationContext); for (RoleEntity role : roles) { if (getId().equals(role.getRealmId())) { - result.add(new RoleAdapter(role, this, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, this, invocationContext)); } else { // Likely applicationRole, but we don't have this application yet - result.add(new RoleAdapter(role, mongoStore, invocationContext)); + result.add(new RoleAdapter(role, invocationContext)); } } return result; @@ -593,13 +592,13 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { @Override public void addScopeMapping(UserModel agent, RoleModel role) { UserEntity userEntity = ((UserAdapter)agent).getUser(); - mongoStore.pushItemToList(userEntity, "scopeIds", role.getId(), true, invocationContext); + getMongoStore().pushItemToList(userEntity, "scopeIds", role.getId(), true, invocationContext); } @Override public void deleteScopeMapping(UserModel user, RoleModel role) { UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pullItemFromList(userEntity, "scopeIds", role.getId(), invocationContext); + getMongoStore().pullItemFromList(userEntity, "scopeIds", role.getId(), invocationContext); } @Override @@ -610,14 +609,14 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { oauthClient.setOauthAgentId(oauthAgent.getUser().getId()); oauthClient.setRealmId(getId()); oauthClient.setName(name); - mongoStore.insertObject(oauthClient, invocationContext); + getMongoStore().insertEntity(oauthClient, invocationContext); - return new OAuthClientAdapter(oauthClient, oauthAgent, mongoStore, invocationContext); + return new OAuthClientAdapter(oauthClient, oauthAgent, invocationContext); } @Override public boolean removeOAuthClient(String id) { - return mongoStore.removeObject(OAuthClientEntity.class, id, invocationContext); + return getMongoStore().removeEntity(OAuthClientEntity.class, id, invocationContext); } @Override @@ -628,15 +627,15 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("realmId").is(getId()) .and("oauthAgentId").is(user.getUser().getId()) .get(); - OAuthClientEntity oauthClient = mongoStore.loadSingleObject(OAuthClientEntity.class, query, invocationContext); - return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, mongoStore, invocationContext); + OAuthClientEntity oauthClient = getMongoStore().loadSingleEntity(OAuthClientEntity.class, query, invocationContext); + return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, invocationContext); } @Override public OAuthClientModel getOAuthClientById(String id) { - OAuthClientEntity clientEntity = mongoStore.loadObject(OAuthClientEntity.class, id, invocationContext); + OAuthClientEntity clientEntity = getMongoStore().loadEntity(OAuthClientEntity.class, id, invocationContext); if (clientEntity == null) return null; - return new OAuthClientAdapter(clientEntity, mongoStore, invocationContext); + return new OAuthClientAdapter(clientEntity, invocationContext); } @Override @@ -644,10 +643,10 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List results = mongoStore.loadObjects(OAuthClientEntity.class, query, invocationContext); + List results = getMongoStore().loadEntities(OAuthClientEntity.class, query, invocationContext); List list = new ArrayList(); for (OAuthClientEntity data : results) { - list.add(new OAuthClientAdapter(data, mongoStore, invocationContext)); + list.add(new OAuthClientAdapter(data, invocationContext)); } return list; } @@ -795,7 +794,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { } credentialEntity.setDevice(cred.getDevice()); - mongoStore.updateObject(userEntity, invocationContext); + getMongoStore().updateEntity(userEntity, invocationContext); } @Override @@ -805,8 +804,8 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { .and("socialLinks.socialUsername").is(socialLink.getSocialUsername()) .and("realmId").is(getId()) .get(); - UserEntity userEntity = mongoStore.loadSingleObject(UserEntity.class, query, invocationContext); - return userEntity==null ? null : new UserAdapter(userEntity, mongoStore, invocationContext); + UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext); + return userEntity==null ? null : new UserAdapter(userEntity, invocationContext); } @Override @@ -833,7 +832,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { socialLinkEntity.setSocialProvider(socialLink.getSocialProvider()); socialLinkEntity.setSocialUsername(socialLink.getSocialUsername()); - mongoStore.pushItemToList(userEntity, "socialLinks", socialLinkEntity, true, invocationContext); + getMongoStore().pushItemToList(userEntity, "socialLinks", socialLinkEntity, true, invocationContext); } @Override @@ -843,11 +842,11 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { socialLinkEntity.setSocialUsername(socialLink.getSocialUsername()); UserEntity userEntity = ((UserAdapter)user).getUser(); - mongoStore.pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext); + getMongoStore().pullItemFromList(userEntity, "socialLinks", socialLinkEntity, invocationContext); } protected void updateRealm() { - mongoStore.updateObject(realm, invocationContext); + getMongoStore().updateEntity(realm, invocationContext); } protected RequiredCredentialModel initRequiredCredentialModel(String type) { @@ -863,7 +862,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); - List users = mongoStore.loadObjects(UserEntity.class, query, invocationContext); + List users = getMongoStore().loadEntities(UserEntity.class, query, invocationContext); return convertUserEntities(users); } @@ -903,7 +902,7 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { ).get() ); - List users = mongoStore.loadObjects(UserEntity.class, builder.get(), invocationContext); + List users = getMongoStore().loadEntities(UserEntity.class, builder.get(), invocationContext); return convertUserEntities(users); } @@ -925,14 +924,14 @@ public class RealmAdapter extends AbstractAdapter implements RealmModel { queryBuilder.and(UserModel.EMAIL).regex(Pattern.compile("(?i:" + entry.getValue() + "$)")); } } - List users = mongoStore.loadObjects(UserEntity.class, queryBuilder.get(), invocationContext); + List users = getMongoStore().loadEntities(UserEntity.class, queryBuilder.get(), invocationContext); return convertUserEntities(users); } protected List convertUserEntities(List userEntities) { List userModels = new ArrayList(); for (UserEntity user : userEntities) { - userModels.add(new UserAdapter(user, mongoStore, invocationContext)); + userModels.add(new UserAdapter(user, invocationContext)); } return userModels; } 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 b6be681a24..a3ed40b16a 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 @@ -10,7 +10,6 @@ import com.mongodb.QueryBuilder; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity; import org.keycloak.models.mongo.keycloak.entities.RealmEntity; @@ -28,15 +27,14 @@ public class RoleAdapter extends AbstractAdapter implements RoleModel { private final RoleEntity role; private RoleContainerModel roleContainer; - public RoleAdapter(RoleEntity roleEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - this(roleEntity, null, mongoStore, invContext); + public RoleAdapter(RoleEntity roleEntity, MongoStoreInvocationContext invContext) { + this(roleEntity, null, invContext); } - public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - super(mongoStore, invContext); + public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) { + super(invContext); this.role = roleEntity; this.roleContainer = roleContainer; - this.mongoStore = mongoStore; } @Override @@ -72,17 +70,17 @@ public class RoleAdapter extends AbstractAdapter implements RoleModel { } protected void updateRole() { - mongoStore.updateObject(role, invocationContext); + getMongoStore().updateEntity(role, invocationContext); } @Override public void addCompositeRole(RoleModel childRole) { - mongoStore.pushItemToList(role, "compositeRoleIds", childRole.getId(), true, invocationContext); + getMongoStore().pushItemToList(role, "compositeRoleIds", childRole.getId(), true, invocationContext); } @Override public void removeCompositeRole(RoleModel childRole) { - mongoStore.pullItemFromList(role, "compositeRoleIds", childRole.getId(), invocationContext); + getMongoStore().pullItemFromList(role, "compositeRoleIds", childRole.getId(), invocationContext); } @Override @@ -94,11 +92,11 @@ public class RoleAdapter extends AbstractAdapter implements RoleModel { DBObject query = new QueryBuilder() .and("_id").in(MongoModelUtils.convertStringsToObjectIds(role.getCompositeRoleIds())) .get(); - List childRoles = mongoStore.loadObjects(RoleEntity.class, query, invocationContext); + List childRoles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext); Set set = new HashSet(); for (RoleEntity childRole : childRoles) { - set.add(new RoleAdapter(childRole, mongoStore, invocationContext)); + set.add(new RoleAdapter(childRole, invocationContext)); } return set; } @@ -108,17 +106,17 @@ public class RoleAdapter extends AbstractAdapter implements RoleModel { if (roleContainer == null) { // Compute it if (role.getRealmId() != null) { - RealmEntity realm = mongoStore.loadObject(RealmEntity.class, role.getRealmId(), invocationContext); + RealmEntity realm = getMongoStore().loadEntity(RealmEntity.class, role.getRealmId(), invocationContext); if (realm == null) { throw new IllegalStateException("Realm with id: " + role.getRealmId() + " doesn't exists"); } - roleContainer = new RealmAdapter(realm, mongoStore, invocationContext); + roleContainer = new RealmAdapter(realm, invocationContext); } else if (role.getApplicationId() != null) { - ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, role.getApplicationId(), invocationContext); + ApplicationEntity appEntity = getMongoStore().loadEntity(ApplicationEntity.class, role.getApplicationId(), invocationContext); if (appEntity == null) { throw new IllegalStateException("Application with id: " + role.getApplicationId() + " doesn't exists"); } - roleContainer = new ApplicationAdapter(appEntity, mongoStore, invocationContext); + roleContainer = new ApplicationAdapter(appEntity, invocationContext); } else { throw new IllegalStateException("Both realmId and applicationId are null for role: " + this); } 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 c66fefaa3c..1a18387d25 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -2,7 +2,6 @@ package org.keycloak.models.mongo.keycloak.adapters; import org.keycloak.models.UserModel; import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.entities.UserEntity; @@ -23,8 +22,8 @@ public class UserAdapter extends AbstractAdapter implements UserModel { private final UserEntity user; - public UserAdapter(UserEntity userEntity, MongoStore mongoStore, MongoStoreInvocationContext invContext) { - super(mongoStore, invContext); + public UserAdapter(UserEntity userEntity, MongoStoreInvocationContext invContext) { + super(invContext); this.user = userEntity; } @@ -139,12 +138,12 @@ public class UserAdapter extends AbstractAdapter implements UserModel { @Override public void addWebOrigin(String webOrigin) { - mongoStore.pushItemToList(user, "webOrigins", webOrigin, true, invocationContext); + getMongoStore().pushItemToList(user, "webOrigins", webOrigin, true, invocationContext); } @Override public void removeWebOrigin(String webOrigin) { - mongoStore.pullItemFromList(user, "webOrigins", webOrigin, invocationContext); + getMongoStore().pullItemFromList(user, "webOrigins", webOrigin, invocationContext); } @Override @@ -166,12 +165,12 @@ public class UserAdapter extends AbstractAdapter implements UserModel { @Override public void addRedirectUri(String redirectUri) { - mongoStore.pushItemToList(user, "redirectUris", redirectUri, true, invocationContext); + getMongoStore().pushItemToList(user, "redirectUris", redirectUri, true, invocationContext); } @Override public void removeRedirectUri(String redirectUri) { - mongoStore.pullItemFromList(user, "redirectUris", redirectUri, invocationContext); + getMongoStore().pullItemFromList(user, "redirectUris", redirectUri, invocationContext); } @Override @@ -185,12 +184,12 @@ public class UserAdapter extends AbstractAdapter implements UserModel { @Override public void addRequiredAction(RequiredAction action) { - mongoStore.pushItemToList(user, "requiredActions", action, true, invocationContext); + getMongoStore().pushItemToList(user, "requiredActions", action, true, invocationContext); } @Override public void removeRequiredAction(RequiredAction action) { - mongoStore.pullItemFromList(user, "requiredActions", action, invocationContext); + getMongoStore().pullItemFromList(user, "requiredActions", action, invocationContext); } @Override @@ -205,7 +204,7 @@ public class UserAdapter extends AbstractAdapter implements UserModel { } protected void updateUser() { - mongoStore.updateObject(user, invocationContext); + getMongoStore().updateEntity(user, invocationContext); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java index 17805819de..39a62346a1 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/ApplicationEntity.java @@ -9,7 +9,6 @@ import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** @@ -103,14 +102,14 @@ public class ApplicationEntity extends AbstractMongoIdentifiableEntity implement } @Override - public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public void afterRemove(MongoStoreInvocationContext context) { // Remove resourceUser of this application - mongoStore.removeObject(UserEntity.class, resourceUserId, invContext); + context.getMongoStore().removeEntity(UserEntity.class, resourceUserId, context); // Remove all roles, which belongs to this application DBObject query = new QueryBuilder() .and("applicationId").is(getId()) .get(); - mongoStore.removeObjects(RoleEntity.class, query, invContext); + context.getMongoStore().removeEntities(RoleEntity.class, query, context); } } 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 index 52dfeceb91..f2875b1be0 100644 --- 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 @@ -4,7 +4,6 @@ import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; /** @@ -46,8 +45,8 @@ public class OAuthClientEntity extends AbstractMongoIdentifiableEntity implement } @Override - public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public void afterRemove(MongoStoreInvocationContext context) { // Remove user of this oauthClient - mongoStore.removeObject(UserEntity.class, oauthAgentId, invContext); + context.getMongoStore().removeEntity(UserEntity.class, oauthAgentId, context); } } 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 index 34652c7695..6ee2fdf652 100755 --- 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 @@ -6,7 +6,6 @@ import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity; import org.keycloak.models.mongo.api.MongoCollection; import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoField; -import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import java.util.ArrayList; @@ -249,18 +248,18 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong } @Override - public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public void afterRemove(MongoStoreInvocationContext context) { DBObject query = new QueryBuilder() .and("realmId").is(getId()) .get(); // Remove all users of this realm - mongoStore.removeObjects(UserEntity.class, query, invContext); + context.getMongoStore().removeEntities(UserEntity.class, query, context); // Remove all roles of this realm - mongoStore.removeObjects(RoleEntity.class, query, invContext); + context.getMongoStore().removeEntities(RoleEntity.class, query, context); // Remove all applications of this realm - mongoStore.removeObjects(ApplicationEntity.class, query, invContext); + context.getMongoStore().removeEntities(ApplicationEntity.class, query, context); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java index 1aaeac2a1c..e2e97279dc 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RoleEntity.java @@ -74,13 +74,15 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo } @Override - public void afterRemove(MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public void afterRemove(MongoStoreInvocationContext invContext) { + MongoStore mongoStore = invContext.getMongoStore(); + // Remove this role from all users, which has it DBObject query = new QueryBuilder() .and("roleIds").is(getId()) .get(); - List users = mongoStore.loadObjects(UserEntity.class, query, invContext); + List users = mongoStore.loadEntities(UserEntity.class, query, invContext); for (UserEntity user : users) { logger.info("Removing role " + getName() + " from user " + user.getLoginName()); mongoStore.pullItemFromList(user, "roleIds", getId(), invContext); @@ -91,7 +93,7 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo .and("scopeIds").is(getId()) .get(); - users = mongoStore.loadObjects(UserEntity.class, query, invContext); + users = mongoStore.loadEntities(UserEntity.class, query, invContext); for (UserEntity user : users) { logger.info("Removing scope " + getName() + " from user " + user.getLoginName()); mongoStore.pullItemFromList(user, "scopeIds", getId(), invContext); @@ -99,7 +101,7 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo // Remove defaultRoles from realm if (realmId != null) { - RealmEntity realmEntity = mongoStore.loadObject(RealmEntity.class, realmId, invContext); + RealmEntity realmEntity = mongoStore.loadEntity(RealmEntity.class, realmId, invContext); // Realm might be already removed at this point if (realmEntity != null) { @@ -109,7 +111,7 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo // Remove defaultRoles from application if (applicationId != null) { - ApplicationEntity appEntity = mongoStore.loadObject(ApplicationEntity.class, applicationId, invContext); + ApplicationEntity appEntity = mongoStore.loadEntity(ApplicationEntity.class, applicationId, invContext); // Application might be already removed at this point if (appEntity != null) { @@ -121,7 +123,7 @@ public class RoleEntity extends AbstractMongoIdentifiableEntity implements Mongo query = new QueryBuilder() .and("compositeRoleIds").is(getId()) .get(); - List parentRoles = mongoStore.loadObjects(RoleEntity.class, query, invContext); + List parentRoles = mongoStore.loadEntities(RoleEntity.class, query, invContext); for (RoleEntity role : parentRoles) { mongoStore.pullItemFromList(role, "compositeRoleIds", getId(), invContext); } 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 deleted file mode 100644 index db445e396a..0000000000 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/EmbeddedMongo.java +++ /dev/null @@ -1,50 +0,0 @@ -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 index 7a22ae6417..7ba22d18f1 100644 --- 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 @@ -12,14 +12,12 @@ public class MongoConfiguration { private final String dbName; private final boolean clearCollectionsOnStartup; - private final boolean startEmbedded; - public MongoConfiguration(String host, int port, String dbName, boolean clearCollectionsOnStartup, boolean startEmbedded) { + public MongoConfiguration(String host, int port, String dbName, boolean clearCollectionsOnStartup) { this.host = host; this.port = port; this.dbName = dbName; this.clearCollectionsOnStartup = clearCollectionsOnStartup; - this.startEmbedded = startEmbedded; } public String getHost() { @@ -38,13 +36,9 @@ public class MongoConfiguration { 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); + return String.format("MongoConfiguration: host: %s, port: %d, dbName: %s, clearCollectionsOnStartup: %b", + host, port, dbName, clearCollectionsOnStartup); } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java index fe4387fc9a..4be29ecb34 100644 --- 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 @@ -9,7 +9,6 @@ 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.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.keycloak.adapters.UserAdapter; import org.keycloak.models.mongo.keycloak.entities.RoleEntity; @@ -29,7 +28,7 @@ public class MongoModelUtils { } // Get everything including both application and realm roles - public static List getAllRolesOfUser(UserModel user, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public static List getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) { UserEntity userEntity = ((UserAdapter)user).getUser(); List roleIds = userEntity.getRoleIds(); @@ -40,11 +39,11 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(convertStringsToObjectIds(roleIds)) .get(); - return mongoStore.loadObjects(RoleEntity.class, query, invContext); + return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext); } // Get everything including both application and realm scopes - public static List getAllScopesOfUser(UserModel user, MongoStore mongoStore, MongoStoreInvocationContext invContext) { + public static List getAllScopesOfUser(UserModel user, MongoStoreInvocationContext invContext) { UserEntity userEntity = ((UserAdapter)user).getUser(); List scopeIds = userEntity.getScopeIds(); @@ -55,6 +54,6 @@ public class MongoModelUtils { DBObject query = new QueryBuilder() .and("_id").in(convertStringsToObjectIds(scopeIds)) .get(); - return mongoStore.loadObjects(RoleEntity.class, query, invContext); + return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext); } } 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 index d30786fefd..ce24c63795 100755 --- 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 @@ -8,39 +8,39 @@ 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"; + private static final String MONGO_CLEAR_ON_STARTUP = "keycloak.mongo.clearOnStartup"; + + // Property names from Liveoak . Those are used as fallback in case that original value is not available + private static final String MONGO_HOST_2 = "mongo.host"; + private static final String MONGO_PORT_2 = "mongo.port"; + private static final String MONGO_DB_NAME_2 = "mongo.db"; + private static final String MONGO_CLEAR_ON_STARTUP_2 = "mongo.clearCollectionsOnStartup"; // 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; + private static final String MONGO_DEFAULT_PORT = "27017"; public static String getMongoHost() { - return System.getProperty(MONGO_HOST, "localhost"); + return getSystemPropertyWithFallback(MONGO_HOST, MONGO_HOST_2, "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; - } + String portProp = getSystemPropertyWithFallback(MONGO_PORT, MONGO_PORT_2, MONGO_DEFAULT_PORT); + return Integer.parseInt(portProp); } public static String getMongoDbName() { - return System.getProperty(MONGO_DB_NAME, "keycloak"); + return getSystemPropertyWithFallback(MONGO_DB_NAME, MONGO_DB_NAME_2, "keycloak"); } public static boolean isClearCollectionsOnStartup() { - return Boolean.parseBoolean(System.getProperty(MONGO_CLEAR_COLLECTIONS_ON_STARTUP, "true")); + String property = getSystemPropertyWithFallback(MONGO_CLEAR_ON_STARTUP, MONGO_CLEAR_ON_STARTUP_2, "false"); + return "true".equalsIgnoreCase(property); } - public static boolean isStartEmbedded() { - return Boolean.parseBoolean(System.getProperty(MONGO_START_EMBEDDED, "false")); + // Check if property propName1 (like "keycloak.mongo.host" is available and if not, then fallback to property "mongo.host" ) + private static String getSystemPropertyWithFallback(String propName1, String propName2, String defaultValue) { + String propValue1 = System.getProperty(propName1); + return propValue1!=null ? propValue1 : System.getProperty(propName2, defaultValue); } // Create configuration based on system properties @@ -49,8 +49,7 @@ public class SystemPropertiesConfigurationProvider { getMongoHost(), getMongoPort(), getMongoDbName(), - isClearCollectionsOnStartup(), - isStartEmbedded() + isClearCollectionsOnStartup() ); } } 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 b2d093de1b..7ee814131c 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 @@ -12,8 +12,8 @@ import org.keycloak.models.mongo.api.MongoEntity; import org.keycloak.models.mongo.api.MongoStore; import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.mongo.impl.MongoStoreImpl; -import org.keycloak.models.mongo.impl.context.SimpleMongoStoreInvocationContext; import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext; +import org.keycloak.models.mongo.utils.SystemPropertiesConfigurationProvider; import java.net.UnknownHostException; import java.util.ArrayList; @@ -37,7 +37,7 @@ public class MongoDBModelTest { public void before() throws Exception { try { // TODO: authentication support - mongoClient = new MongoClient("localhost", 27017); + mongoClient = new MongoClient("localhost", SystemPropertiesConfigurationProvider.getMongoPort()); DB db = mongoClient.getDB("keycloakTest"); mongoStore = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES); @@ -62,7 +62,7 @@ public class MongoDBModelTest { john.setAge(25); john.setGender(Person.Gender.MALE); - mongoStore.insertObject(john, context); + mongoStore.insertEntity(john, context); // Add another user Person mary = new Person(); @@ -83,12 +83,12 @@ public class MongoDBModelTest { mary.setGender(Person.Gender.FEMALE); mary.setGenders(asList(Person.Gender.FEMALE)); - mongoStore.insertObject(mary, context); + mongoStore.insertEntity(mary, context); - Assert.assertEquals(2, mongoStore.loadObjects(Person.class, new QueryBuilder().get(), context).size()); + Assert.assertEquals(2, mongoStore.loadEntities(Person.class, new QueryBuilder().get(), context).size()); DBObject query = new QueryBuilder().and("addresses.flatNumbers").is("flat1").get(); - List persons = mongoStore.loadObjects(Person.class, query, context); + List persons = mongoStore.loadEntities(Person.class, query, context); Assert.assertEquals(1, persons.size()); mary = persons.get(0); Assert.assertEquals(mary.getFirstName(), "mary"); @@ -105,7 +105,7 @@ public class MongoDBModelTest { addr3.setStreet("Broadway"); mongoStore.pushItemToList(mary, "addresses", addr3, true, context); - mary = mongoStore.loadObject(Person.class, mary.getId(), context); + mary = mongoStore.loadEntity(Person.class, mary.getId(), context); Assert.assertEquals(3, mary.getKids().size()); Assert.assertTrue(mary.getKids().contains("Pauline")); Assert.assertFalse(mary.getKids().contains("Paul")); @@ -121,16 +121,16 @@ public class MongoDBModelTest { mary.addAttribute("attr1", "value1"); mary.addAttribute("attr2", "value2"); mary.addAttribute("attr.some3", "value3"); - mongoStore.updateObject(mary, context); + mongoStore.updateEntity(mary, context); - mary = mongoStore.loadObject(Person.class, mary.getId(), context); + mary = mongoStore.loadEntity(Person.class, mary.getId(), context); Assert.assertEquals(3, mary.getAttributes().size()); mary.removeAttribute("attr2"); mary.removeAttribute("nonExisting"); - mongoStore.updateObject(mary, context); + mongoStore.updateEntity(mary, context); - mary = mongoStore.loadObject(Person.class, mary.getId(), context); + mary = mongoStore.loadEntity(Person.class, mary.getId(), context); 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/pom.xml b/model/pom.xml index 3301b71eaa..41d5be1f00 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -38,5 +38,6 @@ jpa mongo + tests diff --git a/model/tests/pom.xml b/model/tests/pom.xml new file mode 100644 index 0000000000..c4d733068f --- /dev/null +++ b/model/tests/pom.xml @@ -0,0 +1,69 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-2-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-tests + Keycloak Model Tests + + + + + org.keycloak + keycloak-services + ${project.version} + compile + + + junit + junit + compile + + + org.codehaus.jackson + jackson-core-asl + compile + + + org.codehaus.jackson + jackson-mapper-asl + compile + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + package-tests-jar + package + + test-jar + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + diff --git a/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java new file mode 100644 index 0000000000..1f2fda1be3 --- /dev/null +++ b/model/tests/src/test/java/org/keycloak/model/test/AbstractModelTest.java @@ -0,0 +1,55 @@ +package org.keycloak.model.test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.jboss.resteasy.logging.Logger; +import org.junit.After; +import org.junit.Before; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.util.JsonSerialization; + +/** + * @author Marek Posolda + */ +public class AbstractModelTest { + + private final Logger log = Logger.getLogger(getClass()); + + protected KeycloakSessionFactory factory; + protected KeycloakSession identitySession; + protected RealmManager realmManager; + + @Before + public void before() throws Exception { + factory = KeycloakApplication.createSessionFactory(); + identitySession = factory.createSession(); + identitySession.getTransaction().begin(); + realmManager = new RealmManager(identitySession); + } + + @After + public void after() throws Exception { + identitySession.getTransaction().commit(); + identitySession.close(); + factory.close(); + } + + public static RealmRepresentation loadJson(String path) throws IOException { + InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int c; + while ((c = is.read()) != -1) { + os.write(c); + } + byte[] bytes = os.toByteArray(); + System.out.println(new String(bytes)); + + return JsonSerialization.readValue(bytes, RealmRepresentation.class); + } +} diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java similarity index 95% rename from services/src/test/java/org/keycloak/test/AdapterTest.java rename to model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java index e729554d03..6bd1f07bd1 100755 --- a/services/src/test/java/org/keycloak/test/AdapterTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/AdapterTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.FixMethodOrder; @@ -23,36 +23,21 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.StringTokenizer; /** * @author Bill Burke * @version $Revision: 1 $ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class AdapterTest extends AbstractKeycloakTest { +public class AdapterTest extends AbstractModelTest { private RealmModel realmModel; - public AdapterTest(String providerId) { - super(providerId); - } - @Test public void installTest() throws Exception { new ApplianceBootstrap().bootstrap(identitySession); } - @Test - public void testMe() { - String hello = "Bill Burke"; - StringTokenizer tokenizer = new StringTokenizer(hello, " "); - while (tokenizer.hasMoreTokens()) { - System.out.println("token: " + tokenizer.nextToken()); - } - } - - @Test public void test1CreateRealm() throws Exception { realmModel = realmManager.createRealm("JUGGLER"); diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java similarity index 94% rename from services/src/test/java/org/keycloak/test/ApplicationModelTest.java rename to model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java index 783d9679ed..d2e460c1c7 100755 --- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ApplicationModelTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.Before; @@ -16,15 +16,11 @@ import java.util.List; /** * @author Stian Thorgersen */ -public class ApplicationModelTest extends AbstractKeycloakTest { +public class ApplicationModelTest extends AbstractModelTest { private ApplicationModel application; private RealmModel realm; private ApplicationManager appManager; - public ApplicationModelTest(String providerId) { - super(providerId); - } - @Before public void before() throws Exception { super.before(); diff --git a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java similarity index 95% rename from services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java rename to model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java index 0a9197bd0f..324802b4d1 100755 --- a/services/src/test/java/org/keycloak/services/managers/AuthenticationManagerTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java @@ -1,4 +1,4 @@ -package org.keycloak.services.managers; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.Before; @@ -9,14 +9,14 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus; -import org.keycloak.test.AbstractKeycloakTest; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.util.UUID; -public class AuthenticationManagerTest extends AbstractKeycloakTest { +public class AuthenticationManagerTest extends AbstractModelTest { private AuthenticationManager am; private MultivaluedMap formData; @@ -24,9 +24,6 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest { private RealmModel realm; private UserModel user; - public AuthenticationManagerTest(String providerId) { - super(providerId); - } @Test public void authForm() { AuthenticationStatus status = am.authenticateForm(realm, user, formData); diff --git a/services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java similarity index 64% rename from services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java rename to model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java index 898a183e29..1baf83e0a9 100644 --- a/services/src/test/java/org/keycloak/test/CompositeRolesModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/CompositeRolesModelTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import java.util.HashSet; import java.util.Set; @@ -16,17 +16,13 @@ import org.keycloak.services.managers.RealmManager; /** * @author Marek Posolda */ -public class CompositeRolesModelTest extends AbstractKeycloakTest { - - public CompositeRolesModelTest(String providerId) { - super(providerId); - } +public class CompositeRolesModelTest extends AbstractModelTest { @Before public void before() throws Exception { super.before(); RealmManager manager = realmManager; - RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testcomposites.json"); + RealmRepresentation rep = AbstractModelTest.loadJson("testcomposites.json"); RealmModel realm = manager.createRealm("Test", rep.getRealm()); manager.importRealm(rep, realm); } @@ -34,13 +30,42 @@ public class CompositeRolesModelTest extends AbstractKeycloakTest { @Test public void testAppComposites() { Set requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "APP_COMPOSITE_USER"); + Assert.assertEquals(2, requestedRoles.size()); + assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles); + assertContains("realm", "REALM_ROLE_1", requestedRoles); + } - RoleModel expectedRole1 = getRole("APP_ROLE_APPLICATION", "APP_ROLE_1"); - RoleModel expectedRole2 = getRole("realm", "REALM_ROLE_1"); + @Test + public void testRealmAppComposites() { + Set requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "REALM_APP_COMPOSITE_USER"); - assertContains(requestedRoles, expectedRole1); - assertContains(requestedRoles, expectedRole2); + Assert.assertEquals(1, requestedRoles.size()); + assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles); + } + + @Test + public void testRealmOnlyWithUserCompositeAppComposite() throws Exception { + Set requestedRoles = getRequestedRoles("REALM_COMPOSITE_1_APPLICATION", "REALM_COMPOSITE_1_USER"); + + Assert.assertEquals(1, requestedRoles.size()); + assertContains("realm", "REALM_COMPOSITE_1", requestedRoles); + } + + @Test + public void testRealmOnlyWithUserCompositeAppRole() throws Exception { + Set requestedRoles = getRequestedRoles("REALM_ROLE_1_APPLICATION", "REALM_COMPOSITE_1_USER"); + + Assert.assertEquals(1, requestedRoles.size()); + assertContains("realm", "REALM_ROLE_1", requestedRoles); + } + + @Test + public void testRealmOnlyWithUserRoleAppComposite() throws Exception { + Set requestedRoles = getRequestedRoles("REALM_COMPOSITE_1_APPLICATION", "REALM_ROLE_1_USER"); + + Assert.assertEquals(1, requestedRoles.size()); + assertContains("realm", "REALM_ROLE_1", requestedRoles); } // TODO: more tests... @@ -92,7 +117,9 @@ public class CompositeRolesModelTest extends AbstractKeycloakTest { } } - private void assertContains(Set requestedRoles, RoleModel expectedRole) { + private void assertContains(String appName, String roleName, Set requestedRoles) { + RoleModel expectedRole = getRole(appName, roleName); + Assert.assertTrue(requestedRoles.contains(expectedRole)); // Check if requestedRole has correct role container diff --git a/services/src/test/java/org/keycloak/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java similarity index 93% rename from services/src/test/java/org/keycloak/test/ImportTest.java rename to model/tests/src/test/java/org/keycloak/model/test/ImportTest.java index d8977ff037..6a4a908f9a 100755 --- a/services/src/test/java/org/keycloak/test/ImportTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.FixMethodOrder; @@ -23,16 +23,12 @@ import java.util.Set; * @version $Revision: 1 $ */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class ImportTest extends AbstractKeycloakTest { - - public ImportTest(String providerId) { - super(providerId); - } +public class ImportTest extends AbstractModelTest { @Test public void install() throws Exception { RealmManager manager = realmManager; - RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm.json"); + RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json"); RealmModel realm = manager.createRealm("demo", rep.getRealm()); manager.importRealm(rep, realm); @@ -148,7 +144,7 @@ public class ImportTest extends AbstractKeycloakTest { @Test public void install2() throws Exception { RealmManager manager = realmManager; - RealmRepresentation rep = AbstractKeycloakServerTest.loadJson("testrealm-demo.json"); + RealmRepresentation rep = AbstractModelTest.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/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java similarity index 95% rename from services/src/test/java/org/keycloak/test/ModelTest.java rename to model/tests/src/test/java/org/keycloak/model/test/ModelTest.java index b244d80c0b..bdc7d3b299 100755 --- a/services/src/test/java/org/keycloak/test/ModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/ModelTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.Test; @@ -12,11 +12,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -public class ModelTest extends AbstractKeycloakTest { - - public ModelTest(String providerId) { - super(providerId); - } +public class ModelTest extends AbstractModelTest { @Test public void importExportRealm() { diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/UserModelTest.java similarity index 96% rename from services/src/test/java/org/keycloak/test/UserModelTest.java rename to model/tests/src/test/java/org/keycloak/model/test/UserModelTest.java index cf504788f2..6567e19971 100755 --- a/services/src/test/java/org/keycloak/test/UserModelTest.java +++ b/model/tests/src/test/java/org/keycloak/model/test/UserModelTest.java @@ -1,4 +1,4 @@ -package org.keycloak.test; +package org.keycloak.model.test; import org.junit.Assert; import org.junit.Test; @@ -14,11 +14,7 @@ import java.util.List; /** * @author Stian Thorgersen */ -public class UserModelTest extends AbstractKeycloakTest { - - public UserModelTest(String providerId) { - super(providerId); - } +public class UserModelTest extends AbstractModelTest { @Test public void persistUser() { diff --git a/services/src/test/resources/testcomposites.json b/model/tests/src/test/resources/testcomposites.json similarity index 100% rename from services/src/test/resources/testcomposites.json rename to model/tests/src/test/resources/testcomposites.json diff --git a/services/src/test/resources/testrealm-demo.json b/model/tests/src/test/resources/testrealm-demo.json similarity index 100% rename from services/src/test/resources/testrealm-demo.json rename to model/tests/src/test/resources/testrealm-demo.json diff --git a/services/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json similarity index 100% rename from services/src/test/resources/testrealm.json rename to model/tests/src/test/resources/testrealm.json diff --git a/pom.xml b/pom.xml index fc58b75b2e..91b0818743 100755 --- a/pom.xml +++ b/pom.xml @@ -313,11 +313,6 @@ mongo-java-driver 2.11.2 - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - 1.40 - org.apache.jmeter ApacheJMeter_java @@ -395,7 +390,7 @@ org.apache.maven.plugins maven-surefire-plugin - + 2.16 once -Xms512m -Xmx512m @@ -479,6 +474,11 @@ exec-maven-plugin 1.2.1 + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + 0.1.10 + diff --git a/services/pom.xml b/services/pom.xml index de0ae59da7..969dc68737 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -56,41 +56,6 @@ keycloak-jaxrs-oauth-client ${project.version} - - - org.keycloak - keycloak-model-jpa - ${project.version} - test - - - - - - org.keycloak - keycloak-model-mongo - ${project.version} - test - - - org.mongodb - mongo-java-driver - test - - - org.picketlink - picketlink-common - test - - - org.keycloak keycloak-social-core @@ -141,21 +106,6 @@ resteasy-multipart-provider provided - - org.jboss.resteasy - resteasy-undertow - test - - - io.undertow - undertow-servlet - test - - - io.undertow - undertow-core - test - org.codehaus.jackson jackson-core-asl @@ -185,21 +135,6 @@ junit test - - org.hibernate.javax.persistence - hibernate-jpa-2.0-api - test - - - com.h2database - h2 - test - - - org.hibernate - hibernate-entitymanager - test - com.icegreen greenmail 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 080e28f167..83bdaca8dd 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -8,7 +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 org.keycloak.models.utils.ModelProviderUtils; import javax.servlet.ServletContext; import javax.ws.rs.core.Application; diff --git a/services/src/test/java/org/keycloak/test/AbstractKeycloakServerTest.java b/services/src/test/java/org/keycloak/test/AbstractKeycloakServerTest.java deleted file mode 100755 index 96dbd49dc3..0000000000 --- a/services/src/test/java/org/keycloak/test/AbstractKeycloakServerTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.keycloak.test; - -import io.undertow.servlet.Servlets; -import io.undertow.servlet.api.DeploymentInfo; -import io.undertow.servlet.api.FilterInfo; -import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; -import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; -import org.jboss.resteasy.spi.ResteasyDeployment; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.keycloak.SkeletonKeyContextResolver; -import org.keycloak.util.JsonSerialization; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.services.filters.KeycloakSessionServletFilter; -import org.keycloak.services.resources.KeycloakApplication; - -import javax.servlet.DispatcherType; -import javax.ws.rs.client.Client; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class AbstractKeycloakServerTest { - public static UndertowJaxrsServer server; - public static ResteasyDeployment deployment; - public static Client client; - public static KeycloakApplication application; - - @BeforeClass - public static void undertowSetup() throws Exception { - deployment = new ResteasyDeployment(); - deployment.setApplicationClass(KeycloakApplication.class.getName()); - server = new UndertowJaxrsServer().start(); - DeploymentInfo di = server.undertowDeployment(deployment); - di.setClassLoader(AbstractKeycloakServerTest.class.getClassLoader()); - di.setContextPath("/"); - di.setDeploymentName("Keycloak"); - - FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class); - di.addFilter(filter); - di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST); - server.deploy(di); - application = (KeycloakApplication) deployment.getApplication(); - client = new ResteasyClientBuilder().connectionPoolSize(10).build(); - client.register(SkeletonKeyContextResolver.class); - - } - - @AfterClass - public static void undertowShutdown() throws Exception { - server.stop(); - } - - public static RealmRepresentation loadJson(String path) throws IOException { - InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int c; - while ((c = is.read()) != -1) { - os.write(c); - } - byte[] bytes = os.toByteArray(); - System.out.println(new String(bytes)); - - return JsonSerialization.readValue(bytes, RealmRepresentation.class); - } -} diff --git a/services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java b/services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java deleted file mode 100755 index d33ea17305..0000000000 --- a/services/src/test/java/org/keycloak/test/AbstractKeycloakTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.keycloak.test; - -import org.jboss.resteasy.logging.Logger; -import org.junit.After; -import org.junit.Before; -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; - -/** - * @author Marek Posolda - */ -@RunWith(Parameterized.class) -public abstract class AbstractKeycloakTest { - - private static final Logger log = Logger.getLogger(AbstractKeycloakTest.class); - - protected KeycloakSessionFactory factory; - protected KeycloakSession identitySession; - protected RealmManager realmManager; - - @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 (ModelProvider provider : modelProviders) { - params.add(new Object[] { provider.getId() }); - } - return params; - } - - - public AbstractKeycloakTest(String providerId) { - System.setProperty(ModelProviderUtils.MODEL_PROVIDER, providerId); - } - - @Before - public void before() throws Exception { - factory = KeycloakApplication.createSessionFactory(); - identitySession = factory.createSession(); - identitySession.getTransaction().begin(); - realmManager = new RealmManager(identitySession); - } - - @After - public void after() throws Exception { - identitySession.getTransaction().commit(); - identitySession.close(); - factory.close(); - } - -} diff --git a/testsuite/integration/README.md b/testsuite/integration/README.md index 642865706d..1b3ed3e802 100644 --- a/testsuite/integration/README.md +++ b/testsuite/integration/README.md @@ -8,6 +8,13 @@ The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome` +Mongo +----- + +The testsuite is executed with JPA model implementation with data saved in H2 database by default. To run testsuite with Mongo model, just add property `-Dkeycloak.model=mongo` when executing it. + +Note that this will automatically run embedded Mongo database on localhost/27018 and it will stop it after whole testsuite is finished. +So you don't need to have Mongo installed on your laptop to run mongo execution tests. Test utils ========== @@ -41,6 +48,16 @@ For example to use the example themes run the server with: **NOTE:** If `keycloak.theme.dir` is specified the default themes (base, rcue and keycloak) are loaded from the classpath +### Run server with Mongo model + +To start a Keycloak server with identity model data persisted in Mongo database instead of default JPA/H2 you can run: + + mvn exec:java -Pkeycloak-server -Dkeycloak.model=mongo + +By default it's using database `keycloak` on localhost/27017 and it uses already existing data from this DB (no cleanup of existing data during bootstrap). Assumption is that you already have DB running on localhost/27017 . Use system properties to configure things differently: + + mvn exec:java -Pkeycloak-server -Dkeycloak.model=mongo -Dkeycloak.mongo.host=localhost -Dkeycloak.mongo.port=27017 -Dkeycloak.mongo.db=keycloak -Dkeycloak.mongo.clearCollectionsOnStartup=false + TOTP codes ---------- diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 9489da1dc5..d668ee8389 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -350,11 +350,72 @@ localhost - 27017 + 27018 keycloak - true + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + test + integration-test + + test + + + + ${keycloak.mongo.host} + ${keycloak.mongo.port} + ${keycloak.mongo.db} + ${keycloak.mongo.clearOnStartup} + + + + + default-test + + true + + + + + + + + com.github.joelittlejohn.embedmongo + embedmongo-maven-plugin + + + start-mongodb + pre-integration-test + + start + + + ${keycloak.mongo.port} + file + ${project.build.directory}/mongodb.log + + + + stop-mongodb + post-integration-test + + stop + + + + + + + \ No newline at end of file