Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
b39ef41e3e
121 changed files with 4454 additions and 3863 deletions
|
@ -17,6 +17,17 @@
|
|||
<groupId>net.iharder</groupId>
|
||||
<artifactId>base64</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk16</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class IdGenerator {
|
||||
private static AtomicLong counter = new AtomicLong(1);
|
||||
public static String generateId() {
|
||||
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.util.PemUtils;
|
||||
|
||||
/**
|
||||
* Set of helper methods, which are useful in various model implementations.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public final class KeycloakModelUtils {
|
||||
|
||||
private KeycloakModelUtils() {
|
||||
}
|
||||
|
||||
private static AtomicLong counter = new AtomicLong(1);
|
||||
|
||||
public static String generateId() {
|
||||
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static PublicKey getPublicKey(String publicKeyPem) {
|
||||
if (publicKeyPem != null) {
|
||||
try {
|
||||
return PemUtils.decodePublicKey(publicKeyPem);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PrivateKey getPrivateKey(String privateKeyPem) {
|
||||
if (privateKeyPem != null) {
|
||||
try {
|
||||
return PemUtils.decodePrivateKey(privateKeyPem);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getPemFromKey(Key key) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PEMWriter pemWriter = new PEMWriter(writer);
|
||||
try {
|
||||
pemWriter.writeObject(key);
|
||||
pemWriter.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
String s = writer.toString();
|
||||
return PemUtils.removeBeginEnd(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep search if given role is descendant of composite role
|
||||
*
|
||||
* @param role role to check
|
||||
* @param composite composite role
|
||||
* @param visited set of already visited roles (used for recursion)
|
||||
* @return true if "role" is descendant of "composite"
|
||||
*/
|
||||
public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
|
||||
if (visited.contains(composite)) return false;
|
||||
visited.add(composite);
|
||||
Set<RoleModel> composites = composite.getComposites();
|
||||
if (composites.contains(role)) return true;
|
||||
for (RoleModel contained : composites) {
|
||||
if (!contained.isComposite()) continue;
|
||||
if (searchFor(role, contained, visited)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public final class KeycloakSessionUtils {
|
||||
|
||||
private KeycloakSessionUtils() {
|
||||
}
|
||||
|
||||
private static AtomicLong counter = new AtomicLong(1);
|
||||
|
||||
public static String generateId() {
|
||||
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.keycloak.models.ModelProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ModelProviderUtils {
|
||||
|
||||
public static final String MODEL_PROVIDER = "keycloak.model";
|
||||
public static final String DEFAULT_MODEL_PROVIDER = "jpa";
|
||||
|
||||
public static Iterable<ModelProvider> getRegisteredProviders() {
|
||||
return ServiceLoader.load(ModelProvider.class);
|
||||
}
|
||||
|
||||
public static ModelProvider getConfiguredModelProvider(Iterable<ModelProvider> providers) {
|
||||
String configuredProvider = System.getProperty(MODEL_PROVIDER);
|
||||
ModelProvider provider = null;
|
||||
|
||||
if (configuredProvider != null) {
|
||||
for (ModelProvider p : providers) {
|
||||
if (p.getId().equals(configuredProvider)) {
|
||||
provider = p;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (ModelProvider p : providers) {
|
||||
if (provider == null) {
|
||||
provider = p;
|
||||
}
|
||||
|
||||
if (p.getId().equals(DEFAULT_MODEL_PROVIDER)) {
|
||||
provider = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static ModelProvider getConfiguredModelProvider() {
|
||||
return getConfiguredModelProvider(getRegisteredProviders());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -59,6 +59,20 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -70,6 +84,22 @@
|
|||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-test</id>
|
||||
<configuration>
|
||||
<dependenciesToScan>
|
||||
<dependency>org.keycloak:keycloak-model-tests</dependency>
|
||||
</dependenciesToScan>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmRoleEntity;
|
||||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.HashSet;
|
||||
|
@ -94,27 +95,13 @@ public class RoleAdapter implements RoleModel {
|
|||
return set;
|
||||
}
|
||||
|
||||
public static boolean searchFor(RoleModel role, RoleModel composite, Set<RoleModel> visited) {
|
||||
if (visited.contains(composite)) return false;
|
||||
visited.add(composite);
|
||||
Set<RoleModel> composites = composite.getComposites();
|
||||
if (composites.contains(role)) return true;
|
||||
for (RoleModel contained : composites) {
|
||||
if (!contained.isComposite()) continue;
|
||||
if (searchFor(role, contained, visited)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return searchFor(role, this, visited);
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-1</version>
|
||||
<version>1.0-alpha-2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
@ -41,27 +41,28 @@
|
|||
<artifactId>picketlink-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-idm-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-tests</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<keycloak.mongo.host>localhost</keycloak.mongo.host>
|
||||
<keycloak.mongo.port>27018</keycloak.mongo.port>
|
||||
<keycloak.mongo.db>keycloak</keycloak.mongo.db>
|
||||
<keycloak.mongo.clearOnStartup>true</keycloak.mongo.clearOnStartup>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
@ -72,6 +73,66 @@
|
|||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<keycloak.mongo.host>${keycloak.mongo.host}</keycloak.mongo.host>
|
||||
<keycloak.mongo.port>${keycloak.mongo.port}</keycloak.mongo.port>
|
||||
<keycloak.mongo.db>${keycloak.mongo.db}</keycloak.mongo.db>
|
||||
<keycloak.mongo.clearOnStartup>${keycloak.mongo.clearOnStartup}</keycloak.mongo.clearOnStartup>
|
||||
</systemPropertyVariables>
|
||||
<dependenciesToScan>
|
||||
<dependency>org.keycloak:keycloak-model-tests</dependency>
|
||||
</dependenciesToScan>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-test</id>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Embedded mongo -->
|
||||
<plugin>
|
||||
<groupId>com.github.joelittlejohn.embedmongo</groupId>
|
||||
<artifactId>embedmongo-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>start-mongodb</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<port>${keycloak.mongo.port}</port>
|
||||
<logging>file</logging>
|
||||
<logFile>${project.build.directory}/mongodb.log</logFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>stop-mongodb</id>
|
||||
<phase>post-integration-test</phase>
|
||||
<goals>
|
||||
<goal>stop</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -1,57 +0,0 @@
|
|||
package org.keycloak.test.common;
|
||||
|
||||
import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||
import de.flapdoodle.embed.mongo.MongodProcess;
|
||||
import de.flapdoodle.embed.mongo.MongodStarter;
|
||||
import de.flapdoodle.embed.mongo.config.MongodConfig;
|
||||
import de.flapdoodle.embed.mongo.distribution.Version;
|
||||
import de.flapdoodle.embed.process.runtime.Network;
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.keycloak.services.utils.PropertiesManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoDBSessionFactoryTestContext implements SessionFactoryTestContext {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(MongoDBSessionFactoryTestContext.class);
|
||||
private static final int PORT = PropertiesManager.MONGO_DEFAULT_PORT_UNIT_TESTS;
|
||||
|
||||
private MongodExecutable mongodExe;
|
||||
private MongodProcess mongod;
|
||||
|
||||
@Override
|
||||
public void beforeTestClass() {
|
||||
logger.info("Bootstrapping MongoDB on localhost, port " + PORT);
|
||||
try {
|
||||
mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, PORT, Network.localhostIsIPv6()));
|
||||
mongod = mongodExe.start();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
logger.info("MongoDB bootstrapped successfully");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass() {
|
||||
if (mongodExe != null) {
|
||||
if (mongod != null) {
|
||||
mongod.stop();
|
||||
}
|
||||
mongodExe.stop();
|
||||
}
|
||||
logger.info("MongoDB stopped successfully");
|
||||
|
||||
// Reset this, so other tests are not affected
|
||||
PropertiesManager.setDefaultSessionFactoryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initEnvironment() {
|
||||
PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_MONGO);
|
||||
PropertiesManager.setMongoHost("localhost");
|
||||
PropertiesManager.setMongoPort(PORT);
|
||||
PropertiesManager.setMongoDbName("keycloakTest");
|
||||
PropertiesManager.setDropDatabaseOnStartup(true);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package org.keycloak.services.listeners;
|
||||
|
||||
import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||
import de.flapdoodle.embed.mongo.MongodProcess;
|
||||
import de.flapdoodle.embed.mongo.MongodStarter;
|
||||
import de.flapdoodle.embed.mongo.config.MongodConfig;
|
||||
import de.flapdoodle.embed.mongo.distribution.Version;
|
||||
import de.flapdoodle.embed.process.runtime.Network;
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.keycloak.services.utils.PropertiesManager;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoRunnerListener implements ServletContextListener {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(MongoRunnerListener.class);
|
||||
|
||||
private MongodExecutable mongodExe;
|
||||
private MongodProcess mongod;
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
if (PropertiesManager.bootstrapEmbeddedMongoAtContextInit()) {
|
||||
int port = PropertiesManager.getMongoPort();
|
||||
logger.info("Going to start embedded MongoDB on port=" + port);
|
||||
|
||||
try {
|
||||
mongodExe = MongodStarter.getDefaultInstance().prepare(new MongodConfig(Version.V2_0_5, port, Network.localhostIsIPv6()));
|
||||
mongod = mongodExe.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Couldn't start Embedded Mongo on port " + port + ". Maybe it's already started? Cause: " + e.getClass() + " " + e.getMessage());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to start MongoDB", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
if (mongodExe != null) {
|
||||
if (mongod != null) {
|
||||
logger.info("Going to stop embedded MongoDB.");
|
||||
mongod.stop();
|
||||
}
|
||||
mongodExe.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package org.keycloak.models.mongo;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PropertiesManager {
|
||||
|
||||
private static final String MONGO_HOST = "keycloak.mongodb.host";
|
||||
private static final String MONGO_PORT = "keycloak.mongodb.port";
|
||||
private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
|
||||
private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
|
||||
private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
|
||||
|
||||
// Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
|
||||
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
|
||||
|
||||
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
|
||||
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
|
||||
|
||||
// Port where unit tests will start embedded MongoDB instance
|
||||
public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
|
||||
|
||||
public static String getMongoHost() {
|
||||
return System.getProperty(MONGO_HOST, "localhost");
|
||||
}
|
||||
|
||||
public static void setMongoHost(String mongoHost) {
|
||||
System.setProperty(MONGO_HOST, mongoHost);
|
||||
}
|
||||
|
||||
public static int getMongoPort() {
|
||||
return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
|
||||
}
|
||||
|
||||
public static void setMongoPort(int mongoPort) {
|
||||
System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
|
||||
}
|
||||
|
||||
public static String getMongoDbName() {
|
||||
return System.getProperty(MONGO_DB_NAME, "keycloak");
|
||||
}
|
||||
|
||||
public static void setMongoDbName(String mongoMongoDbName) {
|
||||
System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
|
||||
}
|
||||
|
||||
public static boolean dropDatabaseOnStartup() {
|
||||
return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
|
||||
}
|
||||
|
||||
public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
|
||||
System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
|
||||
}
|
||||
|
||||
public static boolean bootstrapEmbeddedMongoAtContextInit() {
|
||||
return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractAttributedNoSQLObject extends AbstractNoSQLObject implements AttributedNoSQLObject {
|
||||
|
||||
// Simple hashMap for now (no thread-safe)
|
||||
private Map<String, String> attributes = new HashMap<String, String>();
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
// attributes.remove(name);
|
||||
|
||||
// ensure that particular attribute has null value, so it will be deleted in DB. TODO: needs to be improved
|
||||
attributes.put(name, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.unmodifiableMap(attributes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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(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());
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractNoSQLObject implements NoSQLObject {
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
// Empty by default
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface AttributedNoSQLObject extends NoSQLObject {
|
||||
|
||||
void setAttribute(String name, String value);
|
||||
|
||||
void removeAttribute(String name);
|
||||
|
||||
String getAttribute(String name);
|
||||
|
||||
Map<String, String> getAttributes();
|
||||
}
|
|
@ -15,7 +15,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface NoSQLCollection {
|
||||
public @interface MongoCollection {
|
||||
|
||||
String collectionName();
|
||||
}
|
|
@ -0,0 +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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface MongoEntity {
|
||||
}
|
|
@ -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?
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* Entity with Id
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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(MongoStoreInvocationContext invocationContext);
|
||||
}
|
43
model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java
Executable file
43
model/mongo/src/main/java/org/keycloak/models/mongo/api/MongoStore.java
Executable file
|
@ -0,0 +1,43 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface MongoStore {
|
||||
|
||||
/**
|
||||
* Insert new entity
|
||||
*
|
||||
* @param entity to insert
|
||||
*/
|
||||
void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
|
||||
|
||||
/**
|
||||
* Update existing entity
|
||||
*
|
||||
* @param entity to update
|
||||
*/
|
||||
void updateEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
|
||||
|
||||
|
||||
<T extends MongoIdentifiableEntity> T loadEntity(Class<T> type, String id, MongoStoreInvocationContext context);
|
||||
|
||||
<T extends MongoIdentifiableEntity> T loadSingleEntity(Class<T> type, DBObject query, MongoStoreInvocationContext context);
|
||||
|
||||
<T extends MongoIdentifiableEntity> List<T> loadEntities(Class<T> type, DBObject query, MongoStoreInvocationContext context);
|
||||
|
||||
boolean removeEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
|
||||
|
||||
boolean removeEntity(Class<? extends MongoIdentifiableEntity> type, String id, MongoStoreInvocationContext context);
|
||||
|
||||
boolean removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, MongoStoreInvocationContext context);
|
||||
|
||||
<S> boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context);
|
||||
|
||||
<S> boolean pullItemFromList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPull, MongoStoreInvocationContext context);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface NoSQL {
|
||||
|
||||
/**
|
||||
* Insert object if it's oid is null. Otherwise update
|
||||
*/
|
||||
void saveObject(NoSQLObject object);
|
||||
|
||||
<T extends NoSQLObject> T loadObject(Class<T> type, String oid);
|
||||
|
||||
<T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query);
|
||||
|
||||
<T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query);
|
||||
|
||||
// Object must have filled oid
|
||||
void removeObject(NoSQLObject object);
|
||||
|
||||
void removeObject(Class<? extends NoSQLObject> type, String oid);
|
||||
|
||||
void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query);
|
||||
|
||||
NoSQLQueryBuilder createQueryBuilder();
|
||||
|
||||
<S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush);
|
||||
|
||||
<S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull);
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@Target({METHOD, FIELD})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
public @interface NoSQLId {
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
/**
|
||||
* Base interface for object, which is persisted in NoSQL database
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface NoSQLObject {
|
||||
|
||||
/**
|
||||
* Lifecycle callback, which is called after removal of this object from NoSQL database.
|
||||
* It may be useful for triggering removal of wired objects.
|
||||
*/
|
||||
void afterRemove(NoSQL noSQL);
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface MongoStoreInvocationContext {
|
||||
|
||||
void addCreatedObject(MongoIdentifiableEntity entity);
|
||||
|
||||
void addLoadedObject(MongoIdentifiableEntity entity);
|
||||
|
||||
<T extends MongoIdentifiableEntity> T getLoadedObject(Class<T> type, String id);
|
||||
|
||||
void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task);
|
||||
|
||||
void addRemovedObject(MongoIdentifiableEntity entityToRemove);
|
||||
|
||||
void beforeDBSearch(Class<? extends MongoIdentifiableEntity> entityType);
|
||||
|
||||
void begin();
|
||||
|
||||
void commit();
|
||||
|
||||
void rollback();
|
||||
|
||||
MongoStore getMongoStore();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.keycloak.models.mongo.api.context;
|
||||
|
||||
import org.keycloak.models.mongo.api.MongoStore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface MongoTask {
|
||||
|
||||
void execute();
|
||||
|
||||
boolean isFullUpdate();
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package org.keycloak.models.mongo.api.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class NoSQLQuery {
|
||||
|
||||
private final Map<String, Object> queryAttributes;
|
||||
|
||||
NoSQLQuery(Map<String, Object> queryAttributes) {
|
||||
this.queryAttributes = queryAttributes;
|
||||
};
|
||||
|
||||
public Map<String, Object> getQueryAttributes() {
|
||||
return Collections.unmodifiableMap(queryAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NoSQLQuery [" + queryAttributes + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package org.keycloak.models.mongo.api.query;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class NoSQLQueryBuilder {
|
||||
|
||||
private Map<String, Object> queryAttributes = new HashMap<String, Object>();
|
||||
|
||||
protected NoSQLQueryBuilder() {};
|
||||
|
||||
public NoSQLQuery build() {
|
||||
return new NoSQLQuery(queryAttributes);
|
||||
}
|
||||
|
||||
public NoSQLQueryBuilder andCondition(String name, Object value) {
|
||||
this.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract NoSQLQueryBuilder inCondition(String name, List<?> values);
|
||||
|
||||
protected void put(String name, Object value) {
|
||||
queryAttributes.put(name, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
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
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface Converter<T, S> {
|
||||
|
||||
S convertObject(T objectToConvert);
|
||||
|
||||
Class<? extends T> getConverterObjectType();
|
||||
|
||||
Class<S> getExpectedReturnType();
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
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 mappers should be registered in TypeMapper, which is main entry point to be used by application
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface Mapper<T, S> {
|
||||
|
||||
/**
|
||||
* Convert object from one type to expected type
|
||||
*
|
||||
* @param mapperContext Encapsulates reference to converted object and other things, which might be helpful in conversion
|
||||
* @return converted object
|
||||
*/
|
||||
S convertObject(MapperContext<T, S> mapperContext);
|
||||
|
||||
Class<? extends T> getTypeOfObjectToConvert();
|
||||
|
||||
Class<S> getExpectedReturnType();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.keycloak.models.mongo.api.types;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MapperContext<T, S> {
|
||||
|
||||
// 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<? extends S> expectedReturnType;
|
||||
|
||||
// in case that expected return type is generic type (like "List<String>"), then genericTypes could contain list of expected generic arguments
|
||||
private final List<Class<?>> genericTypes;
|
||||
|
||||
public MapperContext(T objectToConvert, Class<? extends S> expectedReturnType, List<Class<?>> genericTypes) {
|
||||
this.objectToConvert = objectToConvert;
|
||||
this.expectedReturnType = expectedReturnType;
|
||||
this.genericTypes = genericTypes;
|
||||
}
|
||||
|
||||
public T getObjectToConvert() {
|
||||
return objectToConvert;
|
||||
}
|
||||
|
||||
public Class<? extends S> getExpectedReturnType() {
|
||||
return expectedReturnType;
|
||||
}
|
||||
|
||||
public List<Class<?>> getGenericTypes() {
|
||||
return genericTypes;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MapperRegistry {
|
||||
|
||||
// TODO: Thread-safety support (maybe...)
|
||||
// Mappers of Application objects to DB objects
|
||||
private Map<Class<?>, Mapper<?, ?>> appObjectMappers = new HashMap<Class<?>, Mapper<?, ?>>();
|
||||
|
||||
// Mappers of DB objects to Application objects
|
||||
private Map<Class<?>, Map<Class<?>, Mapper<?, ?>>> dbObjectMappers = new HashMap<Class<?>, Map<Class<?>, 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<Class<?>, Mapper<?, ?>> appObjects = dbObjectMappers.get(dbObjectType);
|
||||
if (appObjects == null) {
|
||||
appObjects = new HashMap<Class<?>, Mapper<?, ?>>();
|
||||
dbObjectMappers.put(dbObjectType, appObjects);
|
||||
}
|
||||
appObjects.put(appObjectType, mapper);
|
||||
}
|
||||
|
||||
|
||||
public <S> S convertDBObjectToApplicationObject(MapperContext<Object, S> context) {
|
||||
Object dbObject = context.getObjectToConvert();
|
||||
Class<?> expectedApplicationObjectType = context.getExpectedReturnType();
|
||||
|
||||
Class<?> dbObjectType = dbObject.getClass();
|
||||
Mapper<Object, S> mapper;
|
||||
|
||||
Map<Class<?>, 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<Object, S>)appObjects.values().iterator().next();
|
||||
} else {
|
||||
// Try to find converter for requested application type
|
||||
mapper = (Mapper<Object, S>)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> S convertApplicationObjectToDBObject(Object applicationObject, Class<S> expectedDBObjectType) {
|
||||
Class<?> appObjectType = applicationObject.getClass();
|
||||
Mapper<Object, S> mapper = (Mapper<Object, S>)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<Object, ?> getAppConverterForType(Class<?> appObjectType, Map<Class<?>, Mapper<?, ?>> appObjectConverters) {
|
||||
Mapper<Object, ?> mapper = (Mapper<Object, ?>)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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,112 +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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TypeConverter {
|
||||
|
||||
// TODO: Thread-safety support (maybe...)
|
||||
// Converters of Application objects to DB objects
|
||||
private Map<Class<?>, Converter<?, ?>> appObjectConverters = new HashMap<Class<?>, Converter<?, ?>>();
|
||||
|
||||
// Converters of DB objects to Application objects
|
||||
private Map<Class<?>, Map<Class<?>, Converter<?, ?>>> dbObjectConverters = new HashMap<Class<?>, Map<Class<?>, 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<Class<?>, Converter<?, ?>> appObjects = dbObjectConverters.get(dbObjectType);
|
||||
if (appObjects == null) {
|
||||
appObjects = new HashMap<Class<?>, Converter<?, ?>>();
|
||||
dbObjectConverters.put(dbObjectType, appObjects);
|
||||
}
|
||||
appObjects.put(appObjectType, converter);
|
||||
}
|
||||
|
||||
|
||||
public <S> S convertDBObjectToApplicationObject(Object dbObject, Class<S> expectedApplicationObjectType) {
|
||||
Class<?> dbObjectType = dbObject.getClass();
|
||||
Converter<Object, S> converter;
|
||||
|
||||
Map<Class<?>, Converter<?, ?>> appObjects = dbObjectConverters.get(dbObjectType);
|
||||
if (appObjects == null) {
|
||||
throw new IllegalArgumentException("Not found any converters for type " + dbObjectType);
|
||||
} else {
|
||||
if (appObjects.size() == 1) {
|
||||
converter = (Converter<Object, S>)appObjects.values().iterator().next();
|
||||
} else {
|
||||
// Try to find converter for requested application type
|
||||
converter = (Converter<Object, S>)getAppConverterForType(expectedApplicationObjectType, 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(dbObject);
|
||||
}
|
||||
|
||||
|
||||
public <S> S convertApplicationObjectToDBObject(Object applicationObject, Class<S> expectedDBObjectType) {
|
||||
Class<?> appObjectType = applicationObject.getClass();
|
||||
Converter<Object, S> converter = (Converter<Object, S>)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(applicationObject);
|
||||
}
|
||||
|
||||
// Try to find converter for given type or all it's supertypes
|
||||
private static Converter<Object, ?> getAppConverterForType(Class<?> appObjectType, Map<Class<?>, Converter<?, ?>> appObjectConverters) {
|
||||
Converter<Object, ?> converter = (Converter<Object, ?>)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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -12,20 +12,17 @@ import java.util.Map;
|
|||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ObjectInfo {
|
||||
public class EntityInfo {
|
||||
|
||||
private final Class<? extends NoSQLObject> objectClass;
|
||||
private final Class<? extends MongoEntity> objectClass;
|
||||
|
||||
private final String dbCollectionName;
|
||||
|
||||
private final Property<String> oidProperty;
|
||||
|
||||
private final Map<String, Property<Object>> properties;
|
||||
|
||||
public ObjectInfo(Class<? extends NoSQLObject> objectClass, String dbCollectionName, Property<String> oidProperty, List<Property<Object>> properties) {
|
||||
public EntityInfo(Class<? extends MongoEntity> objectClass, String dbCollectionName, List<Property<Object>> properties) {
|
||||
this.objectClass = objectClass;
|
||||
this.dbCollectionName = dbCollectionName;
|
||||
this.oidProperty = oidProperty;
|
||||
|
||||
Map<String, Property<Object>> props= new HashMap<String, Property<Object>>();
|
||||
for (Property<Object> property : properties) {
|
||||
|
@ -34,7 +31,7 @@ public class ObjectInfo {
|
|||
this.properties = Collections.unmodifiableMap(props);
|
||||
}
|
||||
|
||||
public Class<? extends NoSQLObject> getObjectClass() {
|
||||
public Class<? extends MongoEntity> getObjectClass() {
|
||||
return objectClass;
|
||||
}
|
||||
|
||||
|
@ -42,10 +39,6 @@ public class ObjectInfo {
|
|||
return dbCollectionName;
|
||||
}
|
||||
|
||||
public Property<String> getOidProperty() {
|
||||
return oidProperty;
|
||||
}
|
||||
|
||||
public Collection<Property<Object>> getProperties() {
|
||||
return properties.values();
|
||||
}
|
|
@ -1,324 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||
import org.keycloak.models.mongo.api.types.Converter;
|
||||
import org.keycloak.models.mongo.api.types.TypeConverter;
|
||||
import org.keycloak.models.mongo.impl.types.BasicDBListConverter;
|
||||
import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter;
|
||||
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.SimpleConverter;
|
||||
import org.keycloak.models.mongo.impl.types.StringToEnumConverter;
|
||||
import org.picketlink.common.properties.Property;
|
||||
import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
|
||||
import org.picketlink.common.properties.query.PropertyQueries;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoDBImpl implements NoSQL {
|
||||
|
||||
private static final Class<?>[] SIMPLE_TYPES = { String.class, Integer.class, Boolean.class, Long.class, Double.class, Character.class, Date.class };
|
||||
|
||||
private final DB database;
|
||||
private static final Logger logger = Logger.getLogger(MongoDBImpl.class);
|
||||
|
||||
private final TypeConverter typeConverter;
|
||||
private ConcurrentMap<Class<? extends NoSQLObject>, ObjectInfo> objectInfoCache =
|
||||
new ConcurrentHashMap<Class<? extends NoSQLObject>, ObjectInfo>();
|
||||
|
||||
|
||||
public MongoDBImpl(DB database, boolean dropDatabaseOnStartup, Class<? extends NoSQLObject>[] managedDataTypes) {
|
||||
this.database = database;
|
||||
|
||||
typeConverter = new TypeConverter();
|
||||
|
||||
for (Class<?> simpleConverterClass : SIMPLE_TYPES) {
|
||||
SimpleConverter converter = new SimpleConverter(simpleConverterClass);
|
||||
typeConverter.addAppObjectConverter(converter);
|
||||
typeConverter.addDBObjectConverter(converter);
|
||||
}
|
||||
|
||||
// Specific converter for ArrayList is added just for performance purposes to avoid recursive converter lookup (most of list impl will be ArrayList)
|
||||
typeConverter.addAppObjectConverter(new ListConverter(typeConverter, ArrayList.class));
|
||||
typeConverter.addAppObjectConverter(new ListConverter(typeConverter, List.class));
|
||||
typeConverter.addDBObjectConverter(new BasicDBListConverter(typeConverter));
|
||||
|
||||
// Enum converters
|
||||
typeConverter.addAppObjectConverter(new EnumToStringConverter());
|
||||
typeConverter.addDBObjectConverter(new StringToEnumConverter());
|
||||
|
||||
for (Class<? extends NoSQLObject> type : managedDataTypes) {
|
||||
getObjectInfo(type);
|
||||
typeConverter.addAppObjectConverter(new NoSQLObjectConverter(this, typeConverter, type));
|
||||
typeConverter.addDBObjectConverter(new BasicDBObjectConverter(this, typeConverter, type));
|
||||
}
|
||||
|
||||
if (dropDatabaseOnStartup) {
|
||||
this.database.dropDatabase();
|
||||
logger.info("Database " + this.database.getName() + " dropped in MongoDB");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void saveObject(NoSQLObject object) {
|
||||
Class<? extends NoSQLObject> clazz = object.getClass();
|
||||
|
||||
// Find annotations for ID, for all the properties and for the name of the collection.
|
||||
ObjectInfo objectInfo = getObjectInfo(clazz);
|
||||
|
||||
// Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped)
|
||||
BasicDBObject dbObject = typeConverter.convertApplicationObjectToDBObject(object, BasicDBObject.class);
|
||||
|
||||
DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
|
||||
|
||||
// Decide if we should insert or update (based on presence of oid property in original object)
|
||||
Property<String> 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"));
|
||||
}
|
||||
} else {
|
||||
BasicDBObject query = new BasicDBObject("_id", new ObjectId(currentId));
|
||||
dbCollection.update(query, dbObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
|
||||
BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid));
|
||||
DBObject dbObject = dbCollection.findOne(idQuery);
|
||||
|
||||
return typeConverter.convertDBObjectToApplicationObject(dbObject, type);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends NoSQLObject> T loadSingleObject(Class<T> type, NoSQLQuery query) {
|
||||
List<T> result = loadObjects(type, query);
|
||||
if (result.size() > 1) {
|
||||
throw new IllegalStateException("There are " + result.size() + " results for type=" + type + ", query=" + query + ". We expect just one");
|
||||
} else if (result.size() == 1) {
|
||||
return result.get(0);
|
||||
} else {
|
||||
// 0 results
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query) {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
BasicDBObject dbQuery = getDBQueryFromQuery(query);
|
||||
|
||||
DBCursor cursor = dbCollection.find(dbQuery);
|
||||
|
||||
return convertCursor(type, cursor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeObject(NoSQLObject object) {
|
||||
Class<? extends NoSQLObject> type = object.getClass();
|
||||
ObjectInfo objectInfo = getObjectInfo(type);
|
||||
|
||||
Property<String> idProperty = objectInfo.getOidProperty();
|
||||
String oid = idProperty.getValue(object);
|
||||
|
||||
removeObject(type, oid);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeObject(Class<? extends NoSQLObject> type, String oid) {
|
||||
NoSQLObject found = loadObject(type, oid);
|
||||
if (found == null) {
|
||||
logger.warn("Object of type: " + type + ", oid: " + oid + " doesn't exist in MongoDB. Skip removal");
|
||||
} else {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid));
|
||||
dbCollection.remove(dbQuery);
|
||||
logger.info("Object of type: " + type + ", oid: " + oid + " removed from MongoDB.");
|
||||
|
||||
found.afterRemove(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query) {
|
||||
List<? extends NoSQLObject> foundObjects = loadObjects(type, query);
|
||||
if (foundObjects.size() == 0) {
|
||||
logger.info("Not found any objects of type: " + type + ", query: " + query);
|
||||
} else {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
BasicDBObject dbQuery = getDBQueryFromQuery(query);
|
||||
dbCollection.remove(dbQuery);
|
||||
logger.info("Removed " + foundObjects.size() + " objects of type: " + type + ", query: " + query);
|
||||
|
||||
for (NoSQLObject found : foundObjects) {
|
||||
found.afterRemove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NoSQLQueryBuilder createQueryBuilder() {
|
||||
return new MongoDBQueryBuilder();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <S> void pushItemToList(NoSQLObject object, String listPropertyName, S itemToPush) {
|
||||
Class<? extends NoSQLObject> type = object.getClass();
|
||||
ObjectInfo objectInfo = getObjectInfo(type);
|
||||
|
||||
Property<String> 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<Object> listProperty = objectInfo.getPropertyByName(listPropertyName);
|
||||
if (listProperty == null) {
|
||||
throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object);
|
||||
}
|
||||
|
||||
List<S> list = (List<S>)listProperty.getValue(object);
|
||||
if (list == null) {
|
||||
list = new ArrayList<S>();
|
||||
listProperty.setValue(object, list);
|
||||
}
|
||||
list.add(itemToPush);
|
||||
|
||||
// Push item to DB. We always convert whole list, so it's not so optimal...
|
||||
BasicDBList dbList = typeConverter.convertApplicationObjectToDBObject(list, BasicDBList.class);
|
||||
|
||||
BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
|
||||
BasicDBObject listObject = new BasicDBObject(listPropertyName, dbList);
|
||||
BasicDBObject setCommand = new BasicDBObject("$set", listObject);
|
||||
getDBCollectionForType(type).update(query, setCommand);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <S> void pullItemFromList(NoSQLObject object, String listPropertyName, S itemToPull) {
|
||||
Class<? extends NoSQLObject> type = object.getClass();
|
||||
ObjectInfo objectInfo = getObjectInfo(type);
|
||||
|
||||
Property<String> 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<Object> listProperty = objectInfo.getPropertyByName(listPropertyName);
|
||||
if (listProperty == null) {
|
||||
throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + object);
|
||||
}
|
||||
List<S> list = (List<S>)listProperty.getValue(object);
|
||||
|
||||
// If list is null, we skip both object and DB update
|
||||
if (list != null) {
|
||||
list.remove(itemToPull);
|
||||
|
||||
// Pull item from DB
|
||||
Object dbItemToPull = typeConverter.convertApplicationObjectToDBObject(itemToPull, Object.class);
|
||||
BasicDBObject query = new BasicDBObject("_id", new ObjectId(oidProperty.getValue(object)));
|
||||
BasicDBObject pullObject = new BasicDBObject(listPropertyName, dbItemToPull);
|
||||
BasicDBObject pullCommand = new BasicDBObject("$pull", pullObject);
|
||||
getDBCollectionForType(type).update(query, pullCommand);
|
||||
}
|
||||
}
|
||||
|
||||
// Possibility to add user-defined converters
|
||||
public void addAppObjectConverter(Converter<?, ?> converter) {
|
||||
typeConverter.addAppObjectConverter(converter);
|
||||
}
|
||||
|
||||
public void addDBObjectConverter(Converter<?, ?> converter) {
|
||||
typeConverter.addDBObjectConverter(converter);
|
||||
}
|
||||
|
||||
public ObjectInfo getObjectInfo(Class<? extends NoSQLObject> objectClass) {
|
||||
ObjectInfo objectInfo = objectInfoCache.get(objectClass);
|
||||
if (objectInfo == null) {
|
||||
Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult();
|
||||
|
||||
List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLField.class)).getResultList();
|
||||
|
||||
NoSQLCollection classAnnotation = objectClass.getAnnotation(NoSQLCollection.class);
|
||||
|
||||
String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName();
|
||||
objectInfo = new ObjectInfo(objectClass, dbCollectionName, idProperty, properties);
|
||||
|
||||
ObjectInfo existing = objectInfoCache.putIfAbsent(objectClass, objectInfo);
|
||||
if (existing != null) {
|
||||
objectInfo = existing;
|
||||
}
|
||||
}
|
||||
|
||||
return objectInfo;
|
||||
}
|
||||
|
||||
private <T extends NoSQLObject> List<T> convertCursor(Class<T> type, DBCursor cursor) {
|
||||
List<T> result = new ArrayList<T>();
|
||||
|
||||
try {
|
||||
for (DBObject dbObject : cursor) {
|
||||
T converted = typeConverter.convertDBObjectToApplicationObject(dbObject, type);
|
||||
result.add(converted);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private DBCollection getDBCollectionForType(Class<? extends NoSQLObject> type) {
|
||||
ObjectInfo objectInfo = getObjectInfo(type);
|
||||
return database.getCollection(objectInfo.getDbCollectionName());
|
||||
}
|
||||
|
||||
private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) {
|
||||
Map<String, Object> queryAttributes = query.getQueryAttributes();
|
||||
BasicDBObject dbQuery = new BasicDBObject();
|
||||
for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
|
||||
dbQuery.append(queryAttr.getKey(), queryAttr.getValue());
|
||||
}
|
||||
return dbQuery;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoDBQueryBuilder extends NoSQLQueryBuilder {
|
||||
|
||||
protected MongoDBQueryBuilder() {};
|
||||
|
||||
@Override
|
||||
public NoSQLQueryBuilder inCondition(String name, List<?> values) {
|
||||
if (values == null) {
|
||||
values = new LinkedList<Object>();
|
||||
}
|
||||
|
||||
if ("_id".equals(name)) {
|
||||
// we need to convert Strings to ObjectID
|
||||
List<ObjectId> objIds = new ArrayList<ObjectId>();
|
||||
for (Object object : values) {
|
||||
ObjectId objectId = new ObjectId(object.toString());
|
||||
objIds.add(objectId);
|
||||
}
|
||||
values = objIds;
|
||||
}
|
||||
|
||||
BasicDBObject inObject = new BasicDBObject("$in", values);
|
||||
put(name, inObject);
|
||||
return this;
|
||||
}
|
||||
}
|
420
model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java
Executable file
420
model/mongo/src/main/java/org/keycloak/models/mongo/impl/MongoStoreImpl.java
Executable file
|
@ -0,0 +1,420 @@
|
|||
package org.keycloak.models.mongo.impl;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.jboss.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.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.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;
|
||||
|
||||
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;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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, byte[].class };
|
||||
|
||||
private final DB database;
|
||||
private static final Logger logger = Logger.getLogger(MongoStoreImpl.class);
|
||||
|
||||
private final MapperRegistry mapperRegistry;
|
||||
private ConcurrentMap<Class<? extends MongoEntity>, EntityInfo> entityInfoCache =
|
||||
new ConcurrentHashMap<Class<? extends MongoEntity>, EntityInfo>();
|
||||
|
||||
|
||||
public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<? extends MongoEntity>[] managedEntityTypes) {
|
||||
this.database = database;
|
||||
|
||||
mapperRegistry = new MapperRegistry();
|
||||
|
||||
for (Class<?> simpleConverterClass : SIMPLE_TYPES) {
|
||||
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)
|
||||
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, ArrayList.class));
|
||||
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
|
||||
mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry));
|
||||
|
||||
mapperRegistry.addAppObjectMapper(new MapMapper(HashMap.class));
|
||||
mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
|
||||
mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
|
||||
|
||||
// Enum converters
|
||||
mapperRegistry.addAppObjectMapper(new EnumToStringMapper());
|
||||
mapperRegistry.addDBObjectMapper(new StringToEnumMapper());
|
||||
|
||||
for (Class<? extends MongoEntity> type : managedEntityTypes) {
|
||||
getEntityInfo(type);
|
||||
mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type));
|
||||
mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type));
|
||||
}
|
||||
|
||||
if (clearCollectionsOnStartup) {
|
||||
// dropDatabase();
|
||||
clearManagedCollections(managedEntityTypes);
|
||||
}
|
||||
}
|
||||
|
||||
protected void dropDatabase() {
|
||||
this.database.dropDatabase();
|
||||
logger.info("Database " + this.database.getName() + " dropped in MongoDB");
|
||||
}
|
||||
|
||||
// Don't drop database, but just clear all data in managed collections (useful for development)
|
||||
protected void clearManagedCollections(Class<? extends MongoEntity>[] managedEntityTypes) {
|
||||
for (Class<? extends MongoEntity> clazz : managedEntityTypes) {
|
||||
DBCollection dbCollection = getDBCollectionForType(clazz);
|
||||
if (dbCollection != null) {
|
||||
dbCollection.remove(new BasicDBObject());
|
||||
logger.debug("Collection " + dbCollection.getName() + " cleared from " + this.database.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
Class<? extends MongoEntity> clazz = entity.getClass();
|
||||
|
||||
// Find annotations for ID, for all the properties and for the name of the collection.
|
||||
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 = mapperRegistry.convertApplicationObjectToDBObject(entity, BasicDBObject.class);
|
||||
|
||||
DBCollection dbCollection = database.getCollection(entityInfo.getDbCollectionName());
|
||||
|
||||
String currentId = entity.getId();
|
||||
|
||||
// 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 id to value of given object
|
||||
if (currentId == null) {
|
||||
entity.setId(dbObject.getString("_id"));
|
||||
}
|
||||
|
||||
// Treat object as if it is read (It is already submited to transaction)
|
||||
context.addLoadedObject(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntity(final MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
MongoTask fullUpdateTask = new MongoTask() {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
Class<? extends MongoEntity> clazz = entity.getClass();
|
||||
EntityInfo entityInfo = getEntityInfo(clazz);
|
||||
BasicDBObject dbObject = mapperRegistry.convertApplicationObjectToDBObject(entity, BasicDBObject.class);
|
||||
DBCollection dbCollection = database.getCollection(entityInfo.getDbCollectionName());
|
||||
|
||||
String currentId = entity.getId();
|
||||
|
||||
if (currentId == null) {
|
||||
throw new IllegalStateException("Can't update entity without id: " + entity);
|
||||
} 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(entity, fullUpdateTask);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends MongoIdentifiableEntity> T loadEntity(Class<T> 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(id));
|
||||
DBObject dbObject = dbCollection.findOne(idQuery);
|
||||
|
||||
if (dbObject == null) return null;
|
||||
|
||||
MapperContext<Object, T> mapperContext = new MapperContext<Object, T>(dbObject, type, null);
|
||||
T converted = mapperRegistry.convertDBObjectToApplicationObject(mapperContext);
|
||||
|
||||
// Now add it to loaded objects
|
||||
context.addLoadedObject(converted);
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends MongoIdentifiableEntity> T loadSingleEntity(Class<T> type, DBObject query, MongoStoreInvocationContext context) {
|
||||
List<T> 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) {
|
||||
return result.get(0);
|
||||
} else {
|
||||
// 0 results
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends MongoIdentifiableEntity> List<T> loadEntities(Class<T> 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, context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean removeEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
return removeEntity(entity.getClass(), entity.getId(), context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean removeEntity(Class<? extends MongoIdentifiableEntity> 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("Entity of type: " + type + ", id: " + id + " removed from MongoDB.");
|
||||
|
||||
context.addRemovedObject(found);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, MongoStoreInvocationContext context) {
|
||||
List<? extends MongoIdentifiableEntity> foundObjects = loadEntities(type, query, context);
|
||||
if (foundObjects.size() == 0) {
|
||||
return false;
|
||||
} else {
|
||||
DBCollection dbCollection = getDBCollectionForType(type);
|
||||
dbCollection.remove(query);
|
||||
logger.info("Removed " + foundObjects.size() + " entities of type: " + type + ", query: " + query);
|
||||
|
||||
for (MongoIdentifiableEntity found : foundObjects) {
|
||||
context.addRemovedObject(found);;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> boolean pushItemToList(final MongoIdentifiableEntity entity, final String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context) {
|
||||
final Class<? extends MongoEntity> type = entity.getClass();
|
||||
EntityInfo entityInfo = getEntityInfo(type);
|
||||
|
||||
// Add item to list directly in this object
|
||||
Property<Object> listProperty = entityInfo.getPropertyByName(listPropertyName);
|
||||
if (listProperty == null) {
|
||||
throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + entity);
|
||||
}
|
||||
|
||||
List<S> list = (List<S>)listProperty.getValue(entity);
|
||||
if (list == null) {
|
||||
list = new ArrayList<S>();
|
||||
listProperty.setValue(entity, list);
|
||||
}
|
||||
|
||||
// Skip if item is already in list
|
||||
if (skipIfAlreadyPresent && list.contains(itemToPush)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update java object
|
||||
list.add(itemToPush);
|
||||
|
||||
// Add update of list to pending tasks
|
||||
final List<S> listt = list;
|
||||
context.addUpdateTask(entity, new MongoTask() {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
// Now DB update of new list with usage of $set
|
||||
BasicDBList dbList = mapperRegistry.convertApplicationObjectToDBObject(listt, BasicDBList.class);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullUpdate() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <S> boolean pullItemFromList(final MongoIdentifiableEntity entity, final String listPropertyName, final S itemToPull, MongoStoreInvocationContext context) {
|
||||
final Class<? extends MongoEntity> type = entity.getClass();
|
||||
EntityInfo entityInfo = getEntityInfo(type);
|
||||
|
||||
// Remove item from list directly in this object
|
||||
Property<Object> listProperty = entityInfo.getPropertyByName(listPropertyName);
|
||||
if (listProperty == null) {
|
||||
throw new IllegalArgumentException("Property " + listPropertyName + " doesn't exist on object " + entity);
|
||||
}
|
||||
List<S> list = (List<S>)listProperty.getValue(entity);
|
||||
|
||||
// If list is null, we skip both object and DB update
|
||||
if (list == null || !list.contains(itemToPull)) {
|
||||
return false;
|
||||
} else {
|
||||
|
||||
// Update java object
|
||||
list.remove(itemToPull);
|
||||
|
||||
// Add update of list to pending tasks
|
||||
context.addUpdateTask(entity, new MongoTask() {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
// Pull item from DB
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFullUpdate() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Possibility to add user-defined converters
|
||||
public void addAppObjectConverter(Mapper<?, ?> mapper) {
|
||||
mapperRegistry.addAppObjectMapper(mapper);
|
||||
}
|
||||
|
||||
public void addDBObjectConverter(Mapper<?, ?> mapper) {
|
||||
mapperRegistry.addDBObjectMapper(mapper);
|
||||
}
|
||||
|
||||
public EntityInfo getEntityInfo(Class<? extends MongoEntity> objectClass) {
|
||||
EntityInfo entityInfo = entityInfoCache.get(objectClass);
|
||||
if (entityInfo == null) {
|
||||
List<Property<Object>> properties = PropertyQueries.createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(MongoField.class)).getResultList();
|
||||
|
||||
MongoCollection classAnnotation = objectClass.getAnnotation(MongoCollection.class);
|
||||
|
||||
String dbCollectionName = classAnnotation==null ? null : classAnnotation.collectionName();
|
||||
entityInfo = new EntityInfo(objectClass, dbCollectionName, properties);
|
||||
|
||||
EntityInfo existing = entityInfoCache.putIfAbsent(objectClass, entityInfo);
|
||||
if (existing != null) {
|
||||
entityInfo = existing;
|
||||
}
|
||||
}
|
||||
|
||||
return entityInfo;
|
||||
}
|
||||
|
||||
protected <T extends MongoIdentifiableEntity> List<T> convertCursor(Class<T> type, DBCursor cursor, MongoStoreInvocationContext context) {
|
||||
List<T> result = new ArrayList<T>();
|
||||
|
||||
try {
|
||||
for (DBObject dbObject : cursor) {
|
||||
// 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
|
||||
MapperContext<Object, T> mapperContext = new MapperContext<Object, T>(dbObject, type, null);
|
||||
object = mapperRegistry.convertDBObjectToApplicationObject(mapperContext);
|
||||
context.addLoadedObject(object);
|
||||
}
|
||||
|
||||
result.add(object);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected DBCollection getDBCollectionForType(Class<? extends MongoEntity> type) {
|
||||
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".
|
||||
protected Object getObjectId(String idAsString) {
|
||||
if (ObjectId.isValid(idAsString)) {
|
||||
return new ObjectId(idAsString);
|
||||
} else {
|
||||
return idAsString;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationContext {
|
||||
|
||||
private final MongoStore mongoStore;
|
||||
|
||||
public SimpleMongoStoreInvocationContext(MongoStore mongoStore) {
|
||||
this.mongoStore = mongoStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCreatedObject(MongoIdentifiableEntity entity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoadedObject(MongoIdentifiableEntity entity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends MongoIdentifiableEntity> T getLoadedObject(Class<T> type, String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task) {
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRemovedObject(MongoIdentifiableEntity entityToRemove) {
|
||||
entityToRemove.afterRemove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDBSearch(Class<? extends MongoIdentifiableEntity> entityType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoStore getMongoStore() {
|
||||
return mongoStore;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
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;
|
||||
|
||||
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TransactionMongoStoreInvocationContext implements MongoStoreInvocationContext {
|
||||
|
||||
// Assumption is that all objects has unique ID (unique across all the types)
|
||||
private Map<String, MongoIdentifiableEntity> loadedObjects = new HashMap<String, MongoIdentifiableEntity>();
|
||||
|
||||
private Map<MongoIdentifiableEntity, Set<MongoTask>> pendingUpdateTasks = new HashMap<MongoIdentifiableEntity, Set<MongoTask>>();
|
||||
|
||||
private final MongoStore mongoStore;
|
||||
|
||||
public TransactionMongoStoreInvocationContext(MongoStore mongoStore) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends MongoIdentifiableEntity> T getLoadedObject(Class<T> 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<MongoTask> currentObjectTasks = pendingUpdateTasks.get(entityToUpdate);
|
||||
if (currentObjectTasks == null) {
|
||||
currentObjectTasks = new LinkedHashSet<MongoTask>();
|
||||
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(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDBSearch(Class<? extends MongoIdentifiableEntity> entityType) {
|
||||
// Now execute pending update tasks of type, which will be searched
|
||||
Set<MongoIdentifiableEntity> toRemove = new HashSet<MongoIdentifiableEntity>();
|
||||
|
||||
for (MongoIdentifiableEntity currentEntity : pendingUpdateTasks.keySet()) {
|
||||
if (currentEntity.getClass().equals(entityType)) {
|
||||
Set<MongoTask> 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() {
|
||||
// Now execute all pending update tasks
|
||||
for (Set<MongoTask> mongoTasks : pendingUpdateTasks.values()) {
|
||||
for (MongoTask currentTask : mongoTasks) {
|
||||
currentTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
// And clear it
|
||||
loadedObjects.clear();
|
||||
pendingUpdateTasks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -1,73 +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.TypeConverter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class BasicDBListConverter implements Converter<BasicDBList, ArrayList> {
|
||||
|
||||
private final TypeConverter typeConverter;
|
||||
|
||||
public BasicDBListConverter(TypeConverter typeConverter) {
|
||||
this.typeConverter = typeConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList convertObject(BasicDBList dbList) {
|
||||
ArrayList<Object> appObjects = new ArrayList<Object>();
|
||||
Class<?> expectedListElementType = null;
|
||||
for (Object dbObject : dbList) {
|
||||
|
||||
if (expectedListElementType == null) {
|
||||
expectedListElementType = findExpectedListElementType(dbObject);
|
||||
}
|
||||
|
||||
appObjects.add(typeConverter.convertDBObjectToApplicationObject(dbObject, expectedListElementType));
|
||||
}
|
||||
return appObjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BasicDBList> getConverterObjectType() {
|
||||
return BasicDBList.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ArrayList> getExpectedReturnType() {
|
||||
return ArrayList.class;
|
||||
}
|
||||
|
||||
private Class<?> findExpectedListElementType(Object dbObject) {
|
||||
if (dbObject instanceof BasicDBObject) {
|
||||
BasicDBObject basicDBObject = (BasicDBObject) dbObject;
|
||||
String type = (String)basicDBObject.get(ListConverter.OBJECT_TYPE);
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("Not found OBJECT_TYPE key inside object " + dbObject);
|
||||
}
|
||||
basicDBObject.remove(ListConverter.OBJECT_TYPE);
|
||||
|
||||
try {
|
||||
return Class.forName(type);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new RuntimeException(cnfe);
|
||||
}
|
||||
} else {
|
||||
// Special case (if we have String like "org.keycloak.Gender###MALE" we expect that substring before ### is className
|
||||
if (String.class.equals(dbObject.getClass())) {
|
||||
String dbObjString = (String)dbObject;
|
||||
if (dbObjString.contains(ClassCache.SPLIT)) {
|
||||
String className = dbObjString.substring(0, dbObjString.indexOf(ClassCache.SPLIT));
|
||||
return ClassCache.getInstance().getOrLoadClass(className);
|
||||
}
|
||||
}
|
||||
|
||||
return dbObject.getClass();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class BasicDBListMapper implements Mapper<BasicDBList, List> {
|
||||
|
||||
private final MapperRegistry mapperRegistry;
|
||||
|
||||
public BasicDBListMapper(MapperRegistry mapperRegistry) {
|
||||
this.mapperRegistry = mapperRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List convertObject(MapperContext<BasicDBList, List> context) {
|
||||
BasicDBList dbList = context.getObjectToConvert();
|
||||
ArrayList<Object> appObjects = new ArrayList<Object>();
|
||||
Class<?> expectedListElementType = context.getGenericTypes().get(0);
|
||||
|
||||
for (Object dbObject : dbList) {
|
||||
MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
|
||||
appObjects.add(mapperRegistry.convertDBObjectToApplicationObject(newContext));
|
||||
}
|
||||
return appObjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BasicDBList> getTypeOfObjectToConvert() {
|
||||
return BasicDBList.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<List> getExpectedReturnType() {
|
||||
return List.class;
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
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.types.Converter;
|
||||
import org.keycloak.models.mongo.api.types.TypeConverter;
|
||||
import org.keycloak.models.mongo.impl.MongoDBImpl;
|
||||
import org.keycloak.models.mongo.impl.ObjectInfo;
|
||||
import org.picketlink.common.properties.Property;
|
||||
import org.picketlink.common.reflection.Types;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class BasicDBObjectConverter<S extends NoSQLObject> implements Converter<BasicDBObject, S> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BasicDBObjectConverter.class);
|
||||
|
||||
private final MongoDBImpl mongoDBImpl;
|
||||
private final TypeConverter typeConverter;
|
||||
private final Class<S> expectedNoSQLObjectType;
|
||||
|
||||
public BasicDBObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<S> expectedNoSQLObjectType) {
|
||||
this.mongoDBImpl = mongoDBImpl;
|
||||
this.typeConverter = typeConverter;
|
||||
this.expectedNoSQLObjectType = expectedNoSQLObjectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S convertObject(BasicDBObject dbObject) {
|
||||
if (dbObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(expectedNoSQLObjectType);
|
||||
|
||||
S object;
|
||||
try {
|
||||
object = expectedNoSQLObjectType.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (String key : dbObject.keySet()) {
|
||||
Object value = dbObject.get(key);
|
||||
Property<Object> property;
|
||||
|
||||
if ("_id".equals(key)) {
|
||||
// Current property is "id"
|
||||
Property<String> idProperty = objectInfo.getOidProperty();
|
||||
if (idProperty != null) {
|
||||
idProperty.setValue(object, value.toString());
|
||||
}
|
||||
|
||||
} else if ((property = objectInfo.getPropertyByName(key)) != null) {
|
||||
// It's declared property with @DBField annotation
|
||||
setPropertyValue(object, value, property);
|
||||
|
||||
} else if (object instanceof AttributedNoSQLObject) {
|
||||
// It's attributed object and property is not declared, so we will call setAttribute
|
||||
((AttributedNoSQLObject)object).setAttribute(key, value.toString());
|
||||
|
||||
} else {
|
||||
// Show warning if it's unknown
|
||||
logger.warn("Property with key " + key + " not known for type " + expectedNoSQLObjectType);
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private void setPropertyValue(NoSQLObject object, Object valueFromDB, Property property) {
|
||||
if (valueFromDB == null) {
|
||||
property.setValue(object, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> expectedReturnType = property.getJavaClass();
|
||||
// handle primitives
|
||||
expectedReturnType = Types.boxedClass(expectedReturnType);
|
||||
|
||||
Object appObject = typeConverter.convertDBObjectToApplicationObject(valueFromDB, expectedReturnType);
|
||||
if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) {
|
||||
property.setValue(object, appObject);
|
||||
} else {
|
||||
throw new IllegalStateException("Converted object " + appObject + " is not of type " + expectedReturnType +
|
||||
". So can't be assigned as property " + property.getName() + " of " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BasicDBObject> getConverterObjectType() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<S> getExpectedReturnType() {
|
||||
return expectedNoSQLObjectType;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
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.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.types.Mapper;
|
||||
import org.keycloak.models.mongo.api.types.MapperContext;
|
||||
import org.keycloak.models.mongo.api.types.MapperRegistry;
|
||||
import org.keycloak.models.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.models.mongo.impl.EntityInfo;
|
||||
import org.picketlink.common.properties.Property;
|
||||
import org.picketlink.common.reflection.Types;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class BasicDBObjectMapper<S extends MongoEntity> implements Mapper<BasicDBObject, S> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BasicDBObjectMapper.class);
|
||||
|
||||
private final MongoStoreImpl mongoStoreImpl;
|
||||
private final MapperRegistry mapperRegistry;
|
||||
private final Class<S> expectedObjectType;
|
||||
|
||||
public BasicDBObjectMapper(MongoStoreImpl mongoStoreImpl, MapperRegistry mapperRegistry, Class<S> expectedObjectType) {
|
||||
this.mongoStoreImpl = mongoStoreImpl;
|
||||
this.mapperRegistry = mapperRegistry;
|
||||
this.expectedObjectType = expectedObjectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public S convertObject(MapperContext<BasicDBObject, S> context) {
|
||||
BasicDBObject dbObject = context.getObjectToConvert();
|
||||
if (dbObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityInfo entityInfo = mongoStoreImpl.getEntityInfo(expectedObjectType);
|
||||
|
||||
S object;
|
||||
try {
|
||||
object = expectedObjectType.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for (String key : dbObject.keySet()) {
|
||||
Object value = dbObject.get(key);
|
||||
Property<Object> property;
|
||||
|
||||
if ("_id".equals(key)) {
|
||||
// Current property is "id"
|
||||
if (object instanceof MongoIdentifiableEntity) {
|
||||
((MongoIdentifiableEntity)object).setId(value.toString());
|
||||
}
|
||||
|
||||
} else if ((property = entityInfo.getPropertyByName(key)) != null) {
|
||||
// It's declared property with @DBField annotation
|
||||
setPropertyValue(object, value, property);
|
||||
|
||||
} else {
|
||||
// Show warning if it's unknown
|
||||
logger.warn("Property with key " + key + " not known for type " + expectedObjectType);
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private void setPropertyValue(MongoEntity object, Object valueFromDB, Property property) {
|
||||
if (valueFromDB == null) {
|
||||
property.setValue(object, null);
|
||||
return;
|
||||
}
|
||||
|
||||
MapperContext<Object, ?> context;
|
||||
|
||||
Type type = property.getBaseType();
|
||||
|
||||
// This can be the case when we have parameterized type (like "List<String>")
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterized = (ParameterizedType) type;
|
||||
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
|
||||
|
||||
List<Class<?>> genericTypes = new ArrayList<Class<?>>();
|
||||
for (Type genericType : genericTypeArguments) {
|
||||
genericTypes.add((Class<?>)genericType);
|
||||
}
|
||||
|
||||
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
|
||||
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
|
||||
} else {
|
||||
Class<?> expectedReturnType = (Class<?>)type;
|
||||
// handle primitives
|
||||
expectedReturnType = Types.boxedClass(expectedReturnType);
|
||||
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, null);
|
||||
}
|
||||
|
||||
Object appObject = mapperRegistry.convertDBObjectToApplicationObject(context);
|
||||
|
||||
if (Types.boxedClass(property.getJavaClass()).isAssignableFrom(appObject.getClass())) {
|
||||
property.setValue(object, appObject);
|
||||
} else {
|
||||
throw new IllegalStateException("Converted object " + appObject + " is not of type " + context.getExpectedReturnType() +
|
||||
". So can't be assigned as property " + property.getName() + " of " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BasicDBObject> getTypeOfObjectToConvert() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<S> getExpectedReturnType() {
|
||||
return expectedObjectType;
|
||||
}
|
||||
}
|
|
@ -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.Mapper;
|
||||
import org.keycloak.models.mongo.api.types.MapperContext;
|
||||
|
||||
/**
|
||||
* For now, we support just convert to Map<String, String>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class BasicDBObjectToMapMapper implements Mapper<BasicDBObject, Map> {
|
||||
|
||||
@Override
|
||||
public Map convertObject(MapperContext<BasicDBObject, Map> context) {
|
||||
BasicDBObject objectToConvert = context.getObjectToConvert();
|
||||
|
||||
HashMap<String, String> result = new HashMap<String, String>();
|
||||
for (Map.Entry<String, Object> entry : objectToConvert.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = (String)entry.getValue();
|
||||
|
||||
if (key.contains(MapMapper.DOT_PLACEHOLDER)) {
|
||||
key = key.replaceAll(MapMapper.DOT_PLACEHOLDER, ".");
|
||||
}
|
||||
|
||||
result.put(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BasicDBObject> getTypeOfObjectToConvert() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Map> getExpectedReturnType() {
|
||||
return Map.class;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Helper class for caching of classNames to actual classes (Should help a bit to avoid expensive reflection calls)
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClassCache {
|
||||
|
||||
public static final String SPLIT = "###";
|
||||
private static final ClassCache INSTANCE = new ClassCache();
|
||||
|
||||
private ConcurrentMap<String, Class<?>> cache = new ConcurrentHashMap<String, Class<?>>();
|
||||
|
||||
private ClassCache() {};
|
||||
|
||||
public static ClassCache getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public Class<?> getOrLoadClass(String className) {
|
||||
Class<?> clazz = cache.get(className);
|
||||
if (clazz == null) {
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
cache.putIfAbsent(className, clazz);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw new RuntimeException(cnfe);
|
||||
}
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
import org.keycloak.models.mongo.api.types.Converter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class EnumToStringConverter implements Converter<Enum, String> {
|
||||
|
||||
// It will be saved in form of "org.keycloak.Gender#MALE" so it's possible to parse enumType out of it
|
||||
@Override
|
||||
public String convertObject(Enum objectToConvert) {
|
||||
String className = objectToConvert.getClass().getName();
|
||||
return className + ClassCache.SPLIT + objectToConvert.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Enum> getConverterObjectType() {
|
||||
return Enum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getExpectedReturnType() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class EnumToStringMapper implements Mapper<Enum, String> {
|
||||
|
||||
// It will be saved in form of "org.keycloak.Gender#MALE" so it's possible to parse enumType out of it
|
||||
@Override
|
||||
public String convertObject(MapperContext<Enum, String> context) {
|
||||
Enum objectToConvert = context.getObjectToConvert();
|
||||
|
||||
return objectToConvert.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Enum> getTypeOfObjectToConvert() {
|
||||
return Enum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<String> getExpectedReturnType() {
|
||||
return String.class;
|
||||
}
|
||||
}
|
|
@ -1,52 +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.TypeConverter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ListConverter<T extends List> implements Converter<T, BasicDBList> {
|
||||
|
||||
// Key for ObjectType field, which points to actual Java type of element objects inside list
|
||||
static final String OBJECT_TYPE = "OBJECT_TYPE";
|
||||
|
||||
private final TypeConverter typeConverter;
|
||||
private final Class<T> listType;
|
||||
|
||||
public ListConverter(TypeConverter typeConverter, Class<T> listType) {
|
||||
this.typeConverter = typeConverter;
|
||||
this.listType = listType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicDBList convertObject(T appObjectsList) {
|
||||
BasicDBList dbObjects = new BasicDBList();
|
||||
for (Object appObject : appObjectsList) {
|
||||
Object dbObject = typeConverter.convertApplicationObjectToDBObject(appObject, Object.class);
|
||||
|
||||
// We need to add OBJECT_TYPE key to object, so we can retrieve correct Java type of object during load of this list
|
||||
if (dbObject instanceof BasicDBObject) {
|
||||
BasicDBObject basicDBObject = (BasicDBObject)dbObject;
|
||||
basicDBObject.put(OBJECT_TYPE, appObject.getClass().getName());
|
||||
}
|
||||
|
||||
dbObjects.add(dbObject);
|
||||
}
|
||||
return dbObjects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getConverterObjectType() {
|
||||
return listType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<BasicDBList> getExpectedReturnType() {
|
||||
return BasicDBList.class;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ListMapper<T extends List> implements Mapper<T, BasicDBList> {
|
||||
|
||||
private final MapperRegistry mapperRegistry;
|
||||
private final Class<T> listType;
|
||||
|
||||
public ListMapper(MapperRegistry mapperRegistry, Class<T> listType) {
|
||||
this.mapperRegistry = mapperRegistry;
|
||||
this.listType = listType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicDBList convertObject(MapperContext<T, BasicDBList> 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<? extends T> getTypeOfObjectToConvert() {
|
||||
return listType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<BasicDBList> getExpectedReturnType() {
|
||||
return BasicDBList.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
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.Mapper;
|
||||
import org.keycloak.models.mongo.api.types.MapperContext;
|
||||
|
||||
/**
|
||||
* For now, we support just convert from Map<String, String>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MapMapper<T extends Map> implements Mapper<T, BasicDBObject> {
|
||||
|
||||
// Just some dummy way of encoding . character as it's not allowed by mongo in key fields
|
||||
static final String DOT_PLACEHOLDER = "###";
|
||||
|
||||
private final Class<T> mapType;
|
||||
|
||||
public MapMapper(Class<T> mapType) {
|
||||
this.mapType = mapType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicDBObject convertObject(MapperContext<T, BasicDBObject> context) {
|
||||
T objectToConvert = context.getObjectToConvert();
|
||||
|
||||
BasicDBObject dbObject = new BasicDBObject();
|
||||
Set<Map.Entry> entries = objectToConvert.entrySet();
|
||||
for (Map.Entry entry : entries) {
|
||||
String key = (String)entry.getKey();
|
||||
String value = (String)entry.getValue();
|
||||
|
||||
if (key.contains(".")) {
|
||||
key = key.replaceAll("\\.", DOT_PLACEHOLDER);
|
||||
}
|
||||
|
||||
dbObject.put(key, value);
|
||||
}
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getTypeOfObjectToConvert() {
|
||||
return mapType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<BasicDBObject> getExpectedReturnType() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.types.Mapper;
|
||||
import org.keycloak.models.mongo.api.types.MapperContext;
|
||||
import org.keycloak.models.mongo.api.types.MapperRegistry;
|
||||
import org.keycloak.models.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.models.mongo.impl.EntityInfo;
|
||||
import org.picketlink.common.properties.Property;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, BasicDBObject> {
|
||||
|
||||
private final MongoStoreImpl mongoStoreImpl;
|
||||
private final MapperRegistry mapperRegistry;
|
||||
private final Class<T> expectedMongoEntityType;
|
||||
|
||||
public MongoEntityMapper(MongoStoreImpl mongoStoreImpl, MapperRegistry mapperRegistry, Class<T> expectedMongoEntityType) {
|
||||
this.mongoStoreImpl = mongoStoreImpl;
|
||||
this.mapperRegistry = mapperRegistry;
|
||||
this.expectedMongoEntityType = expectedMongoEntityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicDBObject convertObject(MapperContext<T, BasicDBObject> context) {
|
||||
T applicationObject = context.getObjectToConvert();
|
||||
|
||||
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<Property<Object>> props = entityInfo.getProperties();
|
||||
for (Property<Object> property : props) {
|
||||
String propName = property.getName();
|
||||
Object propValue = property.getValue(applicationObject);
|
||||
|
||||
Object dbValue = propValue == null ? null : mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class);
|
||||
dbObject.put(propName, dbValue);
|
||||
}
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getTypeOfObjectToConvert() {
|
||||
return expectedMongoEntityType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<BasicDBObject> getExpectedReturnType() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
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.types.Converter;
|
||||
import org.keycloak.models.mongo.api.types.TypeConverter;
|
||||
import org.keycloak.models.mongo.impl.MongoDBImpl;
|
||||
import org.keycloak.models.mongo.impl.ObjectInfo;
|
||||
import org.picketlink.common.properties.Property;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class NoSQLObjectConverter<T extends NoSQLObject> implements Converter<T, BasicDBObject> {
|
||||
|
||||
private final MongoDBImpl mongoDBImpl;
|
||||
private final TypeConverter typeConverter;
|
||||
private final Class<T> expectedNoSQLObjectType;
|
||||
|
||||
public NoSQLObjectConverter(MongoDBImpl mongoDBImpl, TypeConverter typeConverter, Class<T> expectedNoSQLObjectType) {
|
||||
this.mongoDBImpl = mongoDBImpl;
|
||||
this.typeConverter = typeConverter;
|
||||
this.expectedNoSQLObjectType = expectedNoSQLObjectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicDBObject convertObject(T applicationObject) {
|
||||
ObjectInfo objectInfo = mongoDBImpl.getObjectInfo(applicationObject.getClass());
|
||||
|
||||
// Create instance of BasicDBObject and add all declared properties to it (properties with null value probably should be skipped)
|
||||
BasicDBObject dbObject = new BasicDBObject();
|
||||
Collection<Property<Object>> props = objectInfo.getProperties();
|
||||
for (Property<Object> property : props) {
|
||||
String propName = property.getName();
|
||||
Object propValue = property.getValue(applicationObject);
|
||||
|
||||
Object dbValue = propValue == null ? null : typeConverter.convertApplicationObjectToDBObject(propValue, Object.class);
|
||||
dbObject.put(propName, dbValue);
|
||||
}
|
||||
|
||||
// Adding attributes
|
||||
if (applicationObject instanceof AttributedNoSQLObject) {
|
||||
AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)applicationObject;
|
||||
Map<String, String> attributes = attributedObject.getAttributes();
|
||||
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
|
||||
dbObject.append(attribute.getKey(), attribute.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getConverterObjectType() {
|
||||
return expectedNoSQLObjectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<BasicDBObject> getExpectedReturnType() {
|
||||
return BasicDBObject.class;
|
||||
}
|
||||
}
|
|
@ -1,25 +1,29 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
import org.keycloak.models.mongo.api.types.Converter;
|
||||
import org.keycloak.models.mongo.api.types.Mapper;
|
||||
import org.keycloak.models.mongo.api.types.MapperContext;
|
||||
|
||||
/**
|
||||
* Just returns input
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SimpleConverter<T> implements Converter<T, T> {
|
||||
public class SimpleMapper<T> implements Mapper<T, T> {
|
||||
|
||||
private final Class<T> expectedType;
|
||||
|
||||
public SimpleConverter(Class<T> expectedType) {
|
||||
public SimpleMapper(Class<T> expectedType) {
|
||||
this.expectedType = expectedType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convertObject(T objectToConvert) {
|
||||
public T convertObject(MapperContext<T, T> context) {
|
||||
T objectToConvert = context.getObjectToConvert();
|
||||
return objectToConvert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends T> getConverterObjectType() {
|
||||
public Class<? extends T> getTypeOfObjectToConvert() {
|
||||
return expectedType;
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package org.keycloak.models.mongo.impl.types;
|
||||
|
||||
import org.keycloak.models.mongo.api.types.Converter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class StringToEnumConverter implements Converter<String, Enum> {
|
||||
|
||||
@Override
|
||||
public Enum convertObject(String objectToConvert) {
|
||||
int index = objectToConvert.indexOf(ClassCache.SPLIT);
|
||||
if (index == -1) {
|
||||
throw new IllegalStateException("Can't convert enum type with value " + objectToConvert);
|
||||
}
|
||||
|
||||
String className = objectToConvert.substring(0, index);
|
||||
String enumValue = objectToConvert.substring(index + 3);
|
||||
Class<? extends Enum> clazz = (Class<? extends Enum>)ClassCache.getInstance().getOrLoadClass(className);
|
||||
return Enum.valueOf(clazz, enumValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends String> getConverterObjectType() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Enum> getExpectedReturnType() {
|
||||
return Enum.class;
|
||||
}
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class StringToEnumMapper implements Mapper<String, Enum> {
|
||||
|
||||
@Override
|
||||
public Enum convertObject(MapperContext<String, Enum> context) {
|
||||
String enumValue = context.getObjectToConvert();
|
||||
|
||||
Class<? extends Enum> clazz = context.getExpectedReturnType();
|
||||
return Enum.valueOf(clazz, enumValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends String> getTypeOfObjectToConvert() {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Enum> getExpectedReturnType() {
|
||||
return Enum.class;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractAdapter {
|
||||
|
||||
protected MongoStoreInvocationContext invocationContext;
|
||||
|
||||
public AbstractAdapter(MongoStoreInvocationContext invocationContext) {
|
||||
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();
|
||||
}
|
||||
|
||||
protected MongoStore getMongoStore() {
|
||||
return invocationContext.getMongoStore();
|
||||
}
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
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.AbstractMongoIdentifiableEntity;
|
||||
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;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
@ -17,31 +20,38 @@ import java.util.Set;
|
|||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ApplicationAdapter implements ApplicationModel {
|
||||
public class ApplicationAdapter extends AbstractAdapter implements ApplicationModel {
|
||||
|
||||
private final ApplicationData application;
|
||||
private final NoSQL noSQL;
|
||||
private final ApplicationEntity application;
|
||||
private UserAdapter resourceUser;
|
||||
|
||||
private UserData resourceUser;
|
||||
public ApplicationAdapter(ApplicationEntity applicationEntity, MongoStoreInvocationContext invContext) {
|
||||
this(applicationEntity, null, invContext);
|
||||
}
|
||||
|
||||
public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) {
|
||||
this.application = applicationData;
|
||||
this.noSQL = noSQL;
|
||||
public ApplicationAdapter(ApplicationEntity applicationEntity, UserAdapter resourceUser, MongoStoreInvocationContext invContext) {
|
||||
super(invContext);
|
||||
this.application = applicationEntity;
|
||||
this.resourceUser = resourceUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateApplication() {
|
||||
noSQL.saveObject(application);
|
||||
getMongoStore().updateEntity(application, invocationContext);
|
||||
}
|
||||
|
||||
@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 = getMongoStore().loadEntity(UserEntity.class, application.getResourceUserId(), invocationContext);
|
||||
if (userEntity == null) {
|
||||
throw new IllegalStateException("User " + application.getResourceUserId() + " not found");
|
||||
}
|
||||
resourceUser = new UserAdapter(userEntity, invocationContext);
|
||||
}
|
||||
|
||||
return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null;
|
||||
return resourceUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -101,182 +111,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 = getMongoStore().loadSingleEntity(RoleEntity.class, query, invocationContext);
|
||||
if (role == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new RoleAdapter(role, noSQL);
|
||||
return new RoleAdapter(role, invocationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(String id) {
|
||||
RoleData role = noSQL.loadObject(RoleData.class, id);
|
||||
RoleEntity role = getMongoStore().loadEntity(RoleEntity.class, id, invocationContext);
|
||||
if (role == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new RoleAdapter(role, noSQL);
|
||||
return new RoleAdapter(role, this, invocationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(UserModel user, RoleModel role) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
noSQL.pushItemToList(userData, "roleIds", role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(UserModel user, String role) {
|
||||
RoleModel roleModel = getRole(role);
|
||||
return hasRole(user, roleModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(UserModel user, RoleModel role) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
|
||||
List<String> roleIds = userData.getRoleIds();
|
||||
String roleId = role.getId();
|
||||
if (roleIds != null) {
|
||||
for (String currentId : roleIds) {
|
||||
if (roleId.equals(currentId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleAdapter addRole(String name) {
|
||||
if (getRole(name) != null) {
|
||||
throw new IllegalArgumentException("Role " + name + " already exists");
|
||||
RoleAdapter existing = getRole(name);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
RoleData roleData = new RoleData();
|
||||
roleData.setName(name);
|
||||
roleData.setApplicationId(getId());
|
||||
RoleEntity roleEntity = new RoleEntity();
|
||||
roleEntity.setName(name);
|
||||
roleEntity.setApplicationId(getId());
|
||||
|
||||
noSQL.saveObject(roleData);
|
||||
return new RoleAdapter(roleData, noSQL);
|
||||
getMongoStore().insertEntity(roleEntity, invocationContext);
|
||||
return new RoleAdapter(roleEntity, this, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleModel> getRoles() {
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("applicationId", getId())
|
||||
.build();
|
||||
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
|
||||
public boolean removeRoleById(String id) {
|
||||
return getMongoStore().removeEntity(RoleEntity.class, id, invocationContext);
|
||||
}
|
||||
|
||||
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||
for (RoleData role : roles) {
|
||||
result.add(new RoleAdapter(role, noSQL));
|
||||
@Override
|
||||
public Set<RoleModel> getRoles() {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("applicationId").is(getId())
|
||||
.get();
|
||||
List<RoleEntity> roles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
|
||||
|
||||
Set<RoleModel> result = new HashSet<RoleModel>();
|
||||
for (RoleEntity role : roles) {
|
||||
result.add(new RoleAdapter(role, this, invocationContext));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Static so that it can be used from RealmAdapter as well
|
||||
static List<RoleData> getAllRolesOfUser(UserModel user, NoSQL noSQL) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
List<String> roleIds = userData.getRoleIds();
|
||||
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.inCondition("_id", roleIds)
|
||||
.build();
|
||||
return noSQL.loadObjects(RoleData.class, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoleMappingValues(UserModel user) {
|
||||
Set<String> result = new HashSet<String>();
|
||||
List<RoleData> roles = getAllRolesOfUser(user, noSQL);
|
||||
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||
for (RoleData role : roles) {
|
||||
public Set<RoleModel> getApplicationRoleMappings(UserModel user) {
|
||||
Set<RoleModel> result = new HashSet<RoleModel>();
|
||||
List<RoleEntity> roles = MongoModelUtils.getAllRolesOfUser(user, invocationContext);
|
||||
|
||||
for (RoleEntity role : roles) {
|
||||
if (getId().equals(role.getApplicationId())) {
|
||||
result.add(role.getName());
|
||||
result.add(new RoleAdapter(role, this, invocationContext));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleModel> getRoleMappings(UserModel user) {
|
||||
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||
List<RoleData> roles = getAllRolesOfUser(user, noSQL);
|
||||
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||
for (RoleData role : roles) {
|
||||
public void addScope(RoleModel role) {
|
||||
UserAdapter appUser = getApplicationUser();
|
||||
getMongoStore().pushItemToList(appUser.getUser(), "scopeIds", role.getId(), true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getApplicationScopeMappings(UserModel user) {
|
||||
Set<RoleModel> result = new HashSet<RoleModel>();
|
||||
List<RoleEntity> roles = MongoModelUtils.getAllScopesOfUser(user, invocationContext);
|
||||
|
||||
for (RoleEntity role : roles) {
|
||||
if (getId().equals(role.getApplicationId())) {
|
||||
result.add(new RoleAdapter(role, noSQL));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(UserModel user, RoleModel role) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
noSQL.pullItemFromList(userData, "roleIds", role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(UserModel agent, String roleName) {
|
||||
RoleAdapter role = getRole(roleName);
|
||||
if (role == null) {
|
||||
throw new RuntimeException("Role not found");
|
||||
}
|
||||
|
||||
addScopeMapping(agent, role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(UserModel agent, RoleModel role) {
|
||||
UserData userData = ((UserAdapter)agent).getUser();
|
||||
noSQL.pushItemToList(userData, "scopeIds", role.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(UserModel user, RoleModel role) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
noSQL.pullItemFromList(userData, "scopeIds", role.getId());
|
||||
}
|
||||
|
||||
// Static so that it can be used from RealmAdapter as well
|
||||
static List<RoleData> getAllScopesOfUser(UserModel user, NoSQL noSQL) {
|
||||
UserData userData = ((UserAdapter)user).getUser();
|
||||
List<String> roleIds = userData.getScopeIds();
|
||||
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.inCondition("_id", roleIds)
|
||||
.build();
|
||||
return noSQL.loadObjects(RoleData.class, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getScopeMappingValues(UserModel agent) {
|
||||
Set<String> result = new HashSet<String>();
|
||||
List<RoleData> roles = getAllScopesOfUser(agent, noSQL);
|
||||
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||
for (RoleData role : roles) {
|
||||
if (getId().equals(role.getApplicationId())) {
|
||||
result.add(role.getName());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleModel> getScopeMappings(UserModel agent) {
|
||||
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||
List<RoleData> roles = getAllScopesOfUser(agent, noSQL);
|
||||
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||
for (RoleData role : roles) {
|
||||
if (getId().equals(role.getApplicationId())) {
|
||||
result.add(new RoleAdapter(role, noSQL));
|
||||
result.add(new RoleAdapter(role, this, invocationContext));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -284,16 +202,36 @@ public class ApplicationAdapter implements ApplicationModel {
|
|||
|
||||
@Override
|
||||
public List<String> getDefaultRoles() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
return application.getDefaultRoles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultRole(String name) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
RoleModel role = getRole(name);
|
||||
if (role == null) {
|
||||
addRole(name);
|
||||
}
|
||||
|
||||
getMongoStore().pushItemToList(application, "defaultRoles", name, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDefaultRoles(String[] defaultRoles) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
List<String> roleNames = new ArrayList<String>();
|
||||
for (String roleName : defaultRoles) {
|
||||
RoleModel role = getRole(roleName);
|
||||
if (role == null) {
|
||||
addRole(roleName);
|
||||
}
|
||||
|
||||
roleNames.add(roleName);
|
||||
}
|
||||
|
||||
application.setDefaultRoles(roleNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractMongoIdentifiableEntity getMongoEntity() {
|
||||
return application;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||
import org.keycloak.models.mongo.impl.MongoDBImpl;
|
||||
import org.keycloak.models.mongo.keycloak.data.ApplicationData;
|
||||
import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
|
||||
import org.keycloak.models.mongo.keycloak.data.RealmData;
|
||||
import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
|
||||
import org.keycloak.models.mongo.keycloak.data.RoleData;
|
||||
import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
|
||||
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||
import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
|
||||
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* NoSQL implementation based on MongoDB
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoDBSessionFactory implements KeycloakSessionFactory {
|
||||
protected static final Logger logger = Logger.getLogger(MongoDBSessionFactory.class);
|
||||
|
||||
private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
|
||||
RealmData.class,
|
||||
UserData.class,
|
||||
RoleData.class,
|
||||
RequiredCredentialData.class,
|
||||
PasswordData.class,
|
||||
OTPData.class,
|
||||
SocialLinkData.class,
|
||||
ApplicationData.class,
|
||||
OAuthClientData.class
|
||||
};
|
||||
|
||||
private final MongoClient mongoClient;
|
||||
private final NoSQL mongoDB;
|
||||
|
||||
public MongoDBSessionFactory(String host, int port, String dbName, boolean dropDatabaseOnStartup) {
|
||||
logger.info(String.format("Going to use MongoDB database. host: %s, port: %d, databaseName: %s, removeAllObjectsAtStartup: %b", host, port, dbName, dropDatabaseOnStartup));
|
||||
try {
|
||||
// TODO: authentication support
|
||||
mongoClient = new MongoClient(host, port);
|
||||
|
||||
DB db = mongoClient.getDB(dbName);
|
||||
mongoDB = new MongoDBImpl(db, dropDatabaseOnStartup, MANAGED_DATA_TYPES);
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSession createSession() {
|
||||
return new NoSQLSession(mongoDB);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.info("Closing MongoDB client");
|
||||
mongoClient.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
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.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoKeycloakSession implements KeycloakSession {
|
||||
|
||||
private final MongoStoreInvocationContext invocationContext;
|
||||
private final MongoKeycloakTransaction transaction;
|
||||
|
||||
public MongoKeycloakSession(MongoStore mongoStore) {
|
||||
// this.invocationContext = new SimpleMongoStoreInvocationContext(mongoStore);
|
||||
this.invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
|
||||
this.transaction = new MongoKeycloakTransaction(invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakTransaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
getMongoStore().insertEntity(newRealm, invocationContext);
|
||||
|
||||
return new RealmAdapter(newRealm, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
RealmEntity realmEntity = getMongoStore().loadEntity(RealmEntity.class, id, invocationContext);
|
||||
return realmEntity != null ? new RealmAdapter(realmEntity, invocationContext) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RealmModel> getRealms(UserModel admin) {
|
||||
DBObject query = new BasicDBObject();
|
||||
List<RealmEntity> realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext);
|
||||
|
||||
List<RealmModel> results = new ArrayList<RealmModel>();
|
||||
for (RealmEntity realmEntity : realms) {
|
||||
results.add(new RealmAdapter(realmEntity, invocationContext));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealmByName(String name) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("name").is(name)
|
||||
.get();
|
||||
RealmEntity realm = getMongoStore().loadSingleEntity(RealmEntity.class, query, invocationContext);
|
||||
|
||||
if (realm == null) return null;
|
||||
return new RealmAdapter(realm, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRealm(String id) {
|
||||
return getMongoStore().removeEntity(RealmEntity.class, id, invocationContext);
|
||||
}
|
||||
|
||||
protected MongoStore getMongoStore() {
|
||||
return invocationContext.getMongoStore();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
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.MongoConfiguration;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* KeycloakSessionFactory implementation based on MongoDB
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
|
||||
protected static final Logger logger = Logger.getLogger(MongoKeycloakSessionFactory.class);
|
||||
|
||||
private static final Class<? extends MongoEntity>[] MANAGED_ENTITY_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
|
||||
RealmEntity.class,
|
||||
UserEntity.class,
|
||||
RoleEntity.class,
|
||||
RequiredCredentialEntity.class,
|
||||
CredentialEntity.class,
|
||||
SocialLinkEntity.class,
|
||||
ApplicationEntity.class,
|
||||
OAuthClientEntity.class
|
||||
};
|
||||
|
||||
private final MongoClient mongoClient;
|
||||
private final MongoStore mongoStore;
|
||||
|
||||
public MongoKeycloakSessionFactory(MongoConfiguration config) {
|
||||
logger.info(String.format("Configuring MongoStore with: " + config));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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() {
|
||||
if (started) {
|
||||
throw new IllegalStateException("Transaction already started");
|
||||
}
|
||||
started = true;
|
||||
invocationContext.begin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
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() {
|
||||
invocationContext.rollback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
this.rollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return rollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return started;
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.keycloak.data.RealmData;
|
||||
import org.keycloak.models.utils.KeycloakSessionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class NoSQLSession implements KeycloakSession {
|
||||
|
||||
private static final NoSQLTransaction PLACEHOLDER = new NoSQLTransaction();
|
||||
private final NoSQL noSQL;
|
||||
|
||||
public NoSQLSession(NoSQL noSQL) {
|
||||
this.noSQL = noSQL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakTransaction getTransaction() {
|
||||
return PLACEHOLDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String name) {
|
||||
return createRealm(KeycloakSessionUtils.generateId(), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel createRealm(String id, String name) {
|
||||
if (getRealm(id) != null) {
|
||||
throw new IllegalStateException("Realm with id '" + id + "' already exists");
|
||||
}
|
||||
|
||||
RealmData newRealm = new RealmData();
|
||||
newRealm.setId(id);
|
||||
newRealm.setName(name);
|
||||
|
||||
noSQL.saveObject(newRealm);
|
||||
|
||||
RealmAdapter realm = new RealmAdapter(newRealm, noSQL);
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm(String id) {
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("id", id)
|
||||
.build();
|
||||
RealmData realmData = noSQL.loadSingleObject(RealmData.class, query);
|
||||
return realmData != null ? new RealmAdapter(realmData, noSQL) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RealmModel> getRealms(UserModel admin) {
|
||||
String userId = ((UserAdapter)admin).getUser().getId();
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("realmAdmins", userId)
|
||||
.build();
|
||||
List<RealmData> realms = noSQL.loadObjects(RealmData.class, query);
|
||||
|
||||
List<RealmModel> results = new ArrayList<RealmModel>();
|
||||
for (RealmData realmData : realms) {
|
||||
results.add(new RealmAdapter(realmData, noSQL));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRealm(RealmModel realm) {
|
||||
String oid = ((RealmAdapter)realm).getOid();
|
||||
noSQL.removeObject(RealmData.class, oid);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class NoSQLTransaction implements KeycloakTransaction {
|
||||
|
||||
@Override
|
||||
public void begin() {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -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.AbstractMongoIdentifiableEntity;
|
||||
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OAuthClientAdapter implements OAuthClientModel {
|
||||
public class OAuthClientAdapter extends AbstractAdapter implements OAuthClientModel {
|
||||
|
||||
private final OAuthClientData delegate;
|
||||
private final OAuthClientEntity delegate;
|
||||
private UserAdapter oauthAgent;
|
||||
private final NoSQL noSQL;
|
||||
|
||||
public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) {
|
||||
this.delegate = oauthClientData;
|
||||
public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, UserAdapter oauthAgent, MongoStoreInvocationContext invContext) {
|
||||
super(invContext);
|
||||
this.delegate = oauthClientEntity;
|
||||
this.oauthAgent = oauthAgent;
|
||||
this.noSQL = noSQL;
|
||||
}
|
||||
|
||||
public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) {
|
||||
this.delegate = oauthClientData;
|
||||
this.noSQL = noSQL;
|
||||
public OAuthClientAdapter(OAuthClientEntity oauthClientEntity, MongoStoreInvocationContext invContext) {
|
||||
this(oauthClientEntity, null, invContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,10 +34,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) {
|
||||
UserData user = noSQL.loadObject(UserData.class, delegate.getOauthAgentId());
|
||||
oauthAgent = user!=null ? new UserAdapter(user, noSQL) : null;
|
||||
UserEntity user = getMongoStore().loadEntity(UserEntity.class, delegate.getOauthAgentId(), invocationContext);
|
||||
oauthAgent = user!=null ? new UserAdapter(user, invocationContext) : null;
|
||||
}
|
||||
return oauthAgent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractMongoIdentifiableEntity getMongoEntity() {
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,45 @@
|
|||
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.AbstractMongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.RoleEntity;
|
||||
import org.keycloak.models.mongo.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)
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RoleAdapter implements RoleModel {
|
||||
public class RoleAdapter extends AbstractAdapter implements RoleModel {
|
||||
|
||||
private final RoleData role;
|
||||
private final NoSQL noSQL;
|
||||
private final RoleEntity role;
|
||||
private RoleContainerModel roleContainer;
|
||||
|
||||
public RoleAdapter(RoleData roleData, NoSQL noSQL) {
|
||||
this.role = roleData;
|
||||
this.noSQL = noSQL;
|
||||
public RoleAdapter(RoleEntity roleEntity, MongoStoreInvocationContext invContext) {
|
||||
this(roleEntity, null, invContext);
|
||||
}
|
||||
|
||||
public RoleAdapter(RoleEntity roleEntity, RoleContainerModel roleContainer, MongoStoreInvocationContext invContext) {
|
||||
super(invContext);
|
||||
this.role = roleEntity;
|
||||
this.roleContainer = roleContainer;
|
||||
}
|
||||
|
||||
@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,84 @@ 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.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
|
||||
}
|
||||
|
||||
protected void updateRole() {
|
||||
getMongoStore().updateEntity(role, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
role.setName(name);
|
||||
noSQL.saveObject(role);
|
||||
public void addCompositeRole(RoleModel childRole) {
|
||||
getMongoStore().pushItemToList(role, "compositeRoleIds", childRole.getId(), true, invocationContext);
|
||||
}
|
||||
|
||||
public RoleData getRole() {
|
||||
@Override
|
||||
public void removeCompositeRole(RoleModel childRole) {
|
||||
getMongoStore().pullItemFromList(role, "compositeRoleIds", childRole.getId(), invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getComposites() {
|
||||
if (role.getCompositeRoleIds() == null || role.getCompositeRoleIds().isEmpty()) {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("_id").in(MongoModelUtils.convertStringsToObjectIds(role.getCompositeRoleIds()))
|
||||
.get();
|
||||
List<RoleEntity> childRoles = getMongoStore().loadEntities(RoleEntity.class, query, invocationContext);
|
||||
|
||||
Set<RoleModel> set = new HashSet<RoleModel>();
|
||||
for (RoleEntity childRole : childRoles) {
|
||||
set.add(new RoleAdapter(childRole, invocationContext));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleContainerModel getContainer() {
|
||||
if (roleContainer == null) {
|
||||
// Compute it
|
||||
if (role.getRealmId() != null) {
|
||||
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, invocationContext);
|
||||
} else if (role.getApplicationId() != null) {
|
||||
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, invocationContext);
|
||||
} else {
|
||||
throw new IllegalStateException("Both realmId and applicationId are null for role: " + this);
|
||||
}
|
||||
}
|
||||
return roleContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
if (this.equals(role)) return true;
|
||||
if (!isComposite()) return false;
|
||||
|
||||
Set<RoleModel> visited = new HashSet<RoleModel>();
|
||||
return KeycloakModelUtils.searchFor(role, this, visited);
|
||||
}
|
||||
|
||||
public RoleEntity getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractMongoIdentifiableEntity getMongoEntity() {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.AbstractMongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
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;
|
||||
|
@ -15,14 +18,13 @@ import java.util.Set;
|
|||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class UserAdapter implements UserModel {
|
||||
public class UserAdapter extends AbstractAdapter implements UserModel {
|
||||
|
||||
private final UserData user;
|
||||
private final NoSQL noSQL;
|
||||
private final UserEntity user;
|
||||
|
||||
public UserAdapter(UserData userData, NoSQL noSQL) {
|
||||
this.user = userData;
|
||||
this.noSQL = noSQL;
|
||||
public UserAdapter(UserEntity userEntity, MongoStoreInvocationContext invContext) {
|
||||
super(invContext);
|
||||
this.user = userEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -38,7 +40,7 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
user.setEnabled(enabled);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,7 +51,7 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
user.setFirstName(firstName);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,7 +62,7 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
user.setLastName(lastName);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,7 +73,7 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setEmail(String email) {
|
||||
user.setEmail(email);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,61 +84,112 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setEmailVerified(boolean verified) {
|
||||
user.setEmailVerified(verified);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
user.setAttribute(name, value);
|
||||
if (user.getAttributes() == null) {
|
||||
user.setAttributes(new HashMap<String, String>());
|
||||
}
|
||||
|
||||
user.getAttributes().put(name, value);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
user.removeAttribute(name);
|
||||
noSQL.saveObject(user);
|
||||
if (user.getAttributes() == null) return;
|
||||
|
||||
user.getAttributes().remove(name);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return user.getAttribute(name);
|
||||
return user.getAttributes()==null ? null : user.getAttributes().get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return user.getAttributes();
|
||||
return user.getAttributes()==null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(user.getAttributes());
|
||||
}
|
||||
|
||||
public UserData getUser() {
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
List<RequiredAction> actions = user.getRequiredActions();
|
||||
public Set<String> getWebOrigins() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
if (user.getWebOrigins() != null) {
|
||||
result.addAll(user.getWebOrigins());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compatibility with picketlink impl
|
||||
if (actions == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<RequiredAction> s = new HashSet<RequiredAction>();
|
||||
for (RequiredAction a : actions) {
|
||||
s.add(a);
|
||||
@Override
|
||||
public void setWebOrigins(Set<String> webOrigins) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
result.addAll(webOrigins);
|
||||
user.setWebOrigins(result);
|
||||
updateUser();
|
||||
}
|
||||
return Collections.unmodifiableSet(s);
|
||||
|
||||
@Override
|
||||
public void addWebOrigin(String webOrigin) {
|
||||
getMongoStore().pushItemToList(user, "webOrigins", webOrigin, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWebOrigin(String webOrigin) {
|
||||
getMongoStore().pullItemFromList(user, "webOrigins", webOrigin, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
if (user.getRedirectUris() != null) {
|
||||
result.addAll(user.getRedirectUris());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUris(Set<String> redirectUris) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
result.addAll(redirectUris);
|
||||
user.setRedirectUris(result);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRedirectUri(String redirectUri) {
|
||||
getMongoStore().pushItemToList(user, "redirectUris", redirectUri, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRedirectUri(String redirectUri) {
|
||||
getMongoStore().pullItemFromList(user, "redirectUris", redirectUri, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RequiredAction> getRequiredActions() {
|
||||
Set<RequiredAction> result = new HashSet<RequiredAction>();
|
||||
if (user.getRequiredActions() != null) {
|
||||
result.addAll(user.getRequiredActions());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
// Push action only if it's not already here
|
||||
if (user.getRequiredActions() == null || !user.getRequiredActions().contains(action)) {
|
||||
noSQL.pushItemToList(user, "requiredActions", action);
|
||||
}
|
||||
getMongoStore().pushItemToList(user, "requiredActions", action, true, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
noSQL.pullItemFromList(user, "requiredActions", action);
|
||||
getMongoStore().pullItemFromList(user, "requiredActions", action, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -147,46 +200,15 @@ public class UserAdapter implements UserModel {
|
|||
@Override
|
||||
public void setTotp(boolean totp) {
|
||||
user.setTotp(totp);
|
||||
noSQL.saveObject(user);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
protected void updateUser() {
|
||||
getMongoStore().updateEntity(user, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getWebOrigins() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWebOrigins(Set<String> webOrigins) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addWebOrigin(String webOrigin) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeWebOrigin(String webOrigin) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRedirectUris() {
|
||||
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRedirectUris(Set<String> redirectUris) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRedirectUri(String redirectUri) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRedirectUri(String redirectUri) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
public AbstractMongoIdentifiableEntity getMongoEntity() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.credentials;
|
||||
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
|
||||
import org.picketlink.idm.credential.Credentials;
|
||||
import org.picketlink.idm.credential.encoder.PasswordEncoder;
|
||||
import org.picketlink.idm.credential.encoder.SHAPasswordEncoder;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler}
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PasswordCredentialHandler {
|
||||
|
||||
private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Stores a <b>stateless</b> instance of {@link org.picketlink.idm.credential.encoder.PasswordEncoder} that should be used to encode passwords.
|
||||
* </p>
|
||||
*/
|
||||
public static final String PASSWORD_ENCODER = "PASSWORD_ENCODER";
|
||||
|
||||
private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);;
|
||||
|
||||
public PasswordCredentialHandler(Map<String, Object> options) {
|
||||
setup(options);
|
||||
}
|
||||
|
||||
private void setup(Map<String, Object> options) {
|
||||
if (options != null) {
|
||||
Object providedEncoder = options.get(PASSWORD_ENCODER);
|
||||
|
||||
if (providedEncoder != null) {
|
||||
if (PasswordEncoder.class.isInstance(providedEncoder)) {
|
||||
this.passwordEncoder = (PasswordEncoder) providedEncoder;
|
||||
} else {
|
||||
throw new IllegalArgumentException("The password encoder [" + providedEncoder
|
||||
+ "] must be an instance of " + PasswordEncoder.class.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate) {
|
||||
Credentials.Status status = Credentials.Status.INVALID;
|
||||
|
||||
user = noSQL.loadObject(UserData.class, user.getId());
|
||||
|
||||
// If the user for the provided username cannot be found we fail validation
|
||||
if (user != null) {
|
||||
if (user.isEnabled()) {
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("userId", user.getId())
|
||||
.build();
|
||||
PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query);
|
||||
|
||||
// If the stored hash is null we automatically fail validation
|
||||
if (passwordData != null) {
|
||||
// TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
|
||||
if (!isCredentialExpired(passwordData.getExpiryDate())) {
|
||||
|
||||
boolean matches = this.passwordEncoder.verify(saltPassword(passwordToValidate, passwordData.getSalt()), passwordData.getEncodedHash());
|
||||
|
||||
if (matches) {
|
||||
status = Credentials.Status.VALID;
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.EXPIRED;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.ACCOUNT_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void update(NoSQL noSQL, UserData user, String password,
|
||||
Date effectiveDate, Date expiryDate) {
|
||||
|
||||
// Delete existing password of user
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("userId", user.getId())
|
||||
.build();
|
||||
noSQL.removeObjects(PasswordData.class, query);
|
||||
|
||||
PasswordData passwordData = new PasswordData();
|
||||
|
||||
String passwordSalt = generateSalt();
|
||||
|
||||
passwordData.setSalt(passwordSalt);
|
||||
passwordData.setEncodedHash(this.passwordEncoder.encode(saltPassword(password, passwordSalt)));
|
||||
|
||||
if (effectiveDate != null) {
|
||||
passwordData.setEffectiveDate(effectiveDate);
|
||||
}
|
||||
|
||||
passwordData.setExpiryDate(expiryDate);
|
||||
|
||||
passwordData.setUserId(user.getId());
|
||||
|
||||
noSQL.saveObject(passwordData);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Salt the give <code>rawPassword</code> with the specified <code>salt</code> value.
|
||||
* </p>
|
||||
*
|
||||
* @param rawPassword
|
||||
* @param salt
|
||||
* @return
|
||||
*/
|
||||
private String saltPassword(String rawPassword, String salt) {
|
||||
return salt + rawPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Generates a random string to be used as a salt for passwords.
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String generateSalt() {
|
||||
// TODO: always returns same salt (See https://issues.jboss.org/browse/PLINK-258)
|
||||
/*SecureRandom pseudoRandom = null;
|
||||
|
||||
try {
|
||||
pseudoRandom = SecureRandom.getInstance(DEFAULT_SALT_ALGORITHM);
|
||||
pseudoRandom.setSeed(1024);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Error getting SecureRandom instance: " + DEFAULT_SALT_ALGORITHM, e);
|
||||
}
|
||||
|
||||
return String.valueOf(pseudoRandom.nextLong());*/
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public static boolean isCredentialExpired(Date expiryDate) {
|
||||
return expiryDate != null && new Date().compareTo(expiryDate) > 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.credentials;
|
||||
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||
import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
|
||||
import org.picketlink.idm.credential.Credentials;
|
||||
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
|
||||
import static org.picketlink.idm.credential.util.TimeBasedOTP.*;
|
||||
|
||||
/**
|
||||
* Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler}
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class TOTPCredentialHandler extends PasswordCredentialHandler {
|
||||
|
||||
public static final String ALGORITHM = "ALGORITHM";
|
||||
public static final String INTERVAL_SECONDS = "INTERVAL_SECONDS";
|
||||
public static final String NUMBER_DIGITS = "NUMBER_DIGITS";
|
||||
public static final String DELAY_WINDOW = "DELAY_WINDOW";
|
||||
public static final String DEFAULT_DEVICE = "DEFAULT_DEVICE";
|
||||
|
||||
private TimeBasedOTP totp;
|
||||
|
||||
public TOTPCredentialHandler(Map<String, Object> options) {
|
||||
super(options);
|
||||
setup(options);
|
||||
}
|
||||
|
||||
private void setup(Map<String, Object> options) {
|
||||
String algorithm = getConfigurationProperty(options, ALGORITHM, DEFAULT_ALGORITHM);
|
||||
String intervalSeconds = getConfigurationProperty(options, INTERVAL_SECONDS, "" + DEFAULT_INTERVAL_SECONDS);
|
||||
String numberDigits = getConfigurationProperty(options, NUMBER_DIGITS, "" + DEFAULT_NUMBER_DIGITS);
|
||||
String delayWindow = getConfigurationProperty(options, DELAY_WINDOW, "" + DEFAULT_DELAY_WINDOW);
|
||||
|
||||
this.totp = new TimeBasedOTP(algorithm, Integer.parseInt(numberDigits), Integer.valueOf(intervalSeconds), Integer.valueOf(delayWindow));
|
||||
}
|
||||
|
||||
public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate, String token, String device) {
|
||||
Credentials.Status status = super.validate(noSQL, user, passwordToValidate);
|
||||
|
||||
if (Credentials.Status.VALID != status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
device = getDevice(device);
|
||||
|
||||
user = noSQL.loadObject(UserData.class, user.getId());
|
||||
|
||||
// If the user for the provided username cannot be found we fail validation
|
||||
if (user != null) {
|
||||
if (user.isEnabled()) {
|
||||
|
||||
// Try to find OTP based on userId and device (For now assume that this is unique combination)
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("userId", user.getId())
|
||||
.andCondition("device", device)
|
||||
.build();
|
||||
OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
|
||||
|
||||
// If the stored OTP is null we automatically fail validation
|
||||
if (otpData != null) {
|
||||
// TODO: Status.INVALID should have bigger priority than Status.EXPIRED?
|
||||
if (!PasswordCredentialHandler.isCredentialExpired(otpData.getExpiryDate())) {
|
||||
boolean isValid = this.totp.validate(token, otpData.getSecretKey().getBytes());
|
||||
if (!isValid) {
|
||||
status = Credentials.Status.INVALID;
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.EXPIRED;
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.UNVALIDATED;
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.ACCOUNT_DISABLED;
|
||||
}
|
||||
} else {
|
||||
status = Credentials.Status.INVALID;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void update(NoSQL noSQL, UserData user, String secret, String device, Date effectiveDate, Date expiryDate) {
|
||||
device = getDevice(device);
|
||||
|
||||
// Try to look if user already has otp (Right now, supports just one OTP per user)
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("userId", user.getId())
|
||||
.andCondition("device", device)
|
||||
.build();
|
||||
|
||||
OTPData otpData = noSQL.loadSingleObject(OTPData.class, query);
|
||||
if (otpData == null) {
|
||||
otpData = new OTPData();
|
||||
}
|
||||
|
||||
otpData.setSecretKey(secret);
|
||||
otpData.setDevice(device);
|
||||
|
||||
if (effectiveDate != null) {
|
||||
otpData.setEffectiveDate(effectiveDate);
|
||||
}
|
||||
|
||||
otpData.setExpiryDate(expiryDate);
|
||||
otpData.setUserId(user.getId());
|
||||
|
||||
noSQL.saveObject(otpData);
|
||||
}
|
||||
|
||||
private String getDevice(String device) {
|
||||
if (isNullOrEmpty(device)) {
|
||||
device = DEFAULT_DEVICE;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
private String getConfigurationProperty(Map<String, Object> options, String key, String defaultValue) {
|
||||
Object value = options.get(key);
|
||||
|
||||
if (value != null) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data;
|
||||
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "oauthClients")
|
||||
public class OAuthClientData implements NoSQLObject {
|
||||
|
||||
private String id;
|
||||
private String baseUrl;
|
||||
|
||||
private String oauthAgentId;
|
||||
private String realmId;
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getOauthAgentId() {
|
||||
return oauthAgentId;
|
||||
}
|
||||
|
||||
public void setOauthAgentId(String oauthUserId) {
|
||||
this.oauthAgentId = oauthUserId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
// Remove user of this oauthClient
|
||||
noSQL.removeObject(UserData.class, oauthAgentId);
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data;
|
||||
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "realms")
|
||||
public class RealmData implements NoSQLObject {
|
||||
|
||||
private String oid;
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean enabled;
|
||||
private boolean sslNotRequired;
|
||||
private boolean cookieLoginAllowed;
|
||||
private boolean registrationAllowed;
|
||||
private boolean verifyEmail;
|
||||
private boolean resetPasswordAllowed;
|
||||
private boolean social;
|
||||
private boolean automaticRegistrationAfterSocialLogin;
|
||||
private int tokenLifespan;
|
||||
private int accessCodeLifespan;
|
||||
private int accessCodeLifespanUserAction;
|
||||
private String publicKeyPem;
|
||||
private String privateKeyPem;
|
||||
|
||||
private List<String> defaultRoles;
|
||||
private List<String> realmAdmins;
|
||||
|
||||
@NoSQLId
|
||||
public String getOid() {
|
||||
return oid;
|
||||
}
|
||||
|
||||
public void setOid(String oid) {
|
||||
this.oid = oid;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String realmName) {
|
||||
this.name = realmName;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isSslNotRequired() {
|
||||
return sslNotRequired;
|
||||
}
|
||||
|
||||
public void setSslNotRequired(boolean sslNotRequired) {
|
||||
this.sslNotRequired = sslNotRequired;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isCookieLoginAllowed() {
|
||||
return cookieLoginAllowed;
|
||||
}
|
||||
|
||||
public void setCookieLoginAllowed(boolean cookieLoginAllowed) {
|
||||
this.cookieLoginAllowed = cookieLoginAllowed;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isRegistrationAllowed() {
|
||||
return registrationAllowed;
|
||||
}
|
||||
|
||||
public void setRegistrationAllowed(boolean registrationAllowed) {
|
||||
this.registrationAllowed = registrationAllowed;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isVerifyEmail() {
|
||||
return verifyEmail;
|
||||
}
|
||||
|
||||
public void setVerifyEmail(boolean verifyEmail) {
|
||||
this.verifyEmail = verifyEmail;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isResetPasswordAllowed() {
|
||||
return resetPasswordAllowed;
|
||||
}
|
||||
|
||||
public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
|
||||
this.resetPasswordAllowed = resetPasswordAllowed;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isSocial() {
|
||||
return social;
|
||||
}
|
||||
|
||||
public void setSocial(boolean social) {
|
||||
this.social = social;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||
return automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||
this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public int getTokenLifespan() {
|
||||
return tokenLifespan;
|
||||
}
|
||||
|
||||
public void setTokenLifespan(int tokenLifespan) {
|
||||
this.tokenLifespan = tokenLifespan;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public int getAccessCodeLifespan() {
|
||||
return accessCodeLifespan;
|
||||
}
|
||||
|
||||
public void setAccessCodeLifespan(int accessCodeLifespan) {
|
||||
this.accessCodeLifespan = accessCodeLifespan;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public int getAccessCodeLifespanUserAction() {
|
||||
return accessCodeLifespanUserAction;
|
||||
}
|
||||
|
||||
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
|
||||
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getPublicKeyPem() {
|
||||
return publicKeyPem;
|
||||
}
|
||||
|
||||
public void setPublicKeyPem(String publicKeyPem) {
|
||||
this.publicKeyPem = publicKeyPem;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getPrivateKeyPem() {
|
||||
return privateKeyPem;
|
||||
}
|
||||
|
||||
public void setPrivateKeyPem(String privateKeyPem) {
|
||||
this.privateKeyPem = privateKeyPem;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public List<String> getDefaultRoles() {
|
||||
return defaultRoles;
|
||||
}
|
||||
|
||||
public void setDefaultRoles(List<String> defaultRoles) {
|
||||
this.defaultRoles = defaultRoles;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public List<String> getRealmAdmins() {
|
||||
return realmAdmins;
|
||||
}
|
||||
|
||||
public void setRealmAdmins(List<String> realmAdmins) {
|
||||
this.realmAdmins = realmAdmins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("realmId", oid)
|
||||
.build();
|
||||
|
||||
// Remove all users of this realm
|
||||
noSQL.removeObjects(UserData.class, query);
|
||||
|
||||
// Remove all requiredCredentials of this realm
|
||||
noSQL.removeObjects(RequiredCredentialData.class, query);
|
||||
|
||||
// Remove all roles of this realm
|
||||
noSQL.removeObjects(RoleData.class, query);
|
||||
|
||||
// Remove all applications of this realm
|
||||
noSQL.removeObjects(ApplicationData.class, query);
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data;
|
||||
|
||||
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "requiredCredentials")
|
||||
public class RequiredCredentialData extends AbstractNoSQLObject {
|
||||
|
||||
public static final int CLIENT_TYPE_USER = 1;
|
||||
public static final int CLIENT_TYPE_RESOURCE = 2;
|
||||
public static final int CLIENT_TYPE_OAUTH_RESOURCE = 3;
|
||||
|
||||
private String id;
|
||||
|
||||
private String type;
|
||||
private boolean input;
|
||||
private boolean secret;
|
||||
private String formLabel;
|
||||
|
||||
private String realmId;
|
||||
private int clientType;
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public void setInput(boolean input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public boolean isSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(boolean secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getFormLabel() {
|
||||
return formLabel;
|
||||
}
|
||||
|
||||
public void setFormLabel(String formLabel) {
|
||||
this.formLabel = formLabel;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public int getClientType() {
|
||||
return clientType;
|
||||
}
|
||||
|
||||
public void setClientType(int clientType) {
|
||||
this.clientType = clientType;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "roles")
|
||||
public class RoleData implements NoSQLObject {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RoleData.class);
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
private String realmId;
|
||||
private String applicationId;
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(String applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
// Remove this role from all users, which has it
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("roleIds", id)
|
||||
.build();
|
||||
|
||||
List<UserData> users = noSQL.loadObjects(UserData.class, query);
|
||||
for (UserData user : users) {
|
||||
logger.info("Removing role " + getName() + " from user " + user.getLoginName());
|
||||
noSQL.pullItemFromList(user, "roleIds", getId());
|
||||
}
|
||||
|
||||
// Remove this scope from all users, which has it
|
||||
query = noSQL.createQueryBuilder()
|
||||
.andCondition("scopeIds", id)
|
||||
.build();
|
||||
|
||||
users = noSQL.loadObjects(UserData.class, query);
|
||||
for (UserData user : users) {
|
||||
logger.info("Removing scope " + getName() + " from user " + user.getLoginName());
|
||||
noSQL.pullItemFromList(user, "scopeIds", getId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,55 +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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "socialLinks")
|
||||
public class SocialLinkData extends AbstractNoSQLObject {
|
||||
|
||||
private String socialUsername;
|
||||
private String socialProvider;
|
||||
private String userId;
|
||||
// realmId is needed to allow searching as combination socialUsername+socialProvider may not be unique
|
||||
// (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
|
||||
private String realmId;
|
||||
|
||||
@NoSQLField
|
||||
public String getSocialUsername() {
|
||||
return socialUsername;
|
||||
}
|
||||
|
||||
public void setSocialUsername(String socialUsername) {
|
||||
this.socialUsername = socialUsername;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getSocialProvider() {
|
||||
return socialProvider;
|
||||
}
|
||||
|
||||
public void setSocialProvider(String socialProvider) {
|
||||
this.socialProvider = socialProvider;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data.credentials;
|
||||
|
||||
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "otpCredentials")
|
||||
public class OTPData extends AbstractNoSQLObject {
|
||||
|
||||
private Date effectiveDate = new Date();
|
||||
private Date expiryDate;
|
||||
private String secretKey;
|
||||
private String device;
|
||||
|
||||
private String userId;
|
||||
|
||||
@NoSQLField
|
||||
public Date getEffectiveDate() {
|
||||
return effectiveDate;
|
||||
}
|
||||
|
||||
public void setEffectiveDate(Date effectiveDate) {
|
||||
this.effectiveDate = effectiveDate;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public Date getExpiryDate() {
|
||||
return expiryDate;
|
||||
}
|
||||
|
||||
public void setExpiryDate(Date expiryDate) {
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
public void setDevice(String device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package org.keycloak.models.mongo.keycloak.data.credentials;
|
||||
|
||||
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "passwordCredentials")
|
||||
public class PasswordData extends AbstractNoSQLObject {
|
||||
|
||||
private Date effectiveDate = new Date();
|
||||
private Date expiryDate;
|
||||
private String encodedHash;
|
||||
private String salt;
|
||||
|
||||
private String userId;
|
||||
|
||||
@NoSQLField
|
||||
public Date getEffectiveDate() {
|
||||
return effectiveDate;
|
||||
}
|
||||
|
||||
public void setEffectiveDate(Date effectiveDate) {
|
||||
this.effectiveDate = effectiveDate;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public Date getExpiryDate() {
|
||||
return expiryDate;
|
||||
}
|
||||
|
||||
public void setExpiryDate(Date expiryDate) {
|
||||
this.expiryDate = expiryDate;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getEncodedHash() {
|
||||
return encodedHash;
|
||||
}
|
||||
|
||||
public void setEncodedHash(String encodedHash) {
|
||||
this.encodedHash = encodedHash;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public void setSalt(String salt) {
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
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.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.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "applications")
|
||||
public class ApplicationData implements NoSQLObject {
|
||||
@MongoCollection(collectionName = "applications")
|
||||
public class ApplicationEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean enabled;
|
||||
private boolean surrogateAuthRequired;
|
||||
|
@ -23,16 +26,10 @@ public class ApplicationData implements NoSQLObject {
|
|||
private String resourceUserId;
|
||||
private String realmId;
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
// We are using names of defaultRoles (not ids)
|
||||
private List<String> defaultRoles = new ArrayList<String>();
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -41,7 +38,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
@ -50,7 +47,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public boolean isSurrogateAuthRequired() {
|
||||
return surrogateAuthRequired;
|
||||
}
|
||||
|
@ -59,7 +56,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getManagementUrl() {
|
||||
return managementUrl;
|
||||
}
|
||||
|
@ -68,7 +65,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.managementUrl = managementUrl;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
@ -77,7 +74,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getResourceUserId() {
|
||||
return resourceUserId;
|
||||
}
|
||||
|
@ -86,7 +83,7 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.resourceUserId = resourceUserId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
@ -95,15 +92,24 @@ public class ApplicationData implements NoSQLObject {
|
|||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getDefaultRoles() {
|
||||
return defaultRoles;
|
||||
}
|
||||
|
||||
public void setDefaultRoles(List<String> defaultRoles) {
|
||||
this.defaultRoles = defaultRoles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
// Remove resourceUser of this application
|
||||
noSQL.removeObject(UserData.class, resourceUserId);
|
||||
context.getMongoStore().removeEntity(UserEntity.class, resourceUserId, context);
|
||||
|
||||
// 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(getId())
|
||||
.get();
|
||||
context.getMongoStore().removeEntities(RoleEntity.class, query, context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CredentialEntity implements MongoEntity {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
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.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "oauthClients")
|
||||
public class OAuthClientEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
private String name;
|
||||
|
||||
private String oauthAgentId;
|
||||
private String realmId;
|
||||
|
||||
@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(MongoStoreInvocationContext context) {
|
||||
// Remove user of this oauthClient
|
||||
context.getMongoStore().removeEntity(UserEntity.class, oauthAgentId, context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
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.context.MongoStoreInvocationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "realms")
|
||||
public class RealmEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
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;
|
||||
|
||||
private String loginTheme;
|
||||
private String accountTheme;
|
||||
|
||||
// We are using names of defaultRoles (not ids)
|
||||
private List<String> defaultRoles = new ArrayList<String>();
|
||||
|
||||
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||
private List<RequiredCredentialEntity> requiredApplicationCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||
private List<RequiredCredentialEntity> requiredOAuthClientCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||
|
||||
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
||||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||
|
||||
@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 String getLoginTheme() {
|
||||
return loginTheme;
|
||||
}
|
||||
|
||||
public void setLoginTheme(String loginTheme) {
|
||||
this.loginTheme = loginTheme;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getAccountTheme() {
|
||||
return accountTheme;
|
||||
}
|
||||
|
||||
public void setAccountTheme(String accountTheme) {
|
||||
this.accountTheme = accountTheme;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getDefaultRoles() {
|
||||
return defaultRoles;
|
||||
}
|
||||
|
||||
public void setDefaultRoles(List<String> defaultRoles) {
|
||||
this.defaultRoles = defaultRoles;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<RequiredCredentialEntity> getRequiredCredentials() {
|
||||
return requiredCredentials;
|
||||
}
|
||||
|
||||
public void setRequiredCredentials(List<RequiredCredentialEntity> requiredCredentials) {
|
||||
this.requiredCredentials = requiredCredentials;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<RequiredCredentialEntity> getRequiredApplicationCredentials() {
|
||||
return requiredApplicationCredentials;
|
||||
}
|
||||
|
||||
public void setRequiredApplicationCredentials(List<RequiredCredentialEntity> requiredApplicationCredentials) {
|
||||
this.requiredApplicationCredentials = requiredApplicationCredentials;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<RequiredCredentialEntity> getRequiredOAuthClientCredentials() {
|
||||
return requiredOAuthClientCredentials;
|
||||
}
|
||||
|
||||
public void setRequiredOAuthClientCredentials(List<RequiredCredentialEntity> requiredOAuthClientCredentials) {
|
||||
this.requiredOAuthClientCredentials = requiredOAuthClientCredentials;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public Map<String, String> getSmtpConfig() {
|
||||
return smtpConfig;
|
||||
}
|
||||
|
||||
public void setSmtpConfig(Map<String, String> smptConfig) {
|
||||
this.smtpConfig = smptConfig;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public Map<String, String> getSocialConfig() {
|
||||
return socialConfig;
|
||||
}
|
||||
|
||||
public void setSocialConfig(Map<String, String> socialConfig) {
|
||||
this.socialConfig = socialConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext context) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(getId())
|
||||
.get();
|
||||
|
||||
// Remove all users of this realm
|
||||
context.getMongoStore().removeEntities(UserEntity.class, query, context);
|
||||
|
||||
// Remove all roles of this realm
|
||||
context.getMongoStore().removeEntities(RoleEntity.class, query, context);
|
||||
|
||||
// Remove all applications of this realm
|
||||
context.getMongoStore().removeEntities(ApplicationEntity.class, query, context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RequiredCredentialEntity implements MongoEntity {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
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.MongoStore;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "roles")
|
||||
public class RoleEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RoleEntity.class);
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
|
||||
private List<String> compositeRoleIds;
|
||||
|
||||
private String realmId;
|
||||
private String applicationId;
|
||||
|
||||
@MongoField
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getCompositeRoleIds() {
|
||||
return compositeRoleIds;
|
||||
}
|
||||
|
||||
public void setCompositeRoleIds(List<String> compositeRoleIds) {
|
||||
this.compositeRoleIds = compositeRoleIds;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
||||
public void setRealmId(String realmId) {
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(String applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invContext) {
|
||||
MongoStore mongoStore = invContext.getMongoStore();
|
||||
|
||||
// Remove this role from all users, which has it
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("roleIds").is(getId())
|
||||
.get();
|
||||
|
||||
List<UserEntity> 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);
|
||||
}
|
||||
|
||||
// Remove this scope from all users, which has it
|
||||
query = new QueryBuilder()
|
||||
.and("scopeIds").is(getId())
|
||||
.get();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Remove defaultRoles from realm
|
||||
if (realmId != null) {
|
||||
RealmEntity realmEntity = mongoStore.loadEntity(RealmEntity.class, realmId, invContext);
|
||||
|
||||
// Realm might be already removed at this point
|
||||
if (realmEntity != null) {
|
||||
mongoStore.pullItemFromList(realmEntity, "defaultRoles", getId(), invContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove defaultRoles from application
|
||||
if (applicationId != null) {
|
||||
ApplicationEntity appEntity = mongoStore.loadEntity(ApplicationEntity.class, applicationId, invContext);
|
||||
|
||||
// Application might be already removed at this point
|
||||
if (appEntity != null) {
|
||||
mongoStore.pullItemFromList(appEntity, "defaultRoles", getId(), invContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this role from others who has it as composite
|
||||
query = new QueryBuilder()
|
||||
.and("compositeRoleIds").is(getId())
|
||||
.get();
|
||||
List<RoleEntity> parentRoles = mongoStore.loadEntities(RoleEntity.class, query, invContext);
|
||||
for (RoleEntity role : parentRoles) {
|
||||
mongoStore.pullItemFromList(role, "compositeRoleIds", getId(), invContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialLinkEntity implements MongoEntity {
|
||||
|
||||
private String socialUsername;
|
||||
private String socialProvider;
|
||||
|
||||
@MongoField
|
||||
public String getSocialUsername() {
|
||||
return socialUsername;
|
||||
}
|
||||
|
||||
public void setSocialUsername(String socialUsername) {
|
||||
this.socialUsername = socialUsername;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public String getSocialProvider() {
|
||||
return socialProvider;
|
||||
}
|
||||
|
||||
public void setSocialProvider(String socialProvider) {
|
||||
this.socialProvider = socialProvider;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int code = 1;
|
||||
if (socialUsername != null) {
|
||||
code = code * 13;
|
||||
}
|
||||
if (socialProvider != null) {
|
||||
code = code * 17;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
|
@ -1,26 +1,21 @@
|
|||
package org.keycloak.models.mongo.keycloak.data;
|
||||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.mongo.api.AbstractAttributedNoSQLObject;
|
||||
import org.keycloak.models.mongo.api.NoSQL;
|
||||
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||
import org.keycloak.models.mongo.api.NoSQLField;
|
||||
import org.keycloak.models.mongo.api.NoSQLId;
|
||||
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "users")
|
||||
public class UserData extends AbstractAttributedNoSQLObject {
|
||||
@MongoCollection(collectionName = "users")
|
||||
public class UserEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(UserData.class);
|
||||
|
||||
private String id;
|
||||
private String loginName;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
@ -33,18 +28,15 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
|
||||
private List<String> roleIds;
|
||||
private List<String> scopeIds;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private List<String> webOrigins;
|
||||
private List<String> redirectUris;
|
||||
private List<UserModel.RequiredAction> requiredActions;
|
||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||
private List<SocialLinkEntity> socialLinks;
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getLoginName() {
|
||||
return loginName;
|
||||
}
|
||||
|
@ -53,7 +45,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.loginName = loginName;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
@ -62,7 +54,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
@ -71,7 +63,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
@ -80,7 +72,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.email = email;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public boolean isEmailVerified() {
|
||||
return emailVerified;
|
||||
}
|
||||
|
@ -89,7 +81,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.emailVerified = emailVerified;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
@ -98,7 +90,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public boolean isTotp() {
|
||||
return totp;
|
||||
}
|
||||
|
@ -107,7 +99,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.totp = totp;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getRealmId() {
|
||||
return realmId;
|
||||
}
|
||||
|
@ -116,7 +108,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<String> getRoleIds() {
|
||||
return roleIds;
|
||||
}
|
||||
|
@ -125,7 +117,7 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.roleIds = roleIds;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<String> getScopeIds() {
|
||||
return scopeIds;
|
||||
}
|
||||
|
@ -134,7 +126,34 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.scopeIds = scopeIds;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getWebOrigins() {
|
||||
return webOrigins;
|
||||
}
|
||||
|
||||
public void setWebOrigins(List<String> webOrigins) {
|
||||
this.webOrigins = webOrigins;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<String> getRedirectUris() {
|
||||
return redirectUris;
|
||||
}
|
||||
|
||||
public void setRedirectUris(List<String> redirectUris) {
|
||||
this.redirectUris = redirectUris;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<UserModel.RequiredAction> getRequiredActions() {
|
||||
return requiredActions;
|
||||
}
|
||||
|
@ -143,25 +162,21 @@ public class UserData extends AbstractAttributedNoSQLObject {
|
|||
this.requiredActions = requiredActions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(NoSQL noSQL) {
|
||||
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||
.andCondition("userId", id)
|
||||
.build();
|
||||
|
||||
// Remove social links and passwords of this user
|
||||
noSQL.removeObjects(SocialLinkData.class, query);
|
||||
noSQL.removeObjects(PasswordData.class, query);
|
||||
|
||||
// Remove this user from all realms, which have him as an admin
|
||||
NoSQLQuery realmQuery = noSQL.createQueryBuilder()
|
||||
.andCondition("realmAdmins", id)
|
||||
.build();
|
||||
|
||||
List<RealmData> realms = noSQL.loadObjects(RealmData.class, realmQuery);
|
||||
for (RealmData realm : realms) {
|
||||
logger.info("Removing admin user " + getLoginName() + " from realm " + realm.getId());
|
||||
noSQL.pullItemFromList(realm, "realmAdmins", getId());
|
||||
@MongoField
|
||||
public List<CredentialEntity> getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void setCredentials(List<CredentialEntity> credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public List<SocialLinkEntity> getSocialLinks() {
|
||||
return socialLinks;
|
||||
}
|
||||
|
||||
public void setSocialLinks(List<SocialLinkEntity> socialLinks) {
|
||||
this.socialLinks = socialLinks;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.keycloak.models.mongo.utils;
|
||||
|
||||
/**
|
||||
* Encapsulates all info about configuration of MongoDB instance
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoConfiguration {
|
||||
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String dbName;
|
||||
|
||||
private final boolean clearCollectionsOnStartup;
|
||||
|
||||
public MongoConfiguration(String host, int port, String dbName, boolean clearCollectionsOnStartup) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.dbName = dbName;
|
||||
this.clearCollectionsOnStartup = clearCollectionsOnStartup;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getDbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
public boolean isClearCollectionsOnStartup() {
|
||||
return clearCollectionsOnStartup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("MongoConfiguration: host: %s, port: %d, dbName: %s, clearCollectionsOnStartup: %b",
|
||||
host, port, dbName, clearCollectionsOnStartup);
|
||||
}
|
||||
}
|
|
@ -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.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;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class MongoModelUtils {
|
||||
|
||||
public static List<ObjectId> convertStringsToObjectIds(Collection<String> strings) {
|
||||
List<ObjectId> result = new ArrayList<ObjectId>();
|
||||
for (String id : strings) {
|
||||
result.add(new ObjectId(id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get everything including both application and realm roles
|
||||
public static List<RoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
|
||||
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||
List<String> roleIds = userEntity.getRoleIds();
|
||||
|
||||
if (roleIds == null || roleIds.isEmpty()) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("_id").in(convertStringsToObjectIds(roleIds))
|
||||
.get();
|
||||
return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext);
|
||||
}
|
||||
|
||||
// Get everything including both application and realm scopes
|
||||
public static List<RoleEntity> getAllScopesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
|
||||
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||
List<String> scopeIds = userEntity.getScopeIds();
|
||||
|
||||
if (scopeIds == null || scopeIds.isEmpty()) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("_id").in(convertStringsToObjectIds(scopeIds))
|
||||
.get();
|
||||
return invContext.getMongoStore().loadEntities(RoleEntity.class, query, invContext);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.keycloak.models.mongo.utils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SystemPropertiesConfigurationProvider {
|
||||
|
||||
private static final String MONGO_HOST = "keycloak.mongo.host";
|
||||
private static final String MONGO_PORT = "keycloak.mongo.port";
|
||||
private static final String MONGO_DB_NAME = "keycloak.mongo.db";
|
||||
private static final String MONGO_CLEAR_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 String MONGO_DEFAULT_PORT = "27017";
|
||||
|
||||
public static String getMongoHost() {
|
||||
return getSystemPropertyWithFallback(MONGO_HOST, MONGO_HOST_2, "localhost");
|
||||
}
|
||||
|
||||
public static int getMongoPort() {
|
||||
String portProp = getSystemPropertyWithFallback(MONGO_PORT, MONGO_PORT_2, MONGO_DEFAULT_PORT);
|
||||
return Integer.parseInt(portProp);
|
||||
}
|
||||
|
||||
public static String getMongoDbName() {
|
||||
return getSystemPropertyWithFallback(MONGO_DB_NAME, MONGO_DB_NAME_2, "keycloak");
|
||||
}
|
||||
|
||||
public static boolean isClearCollectionsOnStartup() {
|
||||
String property = getSystemPropertyWithFallback(MONGO_CLEAR_ON_STARTUP, MONGO_CLEAR_ON_STARTUP_2, "false");
|
||||
return "true".equalsIgnoreCase(property);
|
||||
}
|
||||
|
||||
// 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
|
||||
public static MongoConfiguration createConfiguration() {
|
||||
return new MongoConfiguration(
|
||||
getMongoHost(),
|
||||
getMongoPort(),
|
||||
getMongoDbName(),
|
||||
isClearCollectionsOnStartup()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class Address extends AbstractNoSQLObject {
|
||||
public class Address implements MongoEntity {
|
||||
|
||||
private String street;
|
||||
private int number;
|
||||
private List<String> flatNumbers;
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getStreet() {
|
||||
return street;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class Address extends AbstractNoSQLObject {
|
|||
this.street = street;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public class Address extends AbstractNoSQLObject {
|
|||
this.number = number;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<String> getFlatNumbers() {
|
||||
return flatNumbers;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
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.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.models.mongo.impl.context.TransactionMongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.utils.SystemPropertiesConfigurationProvider;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -20,22 +25,22 @@ import java.util.List;
|
|||
*/
|
||||
public class MongoDBModelTest {
|
||||
|
||||
private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
|
||||
private static final Class<? extends MongoEntity>[] MANAGED_DATA_TYPES = (Class<? extends MongoEntity>[])new Class<?>[] {
|
||||
Person.class,
|
||||
Address.class,
|
||||
};
|
||||
|
||||
private MongoClient mongoClient;
|
||||
private NoSQL mongoDB;
|
||||
private MongoStore mongoStore;
|
||||
|
||||
@Before
|
||||
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");
|
||||
mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES);
|
||||
mongoStore = new MongoStoreImpl(db, true, MANAGED_DATA_TYPES);
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -47,25 +52,27 @@ public class MongoDBModelTest {
|
|||
mongoClient.close();
|
||||
}
|
||||
|
||||
// @Test
|
||||
@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.saveObject(john);
|
||||
mongoStore.insertEntity(john, context);
|
||||
|
||||
// Add another user
|
||||
Person mary = new Person();
|
||||
mary.setFirstName("mary");
|
||||
mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"}));
|
||||
mary.setKids(asList("Peter", "Paul", "Wendy"));
|
||||
|
||||
Address addr1 = new Address();
|
||||
addr1.setStreet("Elm");
|
||||
addr1.setNumber(5);
|
||||
addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"}));
|
||||
addr1.setFlatNumbers(asList("flat1", "flat2"));
|
||||
Address addr2 = new Address();
|
||||
List<Address> addresses = new ArrayList<Address>();
|
||||
addresses.add(addr1);
|
||||
|
@ -74,13 +81,14 @@ 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(asList(Person.Gender.FEMALE));
|
||||
|
||||
Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size());
|
||||
mongoStore.insertEntity(mary, context);
|
||||
|
||||
NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build();
|
||||
List<Person> persons = mongoDB.loadObjects(Person.class, query);
|
||||
Assert.assertEquals(2, mongoStore.loadEntities(Person.class, new QueryBuilder().get(), context).size());
|
||||
|
||||
DBObject query = new QueryBuilder().and("addresses.flatNumbers").is("flat1").get();
|
||||
List<Person> persons = mongoStore.loadEntities(Person.class, query, context);
|
||||
Assert.assertEquals(1, persons.size());
|
||||
mary = persons.get(0);
|
||||
Assert.assertEquals(mary.getFirstName(), "mary");
|
||||
|
@ -89,15 +97,15 @@ public class MongoDBModelTest {
|
|||
Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass());
|
||||
|
||||
// Test push/pull
|
||||
mongoDB.pushItemToList(mary, "kids", "Pauline");
|
||||
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);
|
||||
mongoStore.pushItemToList(mary, "addresses", addr3, true, context);
|
||||
|
||||
mary = mongoDB.loadObject(Person.class, mary.getId());
|
||||
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"));
|
||||
|
@ -107,5 +115,32 @@ 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");
|
||||
mongoStore.updateEntity(mary, context);
|
||||
|
||||
mary = mongoStore.loadEntity(Person.class, mary.getId(), context);
|
||||
Assert.assertEquals(3, mary.getAttributes().size());
|
||||
|
||||
mary.removeAttribute("attr2");
|
||||
mary.removeAttribute("nonExisting");
|
||||
mongoStore.updateEntity(mary, 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"));
|
||||
|
||||
context.commit();
|
||||
}
|
||||
|
||||
private <T> List<T> asList(T... objects) {
|
||||
List<T> list = new ArrayList<T>();
|
||||
list.addAll(Arrays.asList(objects));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +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.AbstractMongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NoSQLCollection(collectionName = "persons")
|
||||
public class Person extends AbstractNoSQLObject {
|
||||
@MongoCollection(collectionName = "persons")
|
||||
public class Person extends AbstractMongoIdentifiableEntity {
|
||||
|
||||
private String id;
|
||||
private String firstName;
|
||||
private int age;
|
||||
private List<String> kids;
|
||||
|
@ -21,18 +21,9 @@ public class Person extends AbstractNoSQLObject {
|
|||
private Address mainAddress;
|
||||
private Gender gender;
|
||||
private List<Gender> genders;
|
||||
private Map<String, String> attributes = new HashMap<String, String>();
|
||||
|
||||
|
||||
@NoSQLId
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
@ -41,7 +32,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
@ -50,7 +41,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.age = age;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public Gender getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
@ -59,7 +50,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.gender = gender;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<Gender> getGenders() {
|
||||
return genders;
|
||||
}
|
||||
|
@ -68,7 +59,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.genders = genders;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<String> getKids() {
|
||||
return kids;
|
||||
}
|
||||
|
@ -77,7 +68,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.kids = kids;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public List<Address> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
@ -86,7 +77,7 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
@NoSQLField
|
||||
@MongoField
|
||||
public Address getMainAddress() {
|
||||
return mainAddress;
|
||||
}
|
||||
|
@ -95,6 +86,23 @@ public class Person extends AbstractNoSQLObject {
|
|||
this.mainAddress = mainAddress;
|
||||
}
|
||||
|
||||
@MongoField
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public void addAttribute(String key, String value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String key) {
|
||||
attributes.remove(key);
|
||||
}
|
||||
|
||||
public static enum Gender {
|
||||
MALE, FEMALE
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<module>api</module>
|
||||
<!-- <module>picketlink</module> -->
|
||||
<module>jpa</module>
|
||||
<!-- <module>mongo</module> -->
|
||||
<module>mongo</module>
|
||||
<module>tests</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
69
model/tests/pom.xml
Normal file
69
model/tests/pom.xml
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-2-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-model-tests</artifactId>
|
||||
<name>Keycloak Model Tests</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>package-tests-jar</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.test;
|
||||
package org.keycloak.model.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.FixMethodOrder;
|
||||
|
@ -17,47 +17,30 @@ 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 org.keycloak.test.common.SessionFactoryTestContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class AdapterTest extends AbstractKeycloakTest {
|
||||
public class AdapterTest extends AbstractModelTest {
|
||||
private RealmModel realmModel;
|
||||
|
||||
public AdapterTest(SessionFactoryTestContext testContext) {
|
||||
super(testContext);
|
||||
}
|
||||
|
||||
@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 = getRealmManager().createRealm("JUGGLER");
|
||||
realmModel = realmManager.createRealm("JUGGLER");
|
||||
realmModel.setAccessCodeLifespan(100);
|
||||
realmModel.setAccessCodeLifespanUserAction(600);
|
||||
realmModel.setEnabled(true);
|
||||
|
@ -69,7 +52,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 +68,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 +80,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 +283,7 @@ public class AdapterTest extends AbstractKeycloakTest {
|
|||
user3.setEmail("knut@redhat.com");
|
||||
}
|
||||
|
||||
RealmManager adapter = getRealmManager();
|
||||
RealmManager adapter = realmManager;
|
||||
|
||||
{
|
||||
List<UserModel> userModels = adapter.searchUsers("total junk query", realmModel);
|
|
@ -1,19 +1,14 @@
|
|||
package org.keycloak.test;
|
||||
package org.keycloak.model.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 java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -21,24 +16,17 @@ import java.util.List;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ApplicationModelTest extends AbstractKeycloakServerTest {
|
||||
private KeycloakSessionFactory factory;
|
||||
private KeycloakSession identitySession;
|
||||
private RealmManager manager;
|
||||
public class ApplicationModelTest extends AbstractModelTest {
|
||||
private ApplicationModel application;
|
||||
private RealmModel realm;
|
||||
private ApplicationManager appManager;
|
||||
|
||||
@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 +45,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 +56,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);
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.services.managers;
|
||||
package org.keycloak.model.test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
@ -9,15 +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.common.AbstractKeycloakTest;
|
||||
import org.keycloak.test.common.SessionFactoryTestContext;
|
||||
|
||||
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<String, String> formData;
|
||||
|
@ -25,10 +24,6 @@ public class AuthenticationManagerTest extends AbstractKeycloakTest {
|
|||
private RealmModel realm;
|
||||
private UserModel user;
|
||||
|
||||
public AuthenticationManagerTest(SessionFactoryTestContext testContext) {
|
||||
super(testContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authForm() {
|
||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
||||
|
@ -126,7 +121,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");
|
|
@ -0,0 +1,132 @@
|
|||
package org.keycloak.model.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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CompositeRolesModelTest extends AbstractModelTest {
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
RealmManager manager = realmManager;
|
||||
RealmRepresentation rep = AbstractModelTest.loadJson("testcomposites.json");
|
||||
RealmModel realm = manager.createRealm("Test", rep.getRealm());
|
||||
manager.importRealm(rep, realm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppComposites() {
|
||||
Set<RoleModel> 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);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmAppComposites() {
|
||||
Set<RoleModel> requestedRoles = getRequestedRoles("APP_COMPOSITE_APPLICATION", "REALM_APP_COMPOSITE_USER");
|
||||
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("APP_ROLE_APPLICATION", "APP_ROLE_1", requestedRoles);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserCompositeAppComposite() throws Exception {
|
||||
Set<RoleModel> 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<RoleModel> 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<RoleModel> requestedRoles = getRequestedRoles("REALM_COMPOSITE_1_APPLICATION", "REALM_ROLE_1_USER");
|
||||
|
||||
Assert.assertEquals(1, requestedRoles.size());
|
||||
assertContains("realm", "REALM_ROLE_1", requestedRoles);
|
||||
}
|
||||
|
||||
// TODO: more tests...
|
||||
|
||||
// Same algorithm as in TokenManager.createAccessCode
|
||||
private Set<RoleModel> getRequestedRoles(String applicationName, String username) {
|
||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||
|
||||
RealmModel realm = realmManager.getRealm("Test");
|
||||
UserModel user = realm.getUser(username);
|
||||
ApplicationModel application = realm.getApplicationByName(applicationName);
|
||||
|
||||
Set<RoleModel> roleMappings = realm.getRoleMappings(user);
|
||||
Set<RoleModel> scopeMappings = realm.getScopeMappings(application.getApplicationUser());
|
||||
Set<RoleModel> 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<RoleModel> visited = new HashSet<RoleModel>();
|
||||
applyScope(role, desiredRole, visited, requestedRoles);
|
||||
}
|
||||
}
|
||||
|
||||
return requestedRoles;
|
||||
}
|
||||
|
||||
private static void applyScope(RoleModel role, RoleModel scope, Set<RoleModel> visited, Set<RoleModel> 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(String appName, String roleName, Set<RoleModel> requestedRoles) {
|
||||
RoleModel expectedRole = getRole(appName, roleName);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue