keycloak-aplcache
Changes
connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java 1(+1 -0)
connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java 44(+44 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java 4(+1 -3)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java 2(+1 -1)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 5(+2 -3)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java 2(+1 -1)
pom.xml 36(+24 -12)
testsuite/integration/pom.xml 64(+55 -9)
testsuite/integration/README.md 30(+30 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java 82(+82 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java 144(+144 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java 75(+75 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java 183(+183 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java 70(+70 -0)
testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java 186(+186 -0)
Details
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
index 35a7e42..aded77e 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
@@ -18,6 +18,7 @@ import org.keycloak.connections.mongo.api.types.Mapper;
import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
import org.keycloak.connections.mongo.impl.types.BasicDBListMapper;
+import org.keycloak.connections.mongo.impl.types.BasicDBListToSetMapper;
import org.keycloak.connections.mongo.impl.types.BasicDBObjectMapper;
import org.keycloak.connections.mongo.impl.types.BasicDBObjectToMapMapper;
import org.keycloak.connections.mongo.impl.types.EnumToStringMapper;
@@ -35,8 +36,10 @@ import org.keycloak.models.utils.reflection.PropertyQueries;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -71,6 +74,10 @@ public class MongoStoreImpl implements MongoStore {
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry));
+ mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, HashSet.class));
+ mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, Set.class));
+ mapperRegistry.addDBObjectMapper(new BasicDBListToSetMapper(mapperRegistry));
+
mapperRegistry.addAppObjectMapper(new MapMapper(HashMap.class));
mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
index f44f545..cc229c6 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListMapper.java
@@ -6,6 +6,7 @@ import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java
new file mode 100644
index 0000000..d43781a
--- /dev/null
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/BasicDBListToSetMapper.java
@@ -0,0 +1,44 @@
+package org.keycloak.connections.mongo.impl.types;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.mongodb.BasicDBList;
+import org.keycloak.connections.mongo.api.types.Mapper;
+import org.keycloak.connections.mongo.api.types.MapperContext;
+import org.keycloak.connections.mongo.api.types.MapperRegistry;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class BasicDBListToSetMapper implements Mapper<BasicDBList, Set> {
+
+ private final MapperRegistry mapperRegistry;
+
+ public BasicDBListToSetMapper(MapperRegistry mapperRegistry) {
+ this.mapperRegistry = mapperRegistry;
+ }
+
+ @Override
+ public Set convertObject(MapperContext<BasicDBList, Set> context) {
+ BasicDBList dbList = context.getObjectToConvert();
+ Set<Object> appObjects = new HashSet<Object>();
+ Class<?> expectedListElementType = context.getGenericTypes().get(0);
+
+ for (Object dbObject : dbList) {
+ MapperContext<Object, Object> newContext = new MapperContext<Object, Object>(dbObject, expectedListElementType, null);
+ appObjects.add(mapperRegistry.convertDBObjectToApplicationObject(newContext));
+ }
+ return appObjects;
+ }
+
+ @Override
+ public Class<? extends BasicDBList> getTypeOfObjectToConvert() {
+ return BasicDBList.class;
+ }
+
+ @Override
+ public Class<Set> getExpectedReturnType() {
+ return Set.class;
+ }
+}
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
index 3274fe3..dc649d6 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/types/ListMapper.java
@@ -5,12 +5,12 @@ import org.keycloak.connections.mongo.api.types.Mapper;
import org.keycloak.connections.mongo.api.types.MapperContext;
import org.keycloak.connections.mongo.api.types.MapperRegistry;
-import java.util.List;
+import java.util.Collection;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class ListMapper<T extends List> implements Mapper<T, BasicDBList> {
+public class ListMapper<T extends Collection> implements Mapper<T, BasicDBList> {
private final MapperRegistry mapperRegistry;
private final Class<T> listType;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index 7ce4935..691bce4 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -27,11 +27,9 @@ public class MongoRealmProvider implements RealmProvider {
private final MongoStoreInvocationContext invocationContext;
private final KeycloakSession session;
- private final MongoStore mongoStore;
- public MongoRealmProvider(KeycloakSession session, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) {
+ public MongoRealmProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
this.session = session;
- this.mongoStore = mongoStore;
this.invocationContext = invocationContext;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
index fceeb82..9606753 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProviderFactory.java
@@ -27,7 +27,7 @@ public class MongoRealmProviderFactory implements RealmProviderFactory {
@Override
public RealmProvider create(KeycloakSession session) {
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
- return new MongoRealmProvider(session, connection.getMongoStore(), connection.getInvocationContext());
+ return new MongoRealmProvider(session, connection.getInvocationContext());
}
@Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 52d4c98..9422711 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -33,11 +33,9 @@ public class MongoUserProvider implements UserProvider {
private final MongoStoreInvocationContext invocationContext;
private final KeycloakSession session;
- private final MongoStore mongoStore;
- public MongoUserProvider(KeycloakSession session, MongoStore mongoStore, MongoStoreInvocationContext invocationContext) {
+ public MongoUserProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
this.session = session;
- this.mongoStore = mongoStore;
this.invocationContext = invocationContext;
}
@@ -311,6 +309,7 @@ public class MongoUserProvider implements UserProvider {
@Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
+ federatedUser = getUserById(federatedUser.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
index 083d0c0..a117048 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProviderFactory.java
@@ -27,7 +27,7 @@ public class MongoUserProviderFactory implements UserProviderFactory {
@Override
public UserProvider create(KeycloakSession session) {
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
- return new MongoUserProvider(session, connection.getMongoStore(), connection.getInvocationContext());
+ return new MongoUserProvider(session, connection.getInvocationContext());
}
@Override
pom.xml 36(+24 -12)
diff --git a/pom.xml b/pom.xml
index 4aada1a..d43ce03 100755
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,8 @@
<properties>
<aesh.version>0.33.12</aesh.version>
+ <apacheds.version>2.0.0-M17</apacheds.version>
+ <apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
<base64.version>2.3.8</base64.version>
<bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
<bouncycastle.mail.version>1.50</bouncycastle.mail.version>
@@ -23,7 +25,6 @@
<!-- <undertow.version>1.1.0.Final</undertow.version> -->
<undertow.version>1.1.1.Final</undertow.version>
<picketlink.version>2.7.0.CR3</picketlink.version>
- <picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
<mongo.driver.version>2.11.3</mongo.driver.version>
<jboss.logging.version>3.1.4.GA</jboss.logging.version>
<syslog4j.version>0.9.30</syslog4j.version>
@@ -308,17 +309,6 @@
<version>${picketlink.version}</version>
</dependency>
<dependency>
- <groupId>org.picketbox</groupId>
- <artifactId>picketbox-ldap</artifactId>
- <version>${picketbox.ldap.version}</version>
- </dependency>
- <dependency>
- <groupId>org.picketbox</groupId>
- <artifactId>picketbox-ldap</artifactId>
- <version>${picketbox.ldap.version}</version>
- <type>test-jar</type>
- </dependency>
- <dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
@@ -422,6 +412,28 @@
<version>${winzipaes.version}</version>
</dependency>
+ <!-- Apache DS -->
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-core-annotations</artifactId>
+ <version>${apacheds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-interceptor-kerberos</artifactId>
+ <version>${apacheds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-server-annotations</artifactId>
+ <version>${apacheds.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.api</groupId>
+ <artifactId>api-ldap-codec-standalone</artifactId>
+ <version>${apacheds.codec.version}</version>
+ </dependency>
+
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
diff --git a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
index 2b78406..fff0bfb 100755
--- a/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/LDAPConnectionTestManager.java
@@ -27,11 +27,10 @@ public class LDAPConnectionTestManager {
try {
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- env.put(Context.SECURITY_AUTHENTICATION, "simple");
-
env.put(Context.PROVIDER_URL, connectionUrl);
if (TEST_AUTHENTICATION.equals(action)) {
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindDn);
char[] bindCredentialChar = null;
testsuite/integration/pom.xml 64(+55 -9)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 5800d98..98697e5 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -114,10 +114,6 @@
<artifactId>javase</artifactId>
</dependency>
<dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk15on</artifactId>
- </dependency>
- <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${keycloak.apache.httpcomponents.version}</version>
@@ -212,16 +208,32 @@
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
</dependency>
+
+ <!-- Apache DS -->
<dependency>
- <groupId>org.picketbox</groupId>
- <artifactId>picketbox-ldap</artifactId>
- <type>test-jar</type>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-core-annotations</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
- <groupId>org.picketbox</groupId>
- <artifactId>picketbox-ldap</artifactId>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-interceptor-kerberos</artifactId>
</dependency>
<dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>apacheds-server-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.api</groupId>
+ <artifactId>api-ldap-codec-standalone</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-wildfly-common</artifactId>
<scope>test</scope>
@@ -270,6 +282,12 @@
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ </plugin>
</plugins>
</build>
@@ -316,6 +334,34 @@
</plugins>
</build>
</profile>
+ <profile>
+ <id>ldap</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.testutils.ldap.LDAPEmbeddedServer</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>kerberos</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.testutils.ldap.KerberosEmbeddedServer</mainClass>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
<profile>
<id>jpa</id>
testsuite/integration/README.md 30(+30 -0)
diff --git a/testsuite/integration/README.md b/testsuite/integration/README.md
index 9de055c..a8d0671 100644
--- a/testsuite/integration/README.md
+++ b/testsuite/integration/README.md
@@ -90,4 +90,34 @@ To configure Keycloak to use the above server add the following system propertie
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
+
+LDAP server
+-----------
+
+To start a ApacheDS based LDAP server for testing LDAP sending run:
+
+ mvn exec:java -Pldap
+
+There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
+in Keycloak admin console with the settings like:
+Vendor: Other
+Connection URL: ldap://localhost:10389
+Base DN: dc=keycloak,dc=org
+User DN Suffix: ou=People,dc=keycloak,dc=org
+Bind DN: uid=admin,ou=system
+Bind credential: secret
+
+Kerberos server
+---------------
+
+To start a ApacheDS based Kerberos server for testing Kerberos + LDAP sending run:
+
+ mvn exec:java -Pkerberos
+
+There are additional system properties you can use to configure (See EmbeddedServersFactory class for details). Once done, you can create LDAP Federation provider
+in Keycloak admin console with same settings like mentioned in previous LDAP section. And you can enable Kerberos with the settings like:
+
+Server Principal: HTTP/localhost@KEYCLOAK.ORG
+KeyTab: $KEYCLOAK_SOURCES/testsuite/integration/src/main/resources/kerberos/http.keytab
+
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
index 9b0c200..d5e1c37 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java
@@ -104,27 +104,8 @@ public class KeycloakServer {
}
public static void main(String[] args) throws Throwable {
- //bootstrapLdap(); Can't seem to get this to work.
bootstrapKeycloakServer(args);
}
- /*private static LDAPEmbeddedServer embeddedServer;
- public static void bootstrapLdap() throws Exception {
- embeddedServer = new LDAPEmbeddedServer();
- embeddedServer.setup();
- embeddedServer.importLDIF("ldap/users.ldif");
- Runtime.getRuntime().addShutdownHook(new Thread() {
- @Override
- public void run() {
- try {
- embeddedServer.tearDown();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- });
-
-
- } */
public static KeycloakServer bootstrapKeycloakServer(String[] args) throws Throwable {
KeycloakServerConfig config = new KeycloakServerConfig();
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java
new file mode 100644
index 0000000..a377d9b
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java
@@ -0,0 +1,82 @@
+package org.keycloak.testutils.ldap;
+
+/**
+ * Factory for ApacheDS based LDAP and Kerberos servers
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class EmbeddedServersFactory {
+
+ private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
+ private static final String DEFAULT_BIND_HOST = "localhost";
+ private static final int DEFAULT_BIND_PORT = 10389;
+ private static final String DEFAULT_LDIF_FILE = "ldap/users.ldif";
+
+ private static final String DEFAULT_KERBEROS_LDIF_FILE = "kerberos/users-kerberos.ldif";
+
+ private static final String DEFAULT_KERBEROS_REALM = "KEYCLOAK.ORG";
+ private static final int DEFAULT_KDC_PORT = 6088;
+
+ private String baseDN;
+ private String bindHost;
+ private int bindPort;
+ private String ldifFile;
+ private String kerberosRealm;
+ private int kdcPort;
+
+
+ public static EmbeddedServersFactory readConfiguration() {
+ EmbeddedServersFactory factory = new EmbeddedServersFactory();
+ factory.readProperties();
+ return factory;
+ }
+
+
+ protected void readProperties() {
+ this.baseDN = System.getProperty("ldap.baseDN");
+ this.bindHost = System.getProperty("ldap.host");
+ String bindPort = System.getProperty("ldap.port");
+ this.ldifFile = System.getProperty("ldap.ldif");
+
+ this.kerberosRealm = System.getProperty("kerberos.realm");
+ String kdcPort = System.getProperty("kerberos.port");
+
+ if (baseDN == null || baseDN.isEmpty()) {
+ baseDN = DEFAULT_BASE_DN;
+ }
+ if (bindHost == null || bindHost.isEmpty()) {
+ bindHost = DEFAULT_BIND_HOST;
+ }
+ this.bindPort = (bindPort == null || bindPort.isEmpty()) ? DEFAULT_BIND_PORT : Integer.parseInt(bindPort);
+ if (ldifFile == null || ldifFile.isEmpty()) {
+ ldifFile = DEFAULT_LDIF_FILE;
+ }
+
+ if (kerberosRealm == null || kerberosRealm.isEmpty()) {
+ kerberosRealm = DEFAULT_KERBEROS_REALM;
+ }
+ this.kdcPort = (kdcPort == null || kdcPort.isEmpty()) ? DEFAULT_KDC_PORT : Integer.parseInt(kdcPort);
+ }
+
+
+ public LDAPEmbeddedServer createLdapServer() {
+
+ // Override LDIF file with default for embedded LDAP
+ if (ldifFile.equals(DEFAULT_KERBEROS_LDIF_FILE)) {
+ ldifFile = DEFAULT_LDIF_FILE;
+ }
+
+ return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile);
+ }
+
+
+ public KerberosEmbeddedServer createKerberosServer() {
+
+ // Override LDIF file with default for embedded Kerberos
+ if (ldifFile.equals(DEFAULT_LDIF_FILE)) {
+ ldifFile = DEFAULT_KERBEROS_LDIF_FILE;
+ }
+
+ return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, kerberosRealm, kdcPort);
+ }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java
new file mode 100644
index 0000000..bba4954
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemoryDirectoryServiceFactory.java
@@ -0,0 +1,144 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.Configuration;
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
+import org.apache.directory.api.ldap.model.schema.LdapComparator;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.model.schema.comparators.NormalizingComparator;
+import org.apache.directory.api.ldap.model.schema.registries.ComparatorRegistry;
+import org.apache.directory.api.ldap.model.schema.registries.SchemaLoader;
+import org.apache.directory.api.ldap.schemaloader.JarLdifSchemaLoader;
+import org.apache.directory.api.ldap.schemamanager.impl.DefaultSchemaManager;
+import org.apache.directory.api.util.exception.Exceptions;
+import org.apache.directory.server.constants.ServerDNConstants;
+import org.apache.directory.server.core.DefaultDirectoryService;
+import org.apache.directory.server.core.api.CacheService;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.api.partition.Partition;
+import org.apache.directory.server.core.api.schema.SchemaPartition;
+import org.apache.directory.server.core.factory.AvlPartitionFactory;
+import org.apache.directory.server.core.factory.DirectoryServiceFactory;
+import org.apache.directory.server.core.factory.PartitionFactory;
+import org.apache.directory.server.i18n.I18n;
+import org.jboss.logging.Logger;
+
+/**
+ * Factory for a fast (mostly in-memory-only) ApacheDS DirectoryService. Use only for tests!!
+ *
+ * @author Josef Cacek
+ */
+class InMemoryDirectoryServiceFactory implements DirectoryServiceFactory {
+
+ private static final Logger log = Logger.getLogger(InMemoryDirectoryServiceFactory.class);
+
+ private final DirectoryService directoryService;
+ private final PartitionFactory partitionFactory;
+
+ /**
+ * Default constructor which creates {@link DefaultDirectoryService} instance and configures {@link AvlPartitionFactory} as
+ * the {@link PartitionFactory} implementation.
+ */
+ public InMemoryDirectoryServiceFactory() {
+ try {
+ directoryService = new DefaultDirectoryService();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ directoryService.setShutdownHookEnabled(false);
+ partitionFactory = new AvlPartitionFactory();
+ }
+
+ /**
+ * Constructor which uses provided {@link DirectoryService} and {@link PartitionFactory} implementations.
+ *
+ * @param directoryService must be not-<code>null</code>
+ * @param partitionFactory must be not-<code>null</code>
+ */
+ public InMemoryDirectoryServiceFactory(DirectoryService directoryService, PartitionFactory partitionFactory) {
+ this.directoryService = directoryService;
+ this.partitionFactory = partitionFactory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void init(String name) throws Exception {
+ if ((directoryService != null) && directoryService.isStarted()) {
+ return;
+ }
+ directoryService.setInstanceId(name);
+
+ // instance layout
+ InstanceLayout instanceLayout = new InstanceLayout(System.getProperty("java.io.tmpdir") + "/server-work-" + name);
+ if (instanceLayout.getInstanceDirectory().exists()) {
+ try {
+ FileUtils.deleteDirectory(instanceLayout.getInstanceDirectory());
+ } catch (IOException e) {
+ log.warn("couldn't delete the instance directory before initializing the DirectoryService", e);
+ }
+ }
+ directoryService.setInstanceLayout(instanceLayout);
+
+ // EhCache in disabled-like-mode
+ Configuration ehCacheConfig = new Configuration();
+ CacheConfiguration defaultCache = new CacheConfiguration("default", 1).eternal(false).timeToIdleSeconds(30)
+ .timeToLiveSeconds(30).overflowToDisk(false);
+ ehCacheConfig.addDefaultCache(defaultCache);
+ CacheService cacheService = new CacheService(new CacheManager(ehCacheConfig));
+ directoryService.setCacheService(cacheService);
+
+ // Init the schema
+ // SchemaLoader loader = new SingleLdifSchemaLoader();
+ SchemaLoader loader = new JarLdifSchemaLoader();
+ SchemaManager schemaManager = new DefaultSchemaManager(loader);
+ schemaManager.loadAllEnabled();
+ ComparatorRegistry comparatorRegistry = schemaManager.getComparatorRegistry();
+ for (LdapComparator<?> comparator : comparatorRegistry) {
+ if (comparator instanceof NormalizingComparator) {
+ ((NormalizingComparator) comparator).setOnServer();
+ }
+ }
+ directoryService.setSchemaManager(schemaManager);
+ InMemorySchemaPartition inMemorySchemaPartition = new InMemorySchemaPartition(schemaManager);
+ SchemaPartition schemaPartition = new SchemaPartition(schemaManager);
+ schemaPartition.setWrappedPartition(inMemorySchemaPartition);
+ directoryService.setSchemaPartition(schemaPartition);
+ List<Throwable> errors = schemaManager.getErrors();
+ if (errors.size() != 0) {
+ throw new Exception(I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)));
+ }
+
+ // Init system partition
+ Partition systemPartition = partitionFactory.createPartition(directoryService.getSchemaManager(),
+ directoryService.getDnFactory(), "system",
+ ServerDNConstants.SYSTEM_DN, 500,
+ new File(directoryService.getInstanceLayout().getPartitionsDirectory(),
+ "system"));
+
+ systemPartition.setSchemaManager(directoryService.getSchemaManager());
+ partitionFactory.addIndex(systemPartition, SchemaConstants.OBJECT_CLASS_AT, 100);
+ directoryService.setSystemPartition(systemPartition);
+ directoryService.startup();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public DirectoryService getDirectoryService() throws Exception {
+ return directoryService;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public PartitionFactory getPartitionFactory() throws Exception {
+ return partitionFactory;
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java
new file mode 100644
index 0000000..a5fed44
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/InMemorySchemaPartition.java
@@ -0,0 +1,75 @@
+package org.keycloak.testutils.ldap;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import javax.naming.InvalidNameException;
+import org.apache.directory.api.ldap.model.constants.SchemaConstants;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.ldif.LdifEntry;
+import org.apache.directory.api.ldap.model.ldif.LdifReader;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor;
+import org.apache.directory.api.ldap.schemaextractor.impl.ResourceMap;
+import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
+import org.apache.directory.server.core.partition.ldif.AbstractLdifPartition;
+import org.jboss.logging.Logger;
+
+/**
+ * In-memory schema-only partition which loads the data in the similar way as the
+ * {@link org.apache.directory.api.ldap.schemaloader.JarLdifSchemaLoader}.
+ *
+ * @author Josef Cacek
+ */
+class InMemorySchemaPartition extends AbstractLdifPartition {
+
+ private static final Logger log = Logger.getLogger(InMemorySchemaPartition.class);
+
+ /**
+ * Filesystem path separator pattern, either forward slash or backslash. java.util.regex.Pattern is immutable so only one
+ * instance is needed for all uses.
+ */
+ public InMemorySchemaPartition(SchemaManager schemaManager) {
+ super(schemaManager);
+ }
+
+ /**
+ * Partition initialization - loads schema entries from the files on classpath.
+ *
+ * @see org.apache.directory.server.core.partition.impl.avl.AvlPartition#doInit()
+ */
+ @Override
+ protected void doInit() throws InvalidNameException, Exception {
+ if (initialized)
+ return;
+ log.debug("Initializing schema partition " + getId());
+ suffixDn.apply(schemaManager);
+ super.doInit();
+
+ // load schema
+ final Map<String, Boolean> resMap = ResourceMap.getResources(Pattern.compile("schema[/\\Q\\\\E]ou=schema.*"));
+ for (String resourcePath : new TreeSet<String>(resMap.keySet())) {
+ if (resourcePath.endsWith(".ldif")) {
+ URL resource = DefaultSchemaLdifExtractor.getUniqueResource(resourcePath, "Schema LDIF file");
+ LdifReader reader = new LdifReader(resource.openStream());
+ LdifEntry ldifEntry = reader.next();
+ reader.close();
+ Entry entry = new DefaultEntry(schemaManager, ldifEntry.getEntry());
+
+ // add mandatory attributes
+ if (entry.get(SchemaConstants.ENTRY_CSN_AT) == null) {
+ entry.add(SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString());
+ }
+ if (entry.get(SchemaConstants.ENTRY_UUID_AT) == null) {
+ entry.add(SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString());
+ }
+ AddOperationContext addContext = new AddOperationContext(null, entry);
+ super.add(addContext);
+ }
+ }
+ }
+
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
new file mode 100644
index 0000000..635c817
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java
@@ -0,0 +1,183 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
+import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
+import org.apache.directory.server.kerberos.KerberosConfig;
+import org.apache.directory.server.kerberos.kdc.KdcServer;
+import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
+import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
+import org.apache.directory.server.protocol.shared.transport.UdpTransport;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.jboss.logging.Logger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
+
+ private static final Logger log = Logger.getLogger(LDAPEmbeddedServer.class);
+
+ private final String kerberosRealm;
+ private final int kdcPort;
+
+ private KdcServer kdcServer;
+
+
+ public static void main(String[] args) throws Exception {
+ EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+ KerberosEmbeddedServer kerberosEmbeddedServer = factory.createKerberosServer();
+ kerberosEmbeddedServer.init();
+ kerberosEmbeddedServer.start();
+ }
+
+
+ protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String kerberosRealm, int kdcPort) {
+ super(baseDN, bindHost, bindPort, ldifFile);
+ this.kerberosRealm = kerberosRealm;
+ this.kdcPort = kdcPort;
+ }
+
+
+ @Override
+ public void init() throws Exception {
+ super.init();
+
+ log.info("Creating KDC server");
+ createAndStartKdcServer();
+ }
+
+
+ @Override
+ protected DirectoryService createDirectoryService() throws Exception {
+ DirectoryService directoryService = super.createDirectoryService();
+
+ directoryService.addLast(new KeyDerivationInterceptor());
+ return directoryService;
+ }
+
+
+ @Override
+ protected LdapServer createLdapServer() {
+ LdapServer ldapServer = super.createLdapServer();
+
+ ldapServer.setSaslHost( this.bindHost );
+ ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@" + this.kerberosRealm);
+
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler());
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.CRAM_MD5, new CramMd5MechanismHandler());
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.DIGEST_MD5, new DigestMd5MechanismHandler());
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.GSSAPI, new GssapiMechanismHandler());
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.NTLM, new NtlmMechanismHandler());
+ ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.GSS_SPNEGO, new NtlmMechanismHandler());
+
+ return ldapServer;
+ }
+
+
+ protected KdcServer createAndStartKdcServer() throws Exception {
+ KerberosConfig kdcConfig = new KerberosConfig();
+ kdcConfig.setServicePrincipal("krbtgt/" + this.kerberosRealm + "@" + this.kerberosRealm);
+ kdcConfig.setPrimaryRealm(this.kerberosRealm);
+ kdcConfig.setMaximumTicketLifetime(60000 * 1440);
+ kdcConfig.setMaximumRenewableLifetime(60000 * 10080);
+ kdcConfig.setPaEncTimestampRequired(false);
+
+ kdcServer = new NoReplayKdcServer(kdcConfig);
+ kdcServer.setSearchBaseDn(this.baseDN);
+
+ UdpTransport udp = new UdpTransport(this.bindHost, this.kdcPort);
+ kdcServer.addTransports(udp);
+
+ kdcServer.setDirectoryService(directoryService);
+
+ // Launch the server
+ kdcServer.start();
+
+ return kdcServer;
+ }
+
+
+ public void stop() throws Exception {
+ stopLdapServer();
+ stopKerberosServer();
+ shutdownDirectoryService();
+ }
+
+
+ protected void stopKerberosServer() {
+ log.info("Stoping Kerberos server.");
+ kdcServer.stop();
+ }
+
+
+ /**
+ * Replacement of apacheDS KdcServer class with disabled ticket replay cache.
+ *
+ * @author Dominik Pospisil <dpospisi@redhat.com>
+ */
+ class NoReplayKdcServer extends KdcServer {
+
+ NoReplayKdcServer(KerberosConfig kdcConfig) {
+ super(kdcConfig);
+ }
+
+ /**
+ *
+ * Dummy implementation of the ApacheDS kerberos replay cache. Essentially disables kerbores ticket replay checks.
+ * https://issues.jboss.org/browse/JBPAPP-10974
+ *
+ * @author Dominik Pospisil <dpospisi@redhat.com>
+ */
+ private class DummyReplayCache implements ReplayCache {
+
+ @Override
+ public boolean isReplay(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+ int clientMicroSeconds) {
+ return false;
+ }
+
+ @Override
+ public void save(KerberosPrincipal serverPrincipal, KerberosPrincipal clientPrincipal, KerberosTime clientTime,
+ int clientMicroSeconds) {
+ return;
+ }
+
+ @Override
+ public void clear() {
+ return;
+ }
+
+ }
+
+ /**
+ * @throws java.io.IOException if we cannot bind to the sockets
+ */
+ @Override
+ public void start() throws IOException, LdapInvalidDnException {
+ super.start();
+
+ try {
+
+ // override initialized replay cache with a dummy implementation
+ Field replayCacheField = KdcServer.class.getDeclaredField("replayCache");
+ replayCacheField.setAccessible(true);
+ replayCacheField.set(this, new DummyReplayCache());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+ }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java
new file mode 100644
index 0000000..aa26153
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosKeytabCreator.java
@@ -0,0 +1,70 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
+import org.apache.directory.shared.kerberos.components.EncryptionKey;
+
+/**
+ * Helper utility for creating Keytab files.
+ *
+ * @author Josef Cacek
+ */
+public class KerberosKeytabCreator {
+
+ // Public methods --------------------------------------------------------
+
+ /**
+ * The main.
+ *
+ * @param args
+ * @throws java.io.IOException
+ */
+ public static void main(String[] args) throws IOException {
+ if (args == null || args.length != 3) {
+ System.out.println("Kerberos keytab generator");
+ System.out.println("-------------------------");
+ System.out.println("Arguments missing or invalid. Required arguments are: <principalName> <passPhrase> <outputKeytabFile>");
+ System.out.println("Example of usage:");
+ System.out.println("mvn exec:java -Dexec.mainClass=\"org.keycloak.testutils.ldap.KerberosKeytabCreator\" -Dexec.args=\"HTTP/localhost@KEYCLOAK.ORG httppwd src/main/resources/kerberos/http.keytab\"");
+ } else {
+ final File keytabFile = new File(args[2]);
+ createKeytab(args[0], args[1], keytabFile);
+ System.out.println("Keytab file was created: " + keytabFile.getAbsolutePath() + ", principal: " + args[0] + ", passphrase: " + args[1]);
+ }
+ }
+
+ /**
+ * Creates a keytab file for given principal.
+ *
+ * @param principalName
+ * @param passPhrase
+ * @param keytabFile
+ * @throws IOException
+ */
+ public static void createKeytab(final String principalName, final String passPhrase, final File keytabFile)
+ throws IOException {
+ final KerberosTime timeStamp = new KerberosTime();
+ final int principalType = 1; // KRB5_NT_PRINCIPAL
+
+ final Keytab keytab = Keytab.getInstance();
+ final List<KeytabEntry> entries = new ArrayList<KeytabEntry>();
+ for (Map.Entry<EncryptionType, EncryptionKey> keyEntry : KerberosKeyFactory.getKerberosKeys(principalName, passPhrase)
+ .entrySet()) {
+ System.out.println("Adding keytab entry of type: " + keyEntry.getKey().getName());
+ final EncryptionKey key = keyEntry.getValue();
+ final byte keyVersion = (byte) key.getKeyVersion();
+ entries.add(new KeytabEntry(principalName, principalType, timeStamp, keyVersion, key));
+ }
+ keytab.setEntries(entries);
+ keytab.write(keytabFile);
+ }
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
new file mode 100644
index 0000000..9c18288
--- /dev/null
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java
@@ -0,0 +1,186 @@
+package org.keycloak.testutils.ldap;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
+import org.apache.directory.api.ldap.model.ldif.LdifEntry;
+import org.apache.directory.api.ldap.model.ldif.LdifReader;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.partition.Partition;
+import org.apache.directory.server.core.factory.DSAnnotationProcessor;
+import org.apache.directory.server.core.factory.PartitionFactory;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+import org.apache.directory.server.protocol.shared.transport.Transport;
+import org.jboss.logging.Logger;
+import org.keycloak.util.StreamUtil;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPEmbeddedServer {
+
+ private static final Logger log = Logger.getLogger(LDAPEmbeddedServer.class);
+
+ protected final String baseDN;
+ protected final String bindHost;
+ protected final int bindPort;
+ protected final String ldifFile;
+
+ protected DirectoryService directoryService;
+ protected LdapServer ldapServer;
+
+
+ public static void main(String[] args) throws Exception {
+ EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+ LDAPEmbeddedServer ldapEmbeddedServer = factory.createLdapServer();
+ ldapEmbeddedServer.init();
+ ldapEmbeddedServer.start();
+ }
+
+ public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile) {
+ this.baseDN = baseDN;
+ this.bindHost = bindHost;
+ this.bindPort = bindPort;
+ this.ldifFile = ldifFile;
+ }
+
+
+ public void init() throws Exception {
+ log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort);
+ this.directoryService = createDirectoryService();
+
+ log.info("Importing LDIF: " + ldifFile);
+ importLdif();
+
+ log.info("Creating LDAP Server");
+ this.ldapServer = createLdapServer();
+ }
+
+
+ public void start() throws Exception {
+ log.info("Starting LDAP Server");
+ ldapServer.start();
+ log.info("LDAP Server started");
+ }
+
+
+ protected DirectoryService createDirectoryService() throws Exception {
+ // Parse "keycloak" from "dc=keycloak,dc=org"
+ String dcName = baseDN.split(",")[0].substring(3);
+
+ InMemoryDirectoryServiceFactory dsf = new InMemoryDirectoryServiceFactory();
+
+ DirectoryService service = dsf.getDirectoryService();
+ service.setAccessControlEnabled(false);
+ service.setAllowAnonymousAccess(false);
+ service.getChangeLog().setEnabled(false);
+
+ dsf.init(dcName + "DS");
+
+ SchemaManager schemaManager = service.getSchemaManager();
+
+ PartitionFactory partitionFactory = dsf.getPartitionFactory();
+ Partition partition = partitionFactory.createPartition(
+ schemaManager,
+ service.getDnFactory(),
+ dcName,
+ this.baseDN,
+ 1000,
+ new File(service.getInstanceLayout().getPartitionsDirectory(), dcName));
+ partition.setCacheService( service.getCacheService() );
+ partition.initialize();
+
+ partition.setSchemaManager( schemaManager );
+
+ // Inject the partition into the DirectoryService
+ service.addPartition( partition );
+
+ // Last, process the context entry
+ String entryLdif =
+ "dn: " + baseDN + "\n" +
+ "dc: " + dcName + "\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n";
+ DSAnnotationProcessor.injectEntries(service, entryLdif);
+
+ return service;
+ }
+
+
+ protected LdapServer createLdapServer() {
+ LdapServer ldapServer = new LdapServer();
+
+ ldapServer.setServiceName("DefaultLdapServer");
+
+ // Read the transports
+ Transport ldap = new TcpTransport(this.bindHost, this.bindPort, 3, 50);
+ ldapServer.addTransports( ldap );
+
+ // Associate the DS to this LdapServer
+ ldapServer.setDirectoryService( directoryService );
+
+ // Propagate the anonymous flag to the DS
+ directoryService.setAllowAnonymousAccess(false);
+
+ ldapServer.setSaslHost( this.bindHost );
+ ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@KEYCLOAK.ORG");
+ ldapServer.setSaslRealms(new ArrayList<String>());
+ return ldapServer;
+ }
+
+
+ private void importLdif() throws Exception {
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("hostname", this.bindHost);
+
+ // For now, assume that LDIF file is on classpath
+ InputStream is = getClass().getClassLoader().getResourceAsStream(ldifFile);
+ if (is == null) {
+ throw new IllegalStateException("LDIF file not found on classpath. Location was: " + ldifFile);
+ }
+
+ final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map);
+ log.info("Importing LDIF: " + ldifContent);
+ final SchemaManager schemaManager = directoryService.getSchemaManager();
+
+ for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(ldifContent))) {
+ try {
+ directoryService.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry()));
+ } catch (LdapEntryAlreadyExistsException ignore) {
+ log.debug("Entry " + ldifEntry.getNewRdn() + " already exists. Ignoring");
+ }
+ }
+ }
+
+
+ public void stop() throws Exception {
+ stopLdapServer();
+ shutdownDirectoryService();
+ }
+
+
+ protected void stopLdapServer() {
+ log.info("Stoping LDAP server.");
+ ldapServer.stop();
+ }
+
+
+ protected void shutdownDirectoryService() throws Exception {
+ log.info("Stoping Directory service.");
+ directoryService.shutdown();
+
+ log.info("Removing Directory service workfiles.");
+ FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory());
+ }
+
+}
diff --git a/testsuite/integration/src/main/resources/kerberos/http.keytab b/testsuite/integration/src/main/resources/kerberos/http.keytab
new file mode 100644
index 0000000..c156500
Binary files /dev/null and b/testsuite/integration/src/main/resources/kerberos/http.keytab differ
diff --git a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif
new file mode 100644
index 0000000..fcde10e
--- /dev/null
+++ b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif
@@ -0,0 +1,88 @@
+dn: dc=keycloak,dc=org
+objectclass: dcObject
+objectclass: organization
+o: Keycloak
+dc: Keycloak
+
+dn: ou=People,dc=keycloak,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: People
+
+dn: uid=krbtgt,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: KDC Service
+sn: Service
+uid: krbtgt
+userPassword: secret
+krb5PrincipalName: krbtgt/KEYCLOAK.ORG@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=ldap,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: LDAP
+sn: Service
+uid: ldap
+userPassword: randall
+krb5PrincipalName: ldap/${hostname}@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=HTTP,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: HTTP
+sn: Service
+uid: HTTP
+userPassword: httppwd
+krb5PrincipalName: HTTP/${hostname}@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=hnelson,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Horatio Nelson
+sn: Nelson
+uid: hnelson
+userPassword: secret
+krb5PrincipalName: hnelson@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=jduke,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: Java Duke
+sn: duke
+uid: jduke
+userPassword: theduke
+krb5PrincipalName: jduke@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
+
+dn: uid=gsstestserver,ou=People,dc=keycloak,dc=org
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: krb5principal
+objectClass: krb5kdcentry
+cn: gsstestserver
+sn: Service
+uid: gsstestserver
+userPassword: gsstestpwd
+krb5PrincipalName: gsstestserver/xxx@KEYCLOAK.ORG
+krb5KeyVersionNumber: 0
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
index 573c238..5e78b31 100755
--- a/testsuite/integration/src/main/resources/log4j.properties
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -19,6 +19,11 @@ log4j.logger.org.keycloak=info
# Enable to view kerberos/spnego logging
# log4j.logger.org.keycloak.broker.kerberos=trace
+# Enable to view detailed AS REQ and TGS REQ requests to embedded Kerberos server
+log4j.logger.org.apache.directory.server.kerberos=debug
+
log4j.logger.org.xnio=off
log4j.logger.org.hibernate=off
-log4j.logger.org.jboss.resteasy=warn
\ No newline at end of file
+log4j.logger.org.jboss.resteasy=warn
+log4j.logger.org.apache.directory.api=warn
+log4j.logger.org.apache.directory.server.core=warn
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 1146257..833e56c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -33,7 +33,6 @@ import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.testutils.LDAPEmbeddedServer;
import org.openqa.selenium.WebDriver;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.model.basic.User;
@@ -56,8 +55,7 @@ public class FederationProvidersIntegrationTest {
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
- LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
- Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+ Map<String,String> ldapConfig = ldapRule.getLdapConfig();
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
index f256ee0..917a544 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
@@ -22,7 +22,6 @@ import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testutils.DummyUserFederationProviderFactory;
-import org.keycloak.testutils.LDAPEmbeddedServer;
import org.keycloak.timer.TimerProvider;
import org.keycloak.util.Time;
import org.picketlink.idm.PartitionManager;
@@ -49,8 +48,7 @@ public class SyncProvidersTest {
// Other tests may left Time offset uncleared, which could cause issues
Time.setOffset(0);
- LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
- Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+ Map<String,String> ldapConfig = ldapRule.getLdapConfig();
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
index 1c631bb..5b12037 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java
@@ -1,37 +1,46 @@
package org.keycloak.testsuite.rule;
+import java.util.Map;
+
import org.junit.rules.ExternalResource;
-import org.keycloak.testutils.LDAPEmbeddedServer;
+import org.keycloak.testutils.ldap.EmbeddedServersFactory;
+import org.keycloak.testutils.ldap.LDAPConfiguration;
+import org.keycloak.testutils.ldap.LDAPEmbeddedServer;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPRule extends ExternalResource {
- private LDAPEmbeddedServer embeddedServer;
+ private LDAPConfiguration ldapConfiguration;
+ private LDAPEmbeddedServer ldapEmbeddedServer;
@Override
protected void before() throws Throwable {
- try {
- embeddedServer = new LDAPEmbeddedServer();
- embeddedServer.setup();
- embeddedServer.importLDIF("ldap/users.ldif");
- } catch (Exception e) {
- throw new RuntimeException("Error starting Embedded LDAP server.", e);
+ ldapConfiguration = LDAPConfiguration.readConfiguration();
+
+ if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
+ EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
+ ldapEmbeddedServer = factory.createLdapServer();
+ ldapEmbeddedServer.init();
+ ldapEmbeddedServer.start();
}
}
@Override
protected void after() {
try {
- embeddedServer.tearDown();
- embeddedServer = null;
+ if (ldapEmbeddedServer != null) {
+ ldapEmbeddedServer.stop();
+ ldapEmbeddedServer = null;
+ ldapConfiguration = null;
+ }
} catch (Exception e) {
throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
}
}
- public LDAPEmbeddedServer getEmbeddedServer() {
- return embeddedServer;
+ public Map<String, String> getLdapConfig() {
+ return ldapConfiguration.getLDAPConfig();
}
}