Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
fc92ead9be
124 changed files with 6340 additions and 355 deletions
|
@ -118,7 +118,14 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
<version>1.3.161</version>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.flapdoodle.embed</groupId>
|
||||||
|
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
<async-supported>true</async-supported>
|
<async-supported>true</async-supported>
|
||||||
</servlet>
|
</servlet>
|
||||||
|
|
||||||
|
<listener>
|
||||||
|
<listener-class>org.keycloak.services.listeners.MongoRunnerListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<filter-name>Keycloak Session Management</filter-name>
|
<filter-name>Keycloak Session Management</filter-name>
|
||||||
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
|
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
|
||||||
|
|
|
@ -46,12 +46,6 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<version>1.0.2.Final</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.faces</groupId>
|
|
||||||
<artifactId>jsf-api</artifactId>
|
|
||||||
<version>2.1</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.zxing</groupId>
|
<groupId>com.google.zxing</groupId>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakSessionUtils {
|
||||||
|
|
||||||
|
private static AtomicLong counter = new AtomicLong(1);
|
||||||
|
|
||||||
|
public static String generateId() {
|
||||||
|
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
77
model/mongo/pom.xml
Normal file
77
model/mongo/pom.xml
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?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-1</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-model-mongo</artifactId>
|
||||||
|
<name>Keycloak Model Mongo</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<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>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<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>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,37 @@
|
||||||
|
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,12 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
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();
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.models.mongo.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||||
|
import org.picketlink.common.properties.Property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.keycloak.models.mongo.api;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@Target({TYPE})
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
public @interface NoSQLCollection {
|
||||||
|
|
||||||
|
String collectionName();
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
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 NoSQLField {
|
||||||
|
|
||||||
|
// TODO: fieldName add lazy loading?
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
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 {
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
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,26 @@
|
||||||
|
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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
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,114 @@
|
||||||
|
package org.keycloak.models.mongo.api.types;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.picketlink.common.reflection.Reflections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of converters, which allow to convert application object to database objects. TypeConverter is main entry point to be used by application.
|
||||||
|
* Application can create instance of TypeConverter and then register required Converter objects.
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
package org.keycloak.models.mongo.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBList;
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.mongodb.DB;
|
||||||
|
import com.mongodb.DBCollection;
|
||||||
|
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.EnumToStringConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.ListConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.BasicDBListConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.BasicDBObjectConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.NoSQLObjectConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.SimpleConverter;
|
||||||
|
import org.keycloak.models.mongo.impl.types.StringToEnumConverter;
|
||||||
|
import org.picketlink.common.properties.Property;
|
||||||
|
import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
|
||||||
|
import org.picketlink.common.properties.query.PropertyQueries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.keycloak.models.mongo.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.keycloak.models.mongo.impl;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||||
|
import org.picketlink.common.properties.Property;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ObjectInfo {
|
||||||
|
|
||||||
|
private final Class<? extends NoSQLObject> 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) {
|
||||||
|
this.objectClass = objectClass;
|
||||||
|
this.dbCollectionName = dbCollectionName;
|
||||||
|
this.oidProperty = oidProperty;
|
||||||
|
|
||||||
|
Map<String, Property<Object>> props= new HashMap<String, Property<Object>>();
|
||||||
|
for (Property<Object> property : properties) {
|
||||||
|
props.put(property.getName(), property);
|
||||||
|
}
|
||||||
|
this.properties = Collections.unmodifiableMap(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends NoSQLObject> getObjectClass() {
|
||||||
|
return objectClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDbCollectionName() {
|
||||||
|
return dbCollectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property<String> getOidProperty() {
|
||||||
|
return oidProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Property<Object>> getProperties() {
|
||||||
|
return properties.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property<Object> getPropertyByName(String propertyName) {
|
||||||
|
return properties.get(propertyName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.keycloak.models.mongo.impl.types;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBList;
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import org.keycloak.models.mongo.api.types.Converter;
|
||||||
|
import org.keycloak.models.mongo.api.types.TypeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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,102 @@
|
||||||
|
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,37 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
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,52 @@
|
||||||
|
package org.keycloak.models.mongo.impl.types;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.mongodb.BasicDBList;
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import org.keycloak.models.mongo.api.types.Converter;
|
||||||
|
import org.keycloak.models.mongo.api.types.TypeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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,66 @@
|
||||||
|
package org.keycloak.models.mongo.impl.types;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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 SimpleConverter<T> implements Converter<T, T> {
|
||||||
|
|
||||||
|
private final Class<T> expectedType;
|
||||||
|
|
||||||
|
public SimpleConverter(Class<T> expectedType) {
|
||||||
|
this.expectedType = expectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T convertObject(T objectToConvert) {
|
||||||
|
return objectToConvert;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends T> getConverterObjectType() {
|
||||||
|
return expectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getExpectedReturnType() {
|
||||||
|
return expectedType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
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,284 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.ApplicationData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RoleData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ApplicationAdapter implements ApplicationModel {
|
||||||
|
|
||||||
|
private final ApplicationData application;
|
||||||
|
private final NoSQL noSQL;
|
||||||
|
|
||||||
|
private UserData resourceUser;
|
||||||
|
|
||||||
|
public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) {
|
||||||
|
this.application = applicationData;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateApplication() {
|
||||||
|
noSQL.saveObject(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getApplicationUser() {
|
||||||
|
// This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object
|
||||||
|
if (resourceUser == null) {
|
||||||
|
resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return application.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return application.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
application.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return application.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
application.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSurrogateAuthRequired() {
|
||||||
|
return application.isSurrogateAuthRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
|
||||||
|
application.setSurrogateAuthRequired(surrogateAuthRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getManagementUrl() {
|
||||||
|
return application.getManagementUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setManagementUrl(String url) {
|
||||||
|
application.setManagementUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaseUrl(String url) {
|
||||||
|
application.setBaseUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return application.getBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleAdapter getRole(String name) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("name", name)
|
||||||
|
.andCondition("applicationId", getId())
|
||||||
|
.build();
|
||||||
|
RoleData role = noSQL.loadSingleObject(RoleData.class, query);
|
||||||
|
if (role == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new RoleAdapter(role, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel getRoleById(String id) {
|
||||||
|
RoleData role = noSQL.loadObject(RoleData.class, id);
|
||||||
|
if (role == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new RoleAdapter(role, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void grantRole(UserModel user, RoleModel role) {
|
||||||
|
UserData userData = ((UserAdapter)user).getUser();
|
||||||
|
noSQL.pushItemToList(userData, "roleIds", role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
RoleData roleData = new RoleData();
|
||||||
|
roleData.setName(name);
|
||||||
|
roleData.setApplicationId(getId());
|
||||||
|
|
||||||
|
noSQL.saveObject(roleData);
|
||||||
|
return new RoleAdapter(roleData, noSQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleModel> getRoles() {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("applicationId", getId())
|
||||||
|
.build();
|
||||||
|
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
|
||||||
|
|
||||||
|
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
result.add(new RoleAdapter(role, noSQL));
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (getId().equals(role.getApplicationId())) {
|
||||||
|
result.add(role.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
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.keycloak.data.ApplicationData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RealmData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RoleData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||||
|
import org.keycloak.models.mongo.impl.MongoDBImpl;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.credentials.OTPData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NoSQL implementation based on MongoDB
|
||||||
|
*
|
||||||
|
* @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,86 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RealmData;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.utils.KeycloakSessionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class OAuthClientAdapter implements OAuthClientModel {
|
||||||
|
|
||||||
|
private final OAuthClientData delegate;
|
||||||
|
private UserAdapter oauthAgent;
|
||||||
|
private final NoSQL noSQL;
|
||||||
|
|
||||||
|
public OAuthClientAdapter(OAuthClientData oauthClientData, UserAdapter oauthAgent, NoSQL noSQL) {
|
||||||
|
this.delegate = oauthClientData;
|
||||||
|
this.oauthAgent = oauthAgent;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OAuthClientAdapter(OAuthClientData oauthClientData, NoSQL noSQL) {
|
||||||
|
this.delegate = oauthClientData;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getOAuthAgent() {
|
||||||
|
// This is not thread-safe. Assumption is that OAuthClientAdapter instance is per-client object
|
||||||
|
if (oauthAgent == null) {
|
||||||
|
UserData user = noSQL.loadObject(UserData.class, delegate.getOauthAgentId());
|
||||||
|
oauthAgent = user!=null ? new UserAdapter(user, noSQL) : null;
|
||||||
|
}
|
||||||
|
return oauthAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return delegate.getBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaseUrl(String base) {
|
||||||
|
delegate.setBaseUrl(base);
|
||||||
|
noSQL.saveObject(delegate);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,862 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
|
import org.keycloak.PemUtils;
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.OAuthClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.SocialLinkModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQueryBuilder;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.OAuthClientData;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.keycloak.credentials.PasswordCredentialHandler;
|
||||||
|
import org.keycloak.models.mongo.keycloak.credentials.TOTPCredentialHandler;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.ApplicationData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RealmData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RequiredCredentialData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RoleData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.SocialLinkData;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||||
|
import org.picketlink.idm.credential.Credentials;
|
||||||
|
import org.picketlink.idm.model.sample.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
|
private final RealmData realm;
|
||||||
|
private final NoSQL noSQL;
|
||||||
|
|
||||||
|
protected volatile transient PublicKey publicKey;
|
||||||
|
protected volatile transient PrivateKey privateKey;
|
||||||
|
|
||||||
|
// TODO: likely shouldn't be static. And ATM, just empty map is passed -> It's not possible to configure stuff like PasswordEncoder etc.
|
||||||
|
private static PasswordCredentialHandler passwordCredentialHandler = new PasswordCredentialHandler(new HashMap<String, Object>());
|
||||||
|
private static TOTPCredentialHandler totpCredentialHandler = new TOTPCredentialHandler(new HashMap<String, Object>());
|
||||||
|
|
||||||
|
public RealmAdapter(RealmData realmData, NoSQL noSQL) {
|
||||||
|
this.realm = realmData;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getOid() {
|
||||||
|
return realm.getOid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return realm.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return realm.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
realm.setName(name);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return realm.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
realm.setEnabled(enabled);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSocial() {
|
||||||
|
return realm.isSocial();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSocial(boolean social) {
|
||||||
|
realm.setSocial(social);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||||
|
return realm.isAutomaticRegistrationAfterSocialLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||||
|
realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSslNotRequired() {
|
||||||
|
return realm.isSslNotRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSslNotRequired(boolean sslNotRequired) {
|
||||||
|
realm.setSslNotRequired(sslNotRequired);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCookieLoginAllowed() {
|
||||||
|
return realm.isCookieLoginAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCookieLoginAllowed(boolean cookieLoginAllowed) {
|
||||||
|
realm.setCookieLoginAllowed(cookieLoginAllowed);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRegistrationAllowed() {
|
||||||
|
return realm.isRegistrationAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegistrationAllowed(boolean registrationAllowed) {
|
||||||
|
realm.setRegistrationAllowed(registrationAllowed);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVerifyEmail() {
|
||||||
|
return realm.isVerifyEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVerifyEmail(boolean verifyEmail) {
|
||||||
|
realm.setVerifyEmail(verifyEmail);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isResetPasswordAllowed() {
|
||||||
|
return realm.isResetPasswordAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setResetPasswordAllowed(boolean resetPassword) {
|
||||||
|
realm.setResetPasswordAllowed(resetPassword);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTokenLifespan() {
|
||||||
|
return realm.getTokenLifespan();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTokenLifespan(int tokenLifespan) {
|
||||||
|
realm.setTokenLifespan(tokenLifespan);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAccessCodeLifespan() {
|
||||||
|
return realm.getAccessCodeLifespan();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAccessCodeLifespan(int accessCodeLifespan) {
|
||||||
|
realm.setAccessCodeLifespan(accessCodeLifespan);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAccessCodeLifespanUserAction() {
|
||||||
|
return realm.getAccessCodeLifespanUserAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
|
||||||
|
realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPublicKeyPem() {
|
||||||
|
return realm.getPublicKeyPem();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublicKeyPem(String publicKeyPem) {
|
||||||
|
realm.setPublicKeyPem(publicKeyPem);
|
||||||
|
this.publicKey = null;
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrivateKeyPem() {
|
||||||
|
return realm.getPrivateKeyPem();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrivateKeyPem(String privateKeyPem) {
|
||||||
|
realm.setPrivateKeyPem(privateKeyPem);
|
||||||
|
this.privateKey = null;
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PublicKey getPublicKey() {
|
||||||
|
if (publicKey != null) return publicKey;
|
||||||
|
String pem = getPublicKeyPem();
|
||||||
|
if (pem != null) {
|
||||||
|
try {
|
||||||
|
publicKey = PemUtils.decodePublicKey(pem);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublicKey(PublicKey publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
PEMWriter pemWriter = new PEMWriter(writer);
|
||||||
|
try {
|
||||||
|
pemWriter.writeObject(publicKey);
|
||||||
|
pemWriter.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
String s = writer.toString();
|
||||||
|
setPublicKeyPem(PemUtils.removeBeginEnd(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey() {
|
||||||
|
if (privateKey != null) return privateKey;
|
||||||
|
String pem = getPrivateKeyPem();
|
||||||
|
if (pem != null) {
|
||||||
|
try {
|
||||||
|
privateKey = PemUtils.decodePrivateKey(pem);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPrivateKey(PrivateKey privateKey) {
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
PEMWriter pemWriter = new PEMWriter(writer);
|
||||||
|
try {
|
||||||
|
pemWriter.writeObject(privateKey);
|
||||||
|
pemWriter.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
String s = writer.toString();
|
||||||
|
setPrivateKeyPem(PemUtils.removeBeginEnd(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserAdapter getUser(String name) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("loginName", name)
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
UserData user = noSQL.loadSingleObject(UserData.class, query);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new UserAdapter(user, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserAdapter addUser(String username) {
|
||||||
|
if (getUser(username) != null) {
|
||||||
|
throw new IllegalArgumentException("User " + username + " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserData userData = new UserData();
|
||||||
|
userData.setLoginName(username);
|
||||||
|
userData.setEnabled(true);
|
||||||
|
userData.setRealmId(getOid());
|
||||||
|
|
||||||
|
noSQL.saveObject(userData);
|
||||||
|
return new UserAdapter(userData, noSQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method doesn't exists on interface actually
|
||||||
|
public void removeUser(String name) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("loginName", name)
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
noSQL.removeObjects(UserData.class, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleAdapter getRole(String name) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("name", name)
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
RoleData role = noSQL.loadSingleObject(RoleData.class, query);
|
||||||
|
if (role == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new RoleAdapter(role, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel addRole(String name) {
|
||||||
|
if (getRole(name) != null) {
|
||||||
|
throw new IllegalArgumentException("Role " + name + " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
RoleData roleData = new RoleData();
|
||||||
|
roleData.setName(name);
|
||||||
|
roleData.setRealmId(getOid());
|
||||||
|
|
||||||
|
noSQL.saveObject(roleData);
|
||||||
|
return new RoleAdapter(roleData, noSQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleModel> getRoles() {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
|
||||||
|
|
||||||
|
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
result.add(new RoleAdapter(role, noSQL));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleModel> getDefaultRoles() {
|
||||||
|
List<String> defaultRoles = realm.getDefaultRoles();
|
||||||
|
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.inCondition("_id", defaultRoles)
|
||||||
|
.build();
|
||||||
|
List<RoleData> defaultRolesData = noSQL.loadObjects(RoleData.class, query);
|
||||||
|
|
||||||
|
List<RoleModel> defaultRoleModels = new ArrayList<RoleModel>();
|
||||||
|
for (RoleData roleData : defaultRolesData) {
|
||||||
|
defaultRoleModels.add(new RoleAdapter(roleData, noSQL));
|
||||||
|
}
|
||||||
|
return defaultRoleModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDefaultRole(String name) {
|
||||||
|
RoleModel role = getRole(name);
|
||||||
|
if (role == null) {
|
||||||
|
role = addRole(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
noSQL.pushItemToList(realm, "defaultRoles", role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDefaultRoles(String[] defaultRoles) {
|
||||||
|
// defaultRoles is array with names of roles. So we need to convert to array of ids
|
||||||
|
List<String> roleIds = new ArrayList<String>();
|
||||||
|
for (String roleName : defaultRoles) {
|
||||||
|
RoleModel role = getRole(roleName);
|
||||||
|
if (role == null) {
|
||||||
|
role = addRole(roleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
roleIds.add(role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
realm.setDefaultRoles(roleIds);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApplicationModel getApplicationById(String id) {
|
||||||
|
ApplicationData appData = noSQL.loadObject(ApplicationData.class, id);
|
||||||
|
|
||||||
|
// Check if application belongs to this realm
|
||||||
|
if (appData == null || !getOid().equals(appData.getRealmId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationModel model = new ApplicationAdapter(appData, noSQL);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ApplicationModel> getApplicationNameMap() {
|
||||||
|
Map<String, ApplicationModel> resourceMap = new HashMap<String, ApplicationModel>();
|
||||||
|
for (ApplicationModel resource : getApplications()) {
|
||||||
|
resourceMap.put(resource.getName(), resource);
|
||||||
|
}
|
||||||
|
return resourceMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ApplicationModel> getApplications() {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
List<ApplicationData> appDatas = noSQL.loadObjects(ApplicationData.class, query);
|
||||||
|
|
||||||
|
List<ApplicationModel> result = new ArrayList<ApplicationModel>();
|
||||||
|
for (ApplicationData appData : appDatas) {
|
||||||
|
result.add(new ApplicationAdapter(appData, noSQL));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApplicationModel addApplication(String name) {
|
||||||
|
UserAdapter resourceUser = addUser(name);
|
||||||
|
|
||||||
|
ApplicationData appData = new ApplicationData();
|
||||||
|
appData.setName(name);
|
||||||
|
appData.setRealmId(getOid());
|
||||||
|
appData.setResourceUserId(resourceUser.getUser().getId());
|
||||||
|
noSQL.saveObject(appData);
|
||||||
|
|
||||||
|
ApplicationModel resource = new ApplicationAdapter(appData, noSQL);
|
||||||
|
resource.addRole("*");
|
||||||
|
resource.addScopeMapping(resourceUser, "*");
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 void grantRole(UserModel user, RoleModel role) {
|
||||||
|
UserData userData = ((UserAdapter)user).getUser();
|
||||||
|
noSQL.pushItemToList(userData, "roleIds", role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleModel> getRoleMappings(UserModel user) {
|
||||||
|
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||||
|
List<RoleData> roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL);
|
||||||
|
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
if (getOid().equals(role.getRealmId())) {
|
||||||
|
result.add(new RoleAdapter(role, noSQL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRoleMappingValues(UserModel user) {
|
||||||
|
Set<String> result = new HashSet<String>();
|
||||||
|
List<RoleData> roles = ApplicationAdapter.getAllRolesOfUser(user, noSQL);
|
||||||
|
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
if (getOid().equals(role.getRealmId())) {
|
||||||
|
result.add(role.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthClientModel addOAuthClient(String name) {
|
||||||
|
UserAdapter oauthAgent = addUser(name);
|
||||||
|
|
||||||
|
OAuthClientData oauthClient = new OAuthClientData();
|
||||||
|
oauthClient.setOauthAgentId(oauthAgent.getUser().getId());
|
||||||
|
oauthClient.setRealmId(getOid());
|
||||||
|
noSQL.saveObject(oauthClient);
|
||||||
|
|
||||||
|
return new OAuthClientAdapter(oauthClient, oauthAgent, noSQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuthClientModel getOAuthClient(String name) {
|
||||||
|
UserAdapter user = getUser(name);
|
||||||
|
if (user == null) return null;
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.andCondition("oauthAgentId", user.getUser().getId())
|
||||||
|
.build();
|
||||||
|
OAuthClientData oauthClient = noSQL.loadSingleObject(OAuthClientData.class, query);
|
||||||
|
return oauthClient == null ? null : new OAuthClientAdapter(oauthClient, user, noSQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<OAuthClientModel> getOAuthClients() {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
List<OAuthClientData> results = noSQL.loadObjects(OAuthClientData.class, query);
|
||||||
|
List<OAuthClientModel> list = new ArrayList<OAuthClientModel>();
|
||||||
|
for (OAuthClientData data : results) {
|
||||||
|
list.add(new OAuthClientAdapter(data, noSQL));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleModel> getScopeMappings(UserModel agent) {
|
||||||
|
List<RoleModel> result = new ArrayList<RoleModel>();
|
||||||
|
List<RoleData> roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL);
|
||||||
|
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
if (getOid().equals(role.getRealmId())) {
|
||||||
|
result.add(new RoleAdapter(role, noSQL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getScopeMappingValues(UserModel agent) {
|
||||||
|
Set<String> result = new HashSet<String>();
|
||||||
|
List<RoleData> roles = ApplicationAdapter.getAllScopesOfUser(agent, noSQL);
|
||||||
|
// TODO: Maybe improve as currently we need to obtain all roles and then filter programmatically...
|
||||||
|
for (RoleData role : roles) {
|
||||||
|
if (getOid().equals(role.getRealmId())) {
|
||||||
|
result.add(role.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRealmAdmin(UserModel agent) {
|
||||||
|
List<String> realmAdmins = realm.getRealmAdmins();
|
||||||
|
String userId = ((UserAdapter)agent).getUser().getId();
|
||||||
|
return realmAdmins.contains(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRealmAdmin(UserModel agent) {
|
||||||
|
UserData userData = ((UserAdapter)agent).getUser();
|
||||||
|
|
||||||
|
noSQL.pushItemToList(realm, "realmAdmins", userData.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel getRoleById(String id) {
|
||||||
|
RoleData role = noSQL.loadObject(RoleData.class, id);
|
||||||
|
if (role == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return new RoleAdapter(role, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasRole(UserModel user, String role) {
|
||||||
|
RoleModel roleModel = getRole(role);
|
||||||
|
return hasRole(user, roleModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredCredential(String cred) {
|
||||||
|
RequiredCredentialModel credentialModel = initRequiredCredentialModel(cred);
|
||||||
|
addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredResourceCredential(String type) {
|
||||||
|
RequiredCredentialModel credentialModel = initRequiredCredentialModel(type);
|
||||||
|
addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredOAuthClientCredential(String type) {
|
||||||
|
RequiredCredentialModel credentialModel = initRequiredCredentialModel(type);
|
||||||
|
addRequiredCredential(credentialModel, RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRequiredCredential(RequiredCredentialModel credentialModel, int clientType) {
|
||||||
|
RequiredCredentialData credData = new RequiredCredentialData();
|
||||||
|
credData.setType(credentialModel.getType());
|
||||||
|
credData.setFormLabel(credentialModel.getFormLabel());
|
||||||
|
credData.setInput(credentialModel.isInput());
|
||||||
|
credData.setSecret(credentialModel.isSecret());
|
||||||
|
|
||||||
|
credData.setRealmId(getOid());
|
||||||
|
credData.setClientType(clientType);
|
||||||
|
|
||||||
|
noSQL.saveObject(credData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateRequiredCredentials(Set<String> creds) {
|
||||||
|
List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_USER);
|
||||||
|
updateRequiredCredentials(creds, credsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateRequiredApplicationCredentials(Set<String> creds) {
|
||||||
|
List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_RESOURCE);
|
||||||
|
updateRequiredCredentials(creds, credsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateRequiredOAuthClientCredentials(Set<String> creds) {
|
||||||
|
List<RequiredCredentialData> credsData = getRequiredCredentialsData(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
|
||||||
|
updateRequiredCredentials(creds, credsData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateRequiredCredentials(Set<String> creds, List<RequiredCredentialData> credsData) {
|
||||||
|
Set<String> already = new HashSet<String>();
|
||||||
|
for (RequiredCredentialData data : credsData) {
|
||||||
|
if (!creds.contains(data.getType())) {
|
||||||
|
noSQL.removeObject(data);
|
||||||
|
} else {
|
||||||
|
already.add(data.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String cred : creds) {
|
||||||
|
// TODO
|
||||||
|
System.out.println("updating cred: " + cred);
|
||||||
|
// logger.info("updating cred: " + cred);
|
||||||
|
if (!already.contains(cred)) {
|
||||||
|
addRequiredCredential(cred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RequiredCredentialModel> getRequiredCredentials() {
|
||||||
|
return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RequiredCredentialModel> getRequiredApplicationCredentials() {
|
||||||
|
return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RequiredCredentialModel> getRequiredOAuthClientCredentials() {
|
||||||
|
return getRequiredCredentials(RequiredCredentialData.CLIENT_TYPE_OAUTH_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<RequiredCredentialModel> getRequiredCredentials(int credentialType) {
|
||||||
|
List<RequiredCredentialData> credsData = getRequiredCredentialsData(credentialType);
|
||||||
|
|
||||||
|
List<RequiredCredentialModel> result = new ArrayList<RequiredCredentialModel>();
|
||||||
|
for (RequiredCredentialData data : credsData) {
|
||||||
|
RequiredCredentialModel model = new RequiredCredentialModel();
|
||||||
|
model.setFormLabel(data.getFormLabel());
|
||||||
|
model.setInput(data.isInput());
|
||||||
|
model.setSecret(data.isSecret());
|
||||||
|
model.setType(data.getType());
|
||||||
|
|
||||||
|
result.add(model);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<RequiredCredentialData> getRequiredCredentialsData(int credentialType) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.andCondition("clientType", credentialType)
|
||||||
|
.build();
|
||||||
|
return noSQL.loadObjects(RequiredCredentialData.class, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validatePassword(UserModel user, String password) {
|
||||||
|
Credentials.Status status = passwordCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password);
|
||||||
|
return status == Credentials.Status.VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validateTOTP(UserModel user, String password, String token) {
|
||||||
|
Credentials.Status status = totpCredentialHandler.validate(noSQL, ((UserAdapter)user).getUser(), password, token, null);
|
||||||
|
return status == Credentials.Status.VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredential(UserModel user, UserCredentialModel cred) {
|
||||||
|
if (cred.getType().equals(CredentialRepresentation.PASSWORD)) {
|
||||||
|
passwordCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), null, null);
|
||||||
|
} else if (cred.getType().equals(CredentialRepresentation.TOTP)) {
|
||||||
|
totpCredentialHandler.update(noSQL, ((UserAdapter)user).getUser(), cred.getValue(), cred.getDevice(), null, null);
|
||||||
|
} else if (cred.getType().equals(CredentialRepresentation.CLIENT_CERT)) {
|
||||||
|
// TODO
|
||||||
|
// X509Certificate cert = null;
|
||||||
|
// try {
|
||||||
|
// cert = org.keycloak.PemUtils.decodeCertificate(cred.getValue());
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// throw new RuntimeException(e);
|
||||||
|
// }
|
||||||
|
// X509CertificateCredentials creds = new X509CertificateCredentials(cert);
|
||||||
|
// idm.updateCredential(((UserAdapter)user).getUser(), creds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("socialProvider", socialLink.getSocialProvider())
|
||||||
|
.andCondition("socialUsername", socialLink.getSocialUsername())
|
||||||
|
.andCondition("realmId", getOid())
|
||||||
|
.build();
|
||||||
|
SocialLinkData socialLinkData = noSQL.loadSingleObject(SocialLinkData.class, query);
|
||||||
|
|
||||||
|
if (socialLinkData == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
UserData userData = noSQL.loadObject(UserData.class, socialLinkData.getUserId());
|
||||||
|
// TODO: Add some checking if userData exists and programmatically remove binding if it doesn't? (There are more similar places where this should be handled)
|
||||||
|
return new UserAdapter(userData, noSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SocialLinkModel> getSocialLinks(UserModel user) {
|
||||||
|
UserData userData = ((UserAdapter)user).getUser();
|
||||||
|
String userId = userData.getId();
|
||||||
|
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("userId", userId)
|
||||||
|
.build();
|
||||||
|
List<SocialLinkData> dbSocialLinks = noSQL.loadObjects(SocialLinkData.class, query);
|
||||||
|
|
||||||
|
Set<SocialLinkModel> result = new HashSet<SocialLinkModel>();
|
||||||
|
for (SocialLinkData socialLinkData : dbSocialLinks) {
|
||||||
|
SocialLinkModel model = new SocialLinkModel(socialLinkData.getSocialProvider(), socialLinkData.getSocialUsername());
|
||||||
|
result.add(model);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
|
||||||
|
UserData userData = ((UserAdapter)user).getUser();
|
||||||
|
SocialLinkData socialLinkData = new SocialLinkData();
|
||||||
|
socialLinkData.setSocialProvider(socialLink.getSocialProvider());
|
||||||
|
socialLinkData.setSocialUsername(socialLink.getSocialUsername());
|
||||||
|
socialLinkData.setUserId(userData.getId());
|
||||||
|
socialLinkData.setRealmId(getOid());
|
||||||
|
|
||||||
|
noSQL.saveObject(socialLinkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeSocialLink(UserModel user, SocialLinkModel socialLink) {
|
||||||
|
UserData userData = ((UserAdapter)user).getUser();
|
||||||
|
String userId = userData.getId();
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("socialProvider", socialLink.getSocialProvider())
|
||||||
|
.andCondition("socialUsername", socialLink.getSocialUsername())
|
||||||
|
.andCondition("userId", userId)
|
||||||
|
.build();
|
||||||
|
noSQL.removeObjects(SocialLinkData.class, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateRealm() {
|
||||||
|
noSQL.saveObject(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
|
||||||
|
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
|
||||||
|
if (model == null) {
|
||||||
|
throw new RuntimeException("Unknown credential type " + type);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes) {
|
||||||
|
NoSQLQueryBuilder queryBuilder = noSQL.createQueryBuilder();
|
||||||
|
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||||
|
if (entry.getKey().equals(UserModel.LOGIN_NAME)) {
|
||||||
|
queryBuilder.andCondition("loginName", entry.getValue());
|
||||||
|
} else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
|
||||||
|
queryBuilder.andCondition(UserModel.FIRST_NAME, entry.getValue());
|
||||||
|
|
||||||
|
} else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) {
|
||||||
|
queryBuilder.andCondition(UserModel.LAST_NAME, entry.getValue());
|
||||||
|
|
||||||
|
} else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) {
|
||||||
|
queryBuilder.andCondition(UserModel.EMAIL, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<UserData> users = noSQL.loadObjects(UserData.class, queryBuilder.build());
|
||||||
|
List<UserModel> userModels = new ArrayList<UserModel>();
|
||||||
|
for (UserData user : users) {
|
||||||
|
userModels.add(new UserAdapter(user, noSQL));
|
||||||
|
}
|
||||||
|
return userModels;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.RoleData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around RoleData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl)
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class RoleAdapter implements RoleModel {
|
||||||
|
|
||||||
|
private final RoleData role;
|
||||||
|
private final NoSQL noSQL;
|
||||||
|
|
||||||
|
public RoleAdapter(RoleData roleData, NoSQL noSQL) {
|
||||||
|
this.role = roleData;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return role.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return role.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDescription(String description) {
|
||||||
|
role.setDescription(description);
|
||||||
|
noSQL.saveObject(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return role.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setName(String name) {
|
||||||
|
role.setName(name);
|
||||||
|
noSQL.saveObject(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleData getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.UserData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around UserData object, which will persist wrapped object after each set operation (compatibility with picketlink based impl)
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class UserAdapter implements UserModel {
|
||||||
|
|
||||||
|
private final UserData user;
|
||||||
|
private final NoSQL noSQL;
|
||||||
|
|
||||||
|
public UserAdapter(UserData userData, NoSQL noSQL) {
|
||||||
|
this.user = userData;
|
||||||
|
this.noSQL = noSQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLoginName() {
|
||||||
|
return user.getLoginName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return user.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
user.setEnabled(enabled);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstName() {
|
||||||
|
return user.getFirstName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
user.setFirstName(firstName);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLastName() {
|
||||||
|
return user.getLastName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
user.setLastName(lastName);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEmail() {
|
||||||
|
return user.getEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmail(String email) {
|
||||||
|
user.setEmail(email);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmailVerified() {
|
||||||
|
return user.isEmailVerified();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmailVerified(boolean verified) {
|
||||||
|
user.setEmailVerified(verified);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, String value) {
|
||||||
|
user.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
user.removeAttribute(name);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAttribute(String name) {
|
||||||
|
return user.getAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return user.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserData getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RequiredAction> getRequiredActions() {
|
||||||
|
List<RequiredAction> actions = user.getRequiredActions();
|
||||||
|
|
||||||
|
// Compatibility with picketlink impl
|
||||||
|
if (actions == null) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
} else {
|
||||||
|
Set<RequiredAction> s = new HashSet<RequiredAction>();
|
||||||
|
for (RequiredAction a : actions) {
|
||||||
|
s.add(a);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRequiredAction(RequiredAction action) {
|
||||||
|
noSQL.pullItemFromList(user, "requiredActions", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTotp() {
|
||||||
|
return user.isTotp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTotp(boolean totp) {
|
||||||
|
user.setTotp(totp);
|
||||||
|
noSQL.saveObject(user);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.credentials;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.credentials;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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 static org.picketlink.common.util.StringUtil.isNullOrEmpty;
|
||||||
|
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_ALGORITHM;
|
||||||
|
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_DELAY_WINDOW;
|
||||||
|
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS;
|
||||||
|
import static org.picketlink.idm.credential.util.TimeBasedOTP.DEFAULT_NUMBER_DIGITS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler}
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@NoSQLCollection(collectionName = "applications")
|
||||||
|
public class ApplicationData implements NoSQLObject {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean surrogateAuthRequired;
|
||||||
|
private String managementUrl;
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
private String resourceUserId;
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@NoSQLId
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public boolean isSurrogateAuthRequired() {
|
||||||
|
return surrogateAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
|
||||||
|
this.surrogateAuthRequired = surrogateAuthRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getManagementUrl() {
|
||||||
|
return managementUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManagementUrl(String managementUrl) {
|
||||||
|
this.managementUrl = managementUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getResourceUserId() {
|
||||||
|
return resourceUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceUserId(String resourceUserId) {
|
||||||
|
this.resourceUserId = resourceUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterRemove(NoSQL noSQL) {
|
||||||
|
// Remove resourceUser of this application
|
||||||
|
noSQL.removeObject(UserData.class, resourceUserId);
|
||||||
|
|
||||||
|
// Remove all roles, which belongs to this application
|
||||||
|
NoSQLQuery query = noSQL.createQueryBuilder()
|
||||||
|
.andCondition("applicationId", id)
|
||||||
|
.build();
|
||||||
|
noSQL.removeObjects(RoleData.class, query);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLField;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLId;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLField;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLId;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLField;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLId;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.mongo.api.AbstractAttributedNoSQLObject;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLCollection;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLField;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLId;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.keycloak.data.credentials.PasswordData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@NoSQLCollection(collectionName = "users")
|
||||||
|
public class UserData extends AbstractAttributedNoSQLObject {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(UserData.class);
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String loginName;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String email;
|
||||||
|
private boolean emailVerified;
|
||||||
|
private boolean totp;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
private List<String> roleIds;
|
||||||
|
private List<String> scopeIds;
|
||||||
|
private List<UserModel.RequiredAction> requiredActions;
|
||||||
|
|
||||||
|
@NoSQLId
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getLoginName() {
|
||||||
|
return loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginName(String loginName) {
|
||||||
|
this.loginName = loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public boolean isEmailVerified() {
|
||||||
|
return emailVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailVerified(boolean emailVerified) {
|
||||||
|
this.emailVerified = emailVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public boolean isTotp() {
|
||||||
|
return totp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotp(boolean totp) {
|
||||||
|
this.totp = totp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getRealmId() {
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealmId(String realmId) {
|
||||||
|
this.realmId = realmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<String> getRoleIds() {
|
||||||
|
return roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleIds(List<String> roleIds) {
|
||||||
|
this.roleIds = roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<String> getScopeIds() {
|
||||||
|
return scopeIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeIds(List<String> scopeIds) {
|
||||||
|
this.scopeIds = scopeIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<UserModel.RequiredAction> getRequiredActions() {
|
||||||
|
return requiredActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data.credentials;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
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 = "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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.data.credentials;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
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 = "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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.keycloak.models.mongo.test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.AbstractNoSQLObject;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class Address extends AbstractNoSQLObject {
|
||||||
|
|
||||||
|
private String street;
|
||||||
|
private int number;
|
||||||
|
private List<String> flatNumbers;
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getStreet() {
|
||||||
|
return street;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStreet(String street) {
|
||||||
|
this.street = street;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public int getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumber(int number) {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<String> getFlatNumbers() {
|
||||||
|
return flatNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlatNumbers(List<String> flatNumbers) {
|
||||||
|
this.flatNumbers = flatNumbers;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.keycloak.models.mongo.test;
|
||||||
|
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.mongodb.DB;
|
||||||
|
import com.mongodb.MongoClient;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQL;
|
||||||
|
import org.keycloak.models.mongo.api.NoSQLObject;
|
||||||
|
import org.keycloak.models.mongo.api.query.NoSQLQuery;
|
||||||
|
import org.keycloak.models.mongo.impl.MongoDBImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class MongoDBModelTest {
|
||||||
|
|
||||||
|
private static final Class<? extends NoSQLObject>[] MANAGED_DATA_TYPES = (Class<? extends NoSQLObject>[])new Class<?>[] {
|
||||||
|
Person.class,
|
||||||
|
Address.class,
|
||||||
|
};
|
||||||
|
|
||||||
|
private MongoClient mongoClient;
|
||||||
|
private NoSQL mongoDB;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
try {
|
||||||
|
// TODO: authentication support
|
||||||
|
mongoClient = new MongoClient("localhost", 27017);
|
||||||
|
|
||||||
|
DB db = mongoClient.getDB("keycloakTest");
|
||||||
|
mongoDB = new MongoDBImpl(db, true, MANAGED_DATA_TYPES);
|
||||||
|
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
mongoClient.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
public void mongoModelTest() throws Exception {
|
||||||
|
// Add some user
|
||||||
|
Person john = new Person();
|
||||||
|
john.setFirstName("john");
|
||||||
|
john.setAge(25);
|
||||||
|
john.setGender(Person.Gender.MALE);
|
||||||
|
|
||||||
|
mongoDB.saveObject(john);
|
||||||
|
|
||||||
|
// Add another user
|
||||||
|
Person mary = new Person();
|
||||||
|
mary.setFirstName("mary");
|
||||||
|
mary.setKids(Arrays.asList(new String[] {"Peter", "Paul", "Wendy"}));
|
||||||
|
|
||||||
|
Address addr1 = new Address();
|
||||||
|
addr1.setStreet("Elm");
|
||||||
|
addr1.setNumber(5);
|
||||||
|
addr1.setFlatNumbers(Arrays.asList(new String[] {"flat1", "flat2"}));
|
||||||
|
Address addr2 = new Address();
|
||||||
|
List<Address> addresses = new ArrayList<Address>();
|
||||||
|
addresses.add(addr1);
|
||||||
|
addresses.add(addr2);
|
||||||
|
|
||||||
|
mary.setAddresses(addresses);
|
||||||
|
mary.setMainAddress(addr1);
|
||||||
|
mary.setGender(Person.Gender.FEMALE);
|
||||||
|
mary.setGenders(Arrays.asList(new Person.Gender[] {Person.Gender.FEMALE}));
|
||||||
|
mongoDB.saveObject(mary);
|
||||||
|
|
||||||
|
Assert.assertEquals(2, mongoDB.loadObjects(Person.class, mongoDB.createQueryBuilder().build()).size());
|
||||||
|
|
||||||
|
NoSQLQuery query = mongoDB.createQueryBuilder().andCondition("addresses.flatNumbers", "flat1").build();
|
||||||
|
List<Person> persons = mongoDB.loadObjects(Person.class, query);
|
||||||
|
Assert.assertEquals(1, persons.size());
|
||||||
|
mary = persons.get(0);
|
||||||
|
Assert.assertEquals(mary.getFirstName(), "mary");
|
||||||
|
Assert.assertTrue(mary.getKids().contains("Paul"));
|
||||||
|
Assert.assertEquals(2, mary.getAddresses().size());
|
||||||
|
Assert.assertEquals(Address.class, mary.getAddresses().get(0).getClass());
|
||||||
|
|
||||||
|
// Test push/pull
|
||||||
|
mongoDB.pushItemToList(mary, "kids", "Pauline");
|
||||||
|
mongoDB.pullItemFromList(mary, "kids", "Paul");
|
||||||
|
|
||||||
|
Address addr3 = new Address();
|
||||||
|
addr3.setNumber(6);
|
||||||
|
addr3.setStreet("Broadway");
|
||||||
|
mongoDB.pushItemToList(mary, "addresses", addr3);
|
||||||
|
|
||||||
|
mary = mongoDB.loadObject(Person.class, mary.getId());
|
||||||
|
Assert.assertEquals(3, mary.getKids().size());
|
||||||
|
Assert.assertTrue(mary.getKids().contains("Pauline"));
|
||||||
|
Assert.assertFalse(mary.getKids().contains("Paul"));
|
||||||
|
Assert.assertEquals(3, mary.getAddresses().size());
|
||||||
|
Address mainAddress = mary.getMainAddress();
|
||||||
|
Assert.assertEquals("Elm", mainAddress.getStreet());
|
||||||
|
Assert.assertEquals(5, mainAddress.getNumber());
|
||||||
|
Assert.assertEquals(Person.Gender.FEMALE, mary.getGender());
|
||||||
|
Assert.assertTrue(mary.getGenders().contains(Person.Gender.FEMALE));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.keycloak.models.mongo.test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 = "persons")
|
||||||
|
public class Person extends AbstractNoSQLObject {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String firstName;
|
||||||
|
private int age;
|
||||||
|
private List<String> kids;
|
||||||
|
private List<Address> addresses;
|
||||||
|
private Address mainAddress;
|
||||||
|
private Gender gender;
|
||||||
|
private List<Gender> genders;
|
||||||
|
|
||||||
|
|
||||||
|
@NoSQLId
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public Gender getGender() {
|
||||||
|
return gender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGender(Gender gender) {
|
||||||
|
this.gender = gender;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<Gender> getGenders() {
|
||||||
|
return genders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGenders(List<Gender> genders) {
|
||||||
|
this.genders = genders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<String> getKids() {
|
||||||
|
return kids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKids(List<String> kids) {
|
||||||
|
this.kids = kids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public List<Address> getAddresses() {
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddresses(List<Address> addresses) {
|
||||||
|
this.addresses = addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoSQLField
|
||||||
|
public Address getMainAddress() {
|
||||||
|
return mainAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainAddress(Address mainAddress) {
|
||||||
|
this.mainAddress = mainAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum Gender {
|
||||||
|
MALE, FEMALE
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.picketlink.mappings.RealmData;
|
import org.keycloak.models.picketlink.mappings.RealmData;
|
||||||
import org.keycloak.models.picketlink.relationships.RealmAdminRelationship;
|
import org.keycloak.models.picketlink.relationships.RealmAdminRelationship;
|
||||||
|
import org.keycloak.models.utils.KeycloakSessionUtils;
|
||||||
import org.picketlink.idm.PartitionManager;
|
import org.picketlink.idm.PartitionManager;
|
||||||
import org.picketlink.idm.RelationshipManager;
|
import org.picketlink.idm.RelationshipManager;
|
||||||
import org.picketlink.idm.query.RelationshipQuery;
|
import org.picketlink.idm.query.RelationshipQuery;
|
||||||
|
@ -25,11 +26,6 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
|
||||||
protected PartitionManager partitionManager;
|
protected PartitionManager partitionManager;
|
||||||
protected EntityManager entityManager;
|
protected EntityManager entityManager;
|
||||||
|
|
||||||
private static AtomicLong counter = new AtomicLong(1);
|
|
||||||
public static String generateId() {
|
|
||||||
return counter.getAndIncrement() + "-" + System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PicketlinkKeycloakSession(PartitionManager partitionManager, EntityManager entityManager) {
|
public PicketlinkKeycloakSession(PartitionManager partitionManager, EntityManager entityManager) {
|
||||||
this.partitionManager = partitionManager;
|
this.partitionManager = partitionManager;
|
||||||
this.entityManager = entityManager;
|
this.entityManager = entityManager;
|
||||||
|
@ -50,7 +46,7 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmAdapter createRealm(String name) {
|
public RealmAdapter createRealm(String name) {
|
||||||
return createRealm(generateId(), name);
|
return createRealm(KeycloakSessionUtils.generateId(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -819,6 +819,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
|
RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
|
||||||
query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
|
query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
|
||||||
query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
|
query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
|
||||||
|
query.setParameter(SocialLinkRelationship.REALM, realm.getName());
|
||||||
List<SocialLinkRelationship> results = query.getResultList();
|
List<SocialLinkRelationship> results = query.getResultList();
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -850,6 +851,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
relationship.setUser(((UserAdapter)user).getUser());
|
relationship.setUser(((UserAdapter)user).getUser());
|
||||||
relationship.setSocialProvider(socialLink.getSocialProvider());
|
relationship.setSocialProvider(socialLink.getSocialProvider());
|
||||||
relationship.setSocialUsername(socialLink.getSocialUsername());
|
relationship.setSocialUsername(socialLink.getSocialUsername());
|
||||||
|
relationship.setRealm(realm.getName());
|
||||||
|
|
||||||
getRelationshipManager().add(relationship);
|
getRelationshipManager().add(relationship);
|
||||||
}
|
}
|
||||||
|
@ -860,6 +862,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
relationship.setUser(((UserAdapter)user).getUser());
|
relationship.setUser(((UserAdapter)user).getUser());
|
||||||
relationship.setSocialProvider(socialLink.getSocialProvider());
|
relationship.setSocialProvider(socialLink.getSocialProvider());
|
||||||
relationship.setSocialUsername(socialLink.getSocialUsername());
|
relationship.setSocialUsername(socialLink.getSocialUsername());
|
||||||
|
relationship.setRealm(realm.getName());
|
||||||
|
|
||||||
getRelationshipManager().remove(relationship);
|
getRelationshipManager().remove(relationship);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.models.picketlink.relationships;
|
||||||
import org.picketlink.idm.model.AbstractAttributedType;
|
import org.picketlink.idm.model.AbstractAttributedType;
|
||||||
import org.picketlink.idm.model.Attribute;
|
import org.picketlink.idm.model.Attribute;
|
||||||
import org.picketlink.idm.model.Relationship;
|
import org.picketlink.idm.model.Relationship;
|
||||||
|
import org.picketlink.idm.model.annotation.AttributeProperty;
|
||||||
import org.picketlink.idm.model.sample.User;
|
import org.picketlink.idm.model.sample.User;
|
||||||
import org.picketlink.idm.query.AttributeParameter;
|
import org.picketlink.idm.query.AttributeParameter;
|
||||||
import org.picketlink.idm.query.RelationshipQueryParameter;
|
import org.picketlink.idm.query.RelationshipQueryParameter;
|
||||||
|
@ -21,6 +22,10 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
|
||||||
public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
|
public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
|
||||||
public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
|
public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
|
||||||
|
|
||||||
|
// realm is needed to allow searching as combination socialUsername+socialProvider may not be unique
|
||||||
|
// (Same user could have mapped same facebook account to username "foo" in "realm1" and to username "bar" in "realm2")
|
||||||
|
public static final AttributeParameter REALM = new AttributeParameter("realm");
|
||||||
|
|
||||||
public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
|
public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,6 +44,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
public String getSocialProvider() {
|
public String getSocialProvider() {
|
||||||
return (String)getAttribute("socialProvider").getValue();
|
return (String)getAttribute("socialProvider").getValue();
|
||||||
}
|
}
|
||||||
|
@ -47,6 +53,7 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
|
||||||
setAttribute(new Attribute<String>("socialProvider", socialProvider));
|
setAttribute(new Attribute<String>("socialProvider", socialProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
public String getSocialUsername() {
|
public String getSocialUsername() {
|
||||||
return (String)getAttribute("socialUsername").getValue();
|
return (String)getAttribute("socialUsername").getValue();
|
||||||
}
|
}
|
||||||
|
@ -54,4 +61,13 @@ public class SocialLinkRelationship extends AbstractAttributedType implements Re
|
||||||
public void setSocialUsername(String socialProviderUserId) {
|
public void setSocialUsername(String socialProviderUserId) {
|
||||||
setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
|
setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
public String getRealm() {
|
||||||
|
return (String)getAttribute("realm").getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealm(String realm) {
|
||||||
|
setAttribute(new Attribute<String>("realm", realm));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,5 +37,6 @@
|
||||||
<module>api</module>
|
<module>api</module>
|
||||||
<module>picketlink</module>
|
<module>picketlink</module>
|
||||||
<module>jpa</module>
|
<module>jpa</module>
|
||||||
|
<!--<module>mongo</module>-->
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
85
pom.xml
85
pom.xml
|
@ -12,6 +12,14 @@
|
||||||
<resteasy.version>3.0.4.Final</resteasy.version>
|
<resteasy.version>3.0.4.Final</resteasy.version>
|
||||||
<undertow.version>1.0.0.Beta12</undertow.version>
|
<undertow.version>1.0.0.Beta12</undertow.version>
|
||||||
<picketlink.version>2.5.0.Beta6</picketlink.version>
|
<picketlink.version>2.5.0.Beta6</picketlink.version>
|
||||||
|
<mongo.driver.version>2.11.2</mongo.driver.version>
|
||||||
|
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
|
||||||
|
<hibernate.javax.persistence.version>1.0.1.Final</hibernate.javax.persistence.version>
|
||||||
|
<hibernate.entitymanager.version>3.6.6.Final</hibernate.entitymanager.version>
|
||||||
|
<h2.version>1.3.161</h2.version>
|
||||||
|
<dom4j.version>1.6.1</dom4j.version>
|
||||||
|
<mysql.version>5.1.25</mysql.version>
|
||||||
|
<slf4j.version>1.6.1</slf4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<url>http://keycloak.org</url>
|
<url>http://keycloak.org</url>
|
||||||
|
@ -149,6 +157,11 @@
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<version>1.0.1.Final</version>
|
<version>1.0.1.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
|
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||||
|
<version>1.0.0.Beta1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.picketlink</groupId>
|
<groupId>org.picketlink</groupId>
|
||||||
<artifactId>picketlink-common</artifactId>
|
<artifactId>picketlink-common</artifactId>
|
||||||
|
@ -177,7 +190,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<version>3.1.1.GA</version>
|
<version>${jboss.logging.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
@ -187,7 +200,17 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate.javax.persistence</groupId>
|
<groupId>org.hibernate.javax.persistence</groupId>
|
||||||
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
||||||
<version>1.0.1.Final</version>
|
<version>${hibernate.javax.persistence.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>${h2.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-entitymanager</artifactId>
|
||||||
|
<version>${hibernate.entitymanager.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.api-client</groupId>
|
<groupId>com.google.api-client</groupId>
|
||||||
|
@ -236,9 +259,9 @@
|
||||||
<groupId>com.icegreen</groupId>
|
<groupId>com.icegreen</groupId>
|
||||||
<artifactId>greenmail</artifactId>
|
<artifactId>greenmail</artifactId>
|
||||||
<version>1.3.1b</version>
|
<version>1.3.1b</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Selenium -->
|
<!-- Selenium -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-java</artifactId>
|
<artifactId>selenium-java</artifactId>
|
||||||
|
@ -248,7 +271,39 @@
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-chrome-driver</artifactId>
|
<artifactId>selenium-chrome-driver</artifactId>
|
||||||
<version>2.35.0</version>
|
<version>2.35.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
<version>2.11.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.flapdoodle.embed</groupId>
|
||||||
|
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||||
|
<version>1.27</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jmeter</groupId>
|
||||||
|
<artifactId>ApacheJMeter_java</artifactId>
|
||||||
|
<version>2.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dom4j</groupId>
|
||||||
|
<artifactId>dom4j</artifactId>
|
||||||
|
<version>${dom4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Needed for picketlink perf test -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>${mysql.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
@ -330,6 +385,26 @@
|
||||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.lazerycode.jmeter</groupId>
|
||||||
|
<artifactId>jmeter-maven-plugin</artifactId>
|
||||||
|
<version>1.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.lazerycode.jmeter</groupId>
|
||||||
|
<artifactId>jmeter-analysis-maven-plugin</artifactId>
|
||||||
|
<version>1.0.4</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>1.2.1</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,12 @@
|
||||||
<artifactId>keycloak-model-picketlink</artifactId>
|
<artifactId>keycloak-model-picketlink</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-mongo</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-social-core</artifactId>
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
|
@ -144,6 +150,21 @@
|
||||||
<artifactId>jackson-xc</artifactId>
|
<artifactId>jackson-xc</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-common</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>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
@ -157,13 +178,11 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
<version>1.3.161</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate</groupId>
|
<groupId>org.hibernate</groupId>
|
||||||
<artifactId>hibernate-entitymanager</artifactId>
|
<artifactId>hibernate-entitymanager</artifactId>
|
||||||
<version>3.6.6.Final</version>
|
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.keycloak.services.listeners;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
import de.flapdoodle.embed.mongo.MongodExecutable;
|
||||||
|
import de.flapdoodle.embed.mongo.MongodProcess;
|
||||||
|
import de.flapdoodle.embed.mongo.MongodStarter;
|
||||||
|
import de.flapdoodle.embed.mongo.config.MongodConfig;
|
||||||
|
import de.flapdoodle.embed.mongo.distribution.Version;
|
||||||
|
import de.flapdoodle.embed.process.runtime.Network;
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
|
import org.keycloak.services.utils.PropertiesManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import org.keycloak.models.picketlink.PicketlinkKeycloakSession;
|
||||||
import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory;
|
import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory;
|
||||||
import org.keycloak.models.picketlink.mappings.ApplicationEntity;
|
import org.keycloak.models.picketlink.mappings.ApplicationEntity;
|
||||||
import org.keycloak.models.picketlink.mappings.RealmEntity;
|
import org.keycloak.models.picketlink.mappings.RealmEntity;
|
||||||
|
import org.keycloak.services.utils.PropertiesManager;
|
||||||
import org.keycloak.social.SocialRequestManager;
|
import org.keycloak.social.SocialRequestManager;
|
||||||
import org.picketlink.idm.PartitionManager;
|
import org.picketlink.idm.PartitionManager;
|
||||||
import org.picketlink.idm.config.IdentityConfigurationBuilder;
|
import org.picketlink.idm.config.IdentityConfigurationBuilder;
|
||||||
|
@ -21,6 +22,8 @@ import javax.persistence.Persistence;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -29,6 +32,7 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class KeycloakApplication extends Application {
|
public class KeycloakApplication extends Application {
|
||||||
|
|
||||||
protected Set<Object> singletons = new HashSet<Object>();
|
protected Set<Object> singletons = new HashSet<Object>();
|
||||||
protected Set<Class<?>> classes = new HashSet<Class<?>>();
|
protected Set<Class<?>> classes = new HashSet<Class<?>>();
|
||||||
|
|
||||||
|
@ -54,10 +58,36 @@ public class KeycloakApplication extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeycloakSessionFactory buildSessionFactory() {
|
public static KeycloakSessionFactory buildSessionFactory() {
|
||||||
|
if (PropertiesManager.isMongoSessionFactory()) {
|
||||||
|
return buildMongoDBSessionFactory();
|
||||||
|
} else if (PropertiesManager.isPicketlinkSessionFactory()) {
|
||||||
|
return buildPicketlinkSessionFactory();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unknown session factory type: " + PropertiesManager.getSessionFactoryType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeycloakSessionFactory buildPicketlinkSessionFactory() {
|
||||||
EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store");
|
EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store");
|
||||||
return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager());
|
return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static KeycloakSessionFactory buildMongoDBSessionFactory() {
|
||||||
|
String host = PropertiesManager.getMongoHost();
|
||||||
|
int port = PropertiesManager.getMongoPort();
|
||||||
|
String dbName = PropertiesManager.getMongoDbName();
|
||||||
|
boolean dropDatabaseOnStartup = PropertiesManager.dropDatabaseOnStartup();
|
||||||
|
|
||||||
|
// Create MongoDBSessionFactory via reflection now
|
||||||
|
try {
|
||||||
|
Class<? extends KeycloakSessionFactory> mongoDBSessionFactoryClass = (Class<? extends KeycloakSessionFactory>)Class.forName("org.keycloak.models.mongo.keycloak.adapters.MongoDBSessionFactory");
|
||||||
|
Constructor<? extends KeycloakSessionFactory> constr = mongoDBSessionFactoryClass.getConstructor(String.class, int.class, String.class, boolean.class);
|
||||||
|
return constr.newInstance(host, port, dbName, dropDatabaseOnStartup);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public KeycloakSessionFactory getFactory() {
|
public KeycloakSessionFactory getFactory() {
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ public class FormFlows {
|
||||||
|
|
||||||
// TODO find a better way to obtain contextPath
|
// TODO find a better way to obtain contextPath
|
||||||
// Getting context path by removing "rest/" substring from the BaseUri path
|
// Getting context path by removing "rest/" substring from the BaseUri path
|
||||||
formDataBean.setContextPath(requestURI.substring(0,requestURI.length()-5));
|
formDataBean.setContextPath(requestURI.substring(0, requestURI.length() - 6));
|
||||||
formDataBean.setSocialRegistration(socialRegistration);
|
formDataBean.setSocialRegistration(socialRegistration);
|
||||||
|
|
||||||
// Find the service and process relevant template
|
// Find the service and process relevant template
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package org.keycloak.services.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class PropertiesManager {
|
||||||
|
|
||||||
|
private static final String SESSION_FACTORY = "keycloak.sessionFactory";
|
||||||
|
public static final String SESSION_FACTORY_PICKETLINK = "picketlink";
|
||||||
|
public static final String SESSION_FACTORY_MONGO = "mongo";
|
||||||
|
|
||||||
|
private static final String MONGO_HOST = "keycloak.mongodb.host";
|
||||||
|
private static final String MONGO_PORT = "keycloak.mongodb.port";
|
||||||
|
private static final String MONGO_DB_NAME = "keycloak.mongodb.databaseName";
|
||||||
|
private static final String MONGO_DROP_DB_ON_STARTUP = "keycloak.mongodb.dropDatabaseOnStartup";
|
||||||
|
private static final String BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT = "keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit";
|
||||||
|
|
||||||
|
// Port where embedded MongoDB will be started during keycloak bootstrap. Same port will be used by KeycloakApplication then
|
||||||
|
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED = 37017;
|
||||||
|
|
||||||
|
// Port where MongoDB instance is normally started on linux. This port should be used if we're not starting embedded instance (keycloak.mongodb.bootstrapEmbeddedMongoAtContextInit is false)
|
||||||
|
private static final int MONGO_DEFAULT_PORT_KEYCLOAK_WAR = 27017;
|
||||||
|
|
||||||
|
// Port where unit tests will start embedded MongoDB instance
|
||||||
|
public static final int MONGO_DEFAULT_PORT_UNIT_TESTS = 27777;
|
||||||
|
|
||||||
|
public static String getSessionFactoryType() {
|
||||||
|
return System.getProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSessionFactoryType(String sessionFactoryType) {
|
||||||
|
System.setProperty(SESSION_FACTORY, sessionFactoryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultSessionFactoryType() {
|
||||||
|
System.setProperty(SESSION_FACTORY, SESSION_FACTORY_PICKETLINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMongoSessionFactory() {
|
||||||
|
return getSessionFactoryType().equals(SESSION_FACTORY_MONGO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPicketlinkSessionFactory() {
|
||||||
|
return getSessionFactoryType().equals(SESSION_FACTORY_PICKETLINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMongoHost() {
|
||||||
|
return System.getProperty(MONGO_HOST, "localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMongoHost(String mongoHost) {
|
||||||
|
System.setProperty(MONGO_HOST, mongoHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMongoPort() {
|
||||||
|
return Integer.parseInt(System.getProperty(MONGO_PORT, String.valueOf(MONGO_DEFAULT_PORT_KEYCLOAK_WAR_EMBEDDED)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMongoPort(int mongoPort) {
|
||||||
|
System.setProperty(MONGO_PORT, String.valueOf(mongoPort));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMongoDbName() {
|
||||||
|
return System.getProperty(MONGO_DB_NAME, "keycloak");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMongoDbName(String mongoMongoDbName) {
|
||||||
|
System.setProperty(MONGO_DB_NAME, mongoMongoDbName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean dropDatabaseOnStartup() {
|
||||||
|
return Boolean.parseBoolean(System.getProperty(MONGO_DROP_DB_ON_STARTUP, "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDropDatabaseOnStartup(boolean dropDatabaseOnStartup) {
|
||||||
|
System.setProperty(MONGO_DROP_DB_ON_STARTUP, String.valueOf(dropDatabaseOnStartup));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean bootstrapEmbeddedMongoAtContextInit() {
|
||||||
|
return isMongoSessionFactory() && Boolean.parseBoolean(System.getProperty(BOOTSTRAP_EMBEDDED_MONGO_AT_CONTEXT_INIT, "true"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,24 +18,20 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.test.common.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.test.common.SessionFactoryTestContext;
|
||||||
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||||
|
|
||||||
public class AuthenticationManagerTest {
|
public class AuthenticationManagerTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
private RealmManager adapter;
|
|
||||||
private AuthenticationManager am;
|
private AuthenticationManager am;
|
||||||
private KeycloakSessionFactory factory;
|
|
||||||
private MultivaluedMap<String, String> formData;
|
private MultivaluedMap<String, String> formData;
|
||||||
private KeycloakSession identitySession;
|
|
||||||
private TimeBasedOTP otp;
|
private TimeBasedOTP otp;
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
private UserModel user;
|
private UserModel user;
|
||||||
|
|
||||||
@After
|
public AuthenticationManagerTest(SessionFactoryTestContext testContext) {
|
||||||
public void after() throws Exception {
|
super(testContext);
|
||||||
identitySession.getTransaction().commit();
|
|
||||||
identitySession.close();
|
|
||||||
factory.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -134,12 +130,8 @@ public class AuthenticationManagerTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
factory = KeycloakApplication.buildSessionFactory();
|
super.before();
|
||||||
identitySession = factory.createSession();
|
realm = getRealmManager().createRealm("Test");
|
||||||
identitySession.getTransaction().begin();
|
|
||||||
adapter = new RealmManager(identitySession);
|
|
||||||
|
|
||||||
realm = adapter.createRealm("Test");
|
|
||||||
realm.setAccessCodeLifespan(100);
|
realm.setAccessCodeLifespan(100);
|
||||||
realm.setCookieLoginAllowed(true);
|
realm.setCookieLoginAllowed(true);
|
||||||
realm.setEnabled(true);
|
realm.setEnabled(true);
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.keycloak.services.managers.OAuthClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.test.common.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.test.common.SessionFactoryTestContext;
|
||||||
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -24,30 +26,16 @@ import java.util.StringTokenizer;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class AdapterTest {
|
public class AdapterTest extends AbstractKeycloakTest {
|
||||||
private KeycloakSessionFactory factory;
|
|
||||||
private KeycloakSession identitySession;
|
|
||||||
private RealmManager adapter;
|
|
||||||
private RealmModel realmModel;
|
private RealmModel realmModel;
|
||||||
|
|
||||||
@Before
|
public AdapterTest(SessionFactoryTestContext testContext) {
|
||||||
public void before() throws Exception {
|
super(testContext);
|
||||||
factory = KeycloakApplication.buildSessionFactory();
|
|
||||||
identitySession = factory.createSession();
|
|
||||||
identitySession.getTransaction().begin();
|
|
||||||
adapter = new RealmManager(identitySession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void after() throws Exception {
|
|
||||||
identitySession.getTransaction().commit();
|
|
||||||
identitySession.close();
|
|
||||||
factory.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void installTest() throws Exception {
|
public void installTest() throws Exception {
|
||||||
new InstallationManager().install(adapter);
|
new InstallationManager().install(getRealmManager());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +51,7 @@ public class AdapterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test1CreateRealm() throws Exception {
|
public void test1CreateRealm() throws Exception {
|
||||||
realmModel = adapter.createRealm("JUGGLER");
|
realmModel = getRealmManager().createRealm("JUGGLER");
|
||||||
realmModel.setAccessCodeLifespan(100);
|
realmModel.setAccessCodeLifespan(100);
|
||||||
realmModel.setAccessCodeLifespanUserAction(600);
|
realmModel.setAccessCodeLifespanUserAction(600);
|
||||||
realmModel.setCookieLoginAllowed(true);
|
realmModel.setCookieLoginAllowed(true);
|
||||||
|
@ -76,7 +64,7 @@ public class AdapterTest {
|
||||||
realmModel.addDefaultRole("foo");
|
realmModel.addDefaultRole("foo");
|
||||||
|
|
||||||
System.out.println(realmModel.getId());
|
System.out.println(realmModel.getId());
|
||||||
realmModel = adapter.getRealm(realmModel.getId());
|
realmModel = getRealmManager().getRealm(realmModel.getId());
|
||||||
Assert.assertNotNull(realmModel);
|
Assert.assertNotNull(realmModel);
|
||||||
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
|
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
|
||||||
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
|
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
|
||||||
|
@ -153,6 +141,8 @@ public class AdapterTest {
|
||||||
user.setEmail("bburke@redhat.com");
|
user.setEmail("bburke@redhat.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealmManager adapter = getRealmManager();
|
||||||
|
|
||||||
{
|
{
|
||||||
List<UserModel> userModels = adapter.searchUsers("total junk query", realmModel);
|
List<UserModel> userModels = adapter.searchUsers("total junk query", realmModel);
|
||||||
Assert.assertEquals(userModels.size(), 0);
|
Assert.assertEquals(userModels.size(), 0);
|
||||||
|
|
|
@ -19,6 +19,8 @@ import org.keycloak.models.SocialLinkModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
import org.keycloak.services.resources.SaasService;
|
import org.keycloak.services.resources.SaasService;
|
||||||
|
import org.keycloak.test.common.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.test.common.SessionFactoryTestContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -28,29 +30,15 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class ImportTest {
|
public class ImportTest extends AbstractKeycloakTest {
|
||||||
private KeycloakSessionFactory factory;
|
|
||||||
private KeycloakSession identitySession;
|
|
||||||
private RealmManager manager;
|
|
||||||
private RealmModel realmModel;
|
|
||||||
|
|
||||||
@Before
|
public ImportTest(SessionFactoryTestContext testContext) {
|
||||||
public void before() throws Exception {
|
super(testContext);
|
||||||
factory = KeycloakApplication.buildSessionFactory();
|
|
||||||
identitySession = factory.createSession();
|
|
||||||
identitySession.getTransaction().begin();
|
|
||||||
manager = new RealmManager(identitySession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void after() throws Exception {
|
|
||||||
identitySession.getTransaction().commit();
|
|
||||||
identitySession.close();
|
|
||||||
factory.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void install() throws Exception {
|
public void install() throws Exception {
|
||||||
|
RealmManager manager = getRealmManager();
|
||||||
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
||||||
defaultRealm.setName(RealmModel.DEFAULT_REALM);
|
defaultRealm.setName(RealmModel.DEFAULT_REALM);
|
||||||
defaultRealm.setEnabled(true);
|
defaultRealm.setEnabled(true);
|
||||||
|
@ -93,7 +81,7 @@ public class ImportTest {
|
||||||
|
|
||||||
List<ApplicationModel> resources = realm.getApplications();
|
List<ApplicationModel> resources = realm.getApplications();
|
||||||
Assert.assertEquals(2, resources.size());
|
Assert.assertEquals(2, resources.size());
|
||||||
List<RealmModel> realms = identitySession.getRealms(admin);
|
List<RealmModel> realms = getIdentitySession().getRealms(admin);
|
||||||
Assert.assertEquals(1, realms.size());
|
Assert.assertEquals(1, realms.size());
|
||||||
|
|
||||||
// Test scope relationship
|
// Test scope relationship
|
||||||
|
@ -129,6 +117,7 @@ public class ImportTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void install2() throws Exception {
|
public void install2() throws Exception {
|
||||||
|
RealmManager manager = getRealmManager();
|
||||||
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
||||||
defaultRealm.setName(RealmModel.DEFAULT_REALM);
|
defaultRealm.setName(RealmModel.DEFAULT_REALM);
|
||||||
defaultRealm.setEnabled(true);
|
defaultRealm.setEnabled(true);
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.keycloak.test.common;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public abstract class AbstractKeycloakTest {
|
||||||
|
|
||||||
|
protected static final SessionFactoryTestContext[] TEST_CONTEXTS;
|
||||||
|
|
||||||
|
private final SessionFactoryTestContext testContext;
|
||||||
|
private KeycloakSessionFactory factory;
|
||||||
|
private KeycloakSession identitySession;
|
||||||
|
private RealmManager realmManager;
|
||||||
|
|
||||||
|
// STATIC METHODS
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
// TODO: MongoDB disabled by default
|
||||||
|
TEST_CONTEXTS = new SessionFactoryTestContext[] {
|
||||||
|
new PicketlinkSessionFactoryTestContext(),
|
||||||
|
// new MongoDBSessionFactoryTestContext()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameterized.Parameters
|
||||||
|
public static Iterable<Object[]> parameters() {
|
||||||
|
List<Object[]> params = new ArrayList<Object[]>();
|
||||||
|
|
||||||
|
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
|
||||||
|
params.add(new Object[] {testContext});
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void baseBeforeClass() {
|
||||||
|
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
|
||||||
|
testContext.beforeTestClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void baseAfterClass() {
|
||||||
|
for (SessionFactoryTestContext testContext : TEST_CONTEXTS) {
|
||||||
|
testContext.afterTestClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NON-STATIC METHODS
|
||||||
|
|
||||||
|
public AbstractKeycloakTest(SessionFactoryTestContext testContext) {
|
||||||
|
this.testContext = testContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
testContext.initEnvironment();
|
||||||
|
factory = KeycloakApplication.buildSessionFactory();
|
||||||
|
identitySession = factory.createSession();
|
||||||
|
identitySession.getTransaction().begin();
|
||||||
|
realmManager = new RealmManager(identitySession);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() throws Exception {
|
||||||
|
identitySession.getTransaction().commit();
|
||||||
|
identitySession.close();
|
||||||
|
factory.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RealmManager getRealmManager() {
|
||||||
|
return realmManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KeycloakSession getIdentitySession() {
|
||||||
|
return identitySession;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
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.resources.KeycloakApplication;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.test.common;
|
||||||
|
|
||||||
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.services.utils.PropertiesManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class PicketlinkSessionFactoryTestContext implements SessionFactoryTestContext {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTestClass() {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTestClass() {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initEnvironment() {
|
||||||
|
PropertiesManager.setSessionFactoryType(PropertiesManager.SESSION_FACTORY_PICKETLINK);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.keycloak.test.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface SessionFactoryTestContext {
|
||||||
|
|
||||||
|
void beforeTestClass();
|
||||||
|
|
||||||
|
void afterTestClass();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init system properties (or other configuration) to ensure that KeycloakApplication.buildSessionFactory() will return correct
|
||||||
|
* instance of KeycloakSessionFactory for our test
|
||||||
|
*/
|
||||||
|
void initEnvironment();
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
Executing testsuite
|
|
||||||
===================
|
|
||||||
|
|
||||||
Browser
|
|
||||||
-------
|
|
||||||
|
|
||||||
The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can also be executed with Chrome or Firefox.
|
|
||||||
|
|
||||||
To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome`
|
|
||||||
|
|
||||||
|
|
||||||
Test utils
|
|
||||||
==========
|
|
||||||
|
|
||||||
Keycloak server
|
|
||||||
---------------
|
|
||||||
|
|
||||||
To start a basic Keycloak server for testing run:
|
|
||||||
|
|
||||||
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer
|
|
||||||
|
|
||||||
or just run KeycloakServer from your favourite IDE!
|
|
||||||
|
|
||||||
When starting the server it can also import a realm from a json file:
|
|
||||||
|
|
||||||
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-import testrealm.json"
|
|
||||||
|
|
||||||
You can also change the host and port the server is bound to:
|
|
||||||
|
|
||||||
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-b host -p 8080"
|
|
||||||
|
|
||||||
TOTP codes
|
|
||||||
----------
|
|
||||||
|
|
||||||
To generate totp codes without Google authenticator run:
|
|
||||||
|
|
||||||
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.TotpGenerator -Dexec.args="PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB"
|
|
||||||
|
|
||||||
or just run TotpGenerator from your favourite IDE!
|
|
||||||
|
|
||||||
Replace value of -Dexec.args with the secret from the totp configuration page
|
|
||||||
|
|
||||||
Mail server
|
|
||||||
-----------
|
|
||||||
|
|
||||||
To start a test mail server for testing email sending run:
|
|
||||||
|
|
||||||
mvn exec:java -Dexec.mainClass=org.keycloak.testutils.MailServer
|
|
||||||
|
|
||||||
or just run MailServer from your favourite IDE!
|
|
||||||
|
|
||||||
To configure Keycloak to use the above server add:
|
|
||||||
|
|
||||||
-Dkeycloak.mail.smtp.from=auto@keycloak.org -Dkeycloak.mail.smtp.host=localhost -Dkeycloak.mail.smtp.port=3025
|
|
57
testsuite/integration/README.md
Normal file
57
testsuite/integration/README.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
Executing testsuite
|
||||||
|
===================
|
||||||
|
|
||||||
|
Browser
|
||||||
|
-------
|
||||||
|
|
||||||
|
The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can also be executed with Chrome or Firefox.
|
||||||
|
|
||||||
|
To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome`
|
||||||
|
|
||||||
|
|
||||||
|
Test utils
|
||||||
|
==========
|
||||||
|
|
||||||
|
Keycloak server
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To start a basic Keycloak server for testing run:
|
||||||
|
|
||||||
|
mvn exec:java -Pkeycloak-server
|
||||||
|
|
||||||
|
or run org.keycloak.testutils.KeycloakServer from your favourite IDE!
|
||||||
|
|
||||||
|
When starting the server it can also import a realm from a json file:
|
||||||
|
|
||||||
|
mvn exec:java -Pkeycloak-server -Dimport=testrealm.json
|
||||||
|
|
||||||
|
TOTP codes
|
||||||
|
----------
|
||||||
|
|
||||||
|
To generate totp codes without Google authenticator run:
|
||||||
|
|
||||||
|
mvn exec:java -Ptotp -Dsecret='PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB'
|
||||||
|
|
||||||
|
or run org.keycloak.testutils.TotpGenerator from your favourite IDE!
|
||||||
|
|
||||||
|
Replace value of -Dsecret with the secret from the totp configuration page (remember quotes!)
|
||||||
|
|
||||||
|
Mail server
|
||||||
|
-----------
|
||||||
|
|
||||||
|
To start a test mail server for testing email sending run:
|
||||||
|
|
||||||
|
mvn exec:java -Pmail-server
|
||||||
|
|
||||||
|
or run org.keycloak.testutils.MailServer from your favourite IDE!
|
||||||
|
|
||||||
|
To configure Keycloak to use the above server add the following system properties:
|
||||||
|
|
||||||
|
keycloak.mail.smtp.from=auto@keycloak.org
|
||||||
|
keycloak.mail.smtp.host=localhost
|
||||||
|
keycloak.mail.smtp.port=3025
|
||||||
|
|
||||||
|
For example if using the test utils Keycloak server start it with:
|
||||||
|
|
||||||
|
mvn exec:java -Pkeycloak-server -Dkeycloak.mail.smtp.from=auto@keycloak.org -Dkeycloak.mail.smtp.host=localhost -Dkeycloak.mail.smtp.port=3025
|
||||||
|
|
253
testsuite/integration/pom.xml
Normal file
253
testsuite/integration/pom.xml
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project>
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-alpha-1</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-testsuite-integration</artifactId>
|
||||||
|
<name>Keycloak Integration TestSuite</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-as7-adapter</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
|
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk16</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-admin-ui</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-admin-ui-styles</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-google</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-twitter</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-social-facebook</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-forms</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-simple-schema</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>jaxrs-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>jose-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-undertow</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.undertow</groupId>
|
||||||
|
<artifactId>undertow-servlet</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.undertow</groupId>
|
||||||
|
<artifactId>undertow-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-xc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.javax.persistence</groupId>
|
||||||
|
<artifactId>hibernate-jpa-2.0-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>1.3.161</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-entitymanager</artifactId>
|
||||||
|
<version>3.6.6.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.icegreen</groupId>
|
||||||
|
<artifactId>greenmail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mongodb</groupId>
|
||||||
|
<artifactId>mongo-java-driver</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>keycloak-server</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>org.keycloak.testutils.KeycloakServer</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>mail-server</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>org.keycloak.testutils.MailServer</mainClass>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>totp</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>org.keycloak.testutils.TotpGenerator</mainClass>
|
||||||
|
<arguments>
|
||||||
|
<argument>${secret}</argument>
|
||||||
|
</arguments>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
|
@ -23,15 +23,13 @@ package org.keycloak.testutils;
|
||||||
|
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
import io.undertow.Undertow.Builder;
|
import io.undertow.Undertow.Builder;
|
||||||
import io.undertow.server.handlers.resource.ClassPathResourceManager;
|
import io.undertow.server.handlers.resource.*;
|
||||||
import io.undertow.servlet.Servlets;
|
import io.undertow.servlet.Servlets;
|
||||||
import io.undertow.servlet.api.DeploymentInfo;
|
import io.undertow.servlet.api.DeploymentInfo;
|
||||||
import io.undertow.servlet.api.FilterInfo;
|
import io.undertow.servlet.api.FilterInfo;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.*;
|
||||||
import java.io.FileInputStream;
|
import java.net.URL;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
|
|
||||||
|
@ -46,7 +44,6 @@ import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.FormService;
|
|
||||||
import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
@ -64,6 +61,7 @@ public class KeycloakServer {
|
||||||
public static class KeycloakServerConfig {
|
public static class KeycloakServerConfig {
|
||||||
private String host = "localhost";
|
private String host = "localhost";
|
||||||
private int port = 8081;
|
private int port = 8081;
|
||||||
|
private String resourcesHome;
|
||||||
|
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
return host;
|
return host;
|
||||||
|
@ -73,6 +71,10 @@ public class KeycloakServer {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getResourcesHome() {
|
||||||
|
return resourcesHome;
|
||||||
|
}
|
||||||
|
|
||||||
public void setHost(String host) {
|
public void setHost(String host) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +82,10 @@ public class KeycloakServer {
|
||||||
public void setPort(int port) {
|
public void setPort(int port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setResourcesHome(String resourcesHome) {
|
||||||
|
this.resourcesHome = resourcesHome;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T loadJson(InputStream is, Class<T> type) {
|
private static <T> T loadJson(InputStream is, Class<T> type) {
|
||||||
|
@ -109,6 +115,19 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getProperties().containsKey("resources")) {
|
||||||
|
String resources = System.getProperty("resources");
|
||||||
|
if (resources == null || resources.equals("")) {
|
||||||
|
for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
|
||||||
|
if (c.contains("keycloak" + File.separator + "testsuite" + File.separator + "integration")) {
|
||||||
|
config.setResourcesHome(c.replaceFirst("testsuite.integration.*", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.setResourcesHome(resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final KeycloakServer keycloak = new KeycloakServer(config);
|
final KeycloakServer keycloak = new KeycloakServer(config);
|
||||||
keycloak.sysout = true;
|
keycloak.sysout = true;
|
||||||
keycloak.start();
|
keycloak.start();
|
||||||
|
@ -119,6 +138,10 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getProperties().containsKey("import")) {
|
||||||
|
keycloak.importRealm(new FileInputStream(System.getProperty("import")));
|
||||||
|
}
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -193,22 +216,18 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM);
|
||||||
manager.generateRealmKeys(defaultRealm);
|
defaultRealm.setName(RealmModel.DEFAULT_REALM);
|
||||||
|
|
||||||
defaultRealm.setEnabled(true);
|
defaultRealm.setEnabled(true);
|
||||||
defaultRealm.setTokenLifespan(300);
|
defaultRealm.setTokenLifespan(300);
|
||||||
defaultRealm.setAccessCodeLifespan(60);
|
defaultRealm.setAccessCodeLifespan(60);
|
||||||
defaultRealm.setAccessCodeLifespanUserAction(600);
|
defaultRealm.setAccessCodeLifespanUserAction(600);
|
||||||
defaultRealm.setSslNotRequired(false);
|
defaultRealm.setSslNotRequired(true);
|
||||||
defaultRealm.setCookieLoginAllowed(true);
|
defaultRealm.setCookieLoginAllowed(true);
|
||||||
defaultRealm.setRegistrationAllowed(true);
|
defaultRealm.setRegistrationAllowed(true);
|
||||||
defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
|
manager.generateRealmKeys(defaultRealm);
|
||||||
defaultRealm.setVerifyEmail(false);
|
|
||||||
|
|
||||||
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
|
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
|
||||||
UserModel admin = defaultRealm.addUser("admin");
|
defaultRealm.addDefaultRole(SaasService.REALM_CREATOR_ROLE);
|
||||||
defaultRealm.grantRole(admin, role);
|
|
||||||
|
|
||||||
session.getTransaction().commit();
|
session.getTransaction().commit();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -230,7 +249,7 @@ public class KeycloakServer {
|
||||||
di.setClassLoader(getClass().getClassLoader());
|
di.setClassLoader(getClass().getClassLoader());
|
||||||
di.setContextPath("/auth-server");
|
di.setContextPath("/auth-server");
|
||||||
di.setDeploymentName("Keycloak");
|
di.setDeploymentName("Keycloak");
|
||||||
di.setResourceManager(new ClassPathResourceManager(FormService.class.getClassLoader(), "META-INF/resources"));
|
di.setResourceManager(new KeycloakResourceManager(config.getResourcesHome()));
|
||||||
|
|
||||||
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
|
FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class);
|
||||||
di.addFilter(filter);
|
di.addFilter(filter);
|
||||||
|
@ -242,6 +261,10 @@ public class KeycloakServer {
|
||||||
|
|
||||||
setupDefaultRealm();
|
setupDefaultRealm();
|
||||||
|
|
||||||
|
if (config.getResourcesHome() != null) {
|
||||||
|
info("Loading resources from " + config.getResourcesHome());
|
||||||
|
}
|
||||||
|
|
||||||
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth-server) in "
|
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth-server) in "
|
||||||
+ (System.currentTimeMillis() - start) + " ms\n");
|
+ (System.currentTimeMillis() - start) + " ms\n");
|
||||||
}
|
}
|
||||||
|
@ -261,4 +284,51 @@ public class KeycloakServer {
|
||||||
info("Stopped Keycloak");
|
info("Stopped Keycloak");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class KeycloakResourceManager implements ResourceManager {
|
||||||
|
|
||||||
|
private String resourcesHome;
|
||||||
|
|
||||||
|
public KeycloakResourceManager(String resourcesHome) {
|
||||||
|
this.resourcesHome = resourcesHome;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource getResource(String path) throws IOException {
|
||||||
|
if (resourcesHome == null) {
|
||||||
|
String realPath = "META-INF/resources" + path;
|
||||||
|
|
||||||
|
if (realPath.endsWith("/admin/")) {
|
||||||
|
realPath += "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
URL url = getClass().getClassLoader().getResource(realPath);
|
||||||
|
|
||||||
|
return new URLResource(url, url.openConnection(), path);
|
||||||
|
} else {
|
||||||
|
File file;
|
||||||
|
if (path.startsWith("/forms")) {
|
||||||
|
file = file(resourcesHome, "forms", "src", "main", "resources", "META-INF", "resources", path.replace('/', File.separatorChar));
|
||||||
|
} else if (path.startsWith("/admin")) {
|
||||||
|
file = file(resourcesHome, "admin-ui", "src", "main", "resources", "META-INF", "resources", path.replace('/', File.separatorChar));
|
||||||
|
if (!file.isFile()) {
|
||||||
|
file = file(resourcesHome, "admin-ui-styles", "src", "main", "resources", "META-INF", "resources", path.replace('/', File.separatorChar));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IOException("Unknown resource " + path);
|
||||||
|
}
|
||||||
|
return new FileResource(file, new FileResourceManager(file.getParentFile(), 1), path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File file(String... path) {
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
s.append(path[0]);
|
||||||
|
for (int i = 1; i < path.length; i++) {
|
||||||
|
s.append(File.separator);
|
||||||
|
s.append(path[i]);
|
||||||
|
}
|
||||||
|
return new File(s.toString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,6 +13,9 @@ public class MailServer {
|
||||||
|
|
||||||
GreenMail greenMail = new GreenMail(setup);
|
GreenMail greenMail = new GreenMail(setup);
|
||||||
greenMail.start();
|
greenMail.start();
|
||||||
|
|
||||||
|
System.out.println("Started mail server (localhost:3025)");
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int c = greenMail.getReceivedMessages().length;
|
int c = greenMail.getReceivedMessages().length;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue