Merge pull request #977 from mposolda/master
LDAP testing improvements. Support for embedded Kerberos server in testsuite
This commit is contained in:
commit
8f5010e71a
26 changed files with 1035 additions and 179 deletions
|
@ -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.MapperContext;
|
||||||
import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
||||||
import org.keycloak.connections.mongo.impl.types.BasicDBListMapper;
|
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.BasicDBObjectMapper;
|
||||||
import org.keycloak.connections.mongo.impl.types.BasicDBObjectToMapMapper;
|
import org.keycloak.connections.mongo.impl.types.BasicDBObjectToMapMapper;
|
||||||
import org.keycloak.connections.mongo.impl.types.EnumToStringMapper;
|
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.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
@ -71,6 +74,10 @@ public class MongoStoreImpl implements MongoStore {
|
||||||
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
|
mapperRegistry.addAppObjectMapper(new ListMapper(mapperRegistry, List.class));
|
||||||
mapperRegistry.addDBObjectMapper(new BasicDBListMapper(mapperRegistry));
|
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(HashMap.class));
|
||||||
mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
|
mapperRegistry.addAppObjectMapper(new MapMapper(Map.class));
|
||||||
mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
|
mapperRegistry.addDBObjectMapper(new BasicDBObjectToMapMapper());
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.connections.mongo.api.types.MapperContext;
|
||||||
import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.MapperContext;
|
||||||
import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
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>
|
* @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 MapperRegistry mapperRegistry;
|
||||||
private final Class<T> listType;
|
private final Class<T> listType;
|
||||||
|
|
|
@ -27,11 +27,9 @@ public class MongoRealmProvider implements RealmProvider {
|
||||||
|
|
||||||
private final MongoStoreInvocationContext invocationContext;
|
private final MongoStoreInvocationContext invocationContext;
|
||||||
private final KeycloakSession session;
|
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.session = session;
|
||||||
this.mongoStore = mongoStore;
|
|
||||||
this.invocationContext = invocationContext;
|
this.invocationContext = invocationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class MongoRealmProviderFactory implements RealmProviderFactory {
|
||||||
@Override
|
@Override
|
||||||
public RealmProvider create(KeycloakSession session) {
|
public RealmProvider create(KeycloakSession session) {
|
||||||
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
|
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
|
||||||
return new MongoRealmProvider(session, connection.getMongoStore(), connection.getInvocationContext());
|
return new MongoRealmProvider(session, connection.getInvocationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -33,11 +33,9 @@ public class MongoUserProvider implements UserProvider {
|
||||||
|
|
||||||
private final MongoStoreInvocationContext invocationContext;
|
private final MongoStoreInvocationContext invocationContext;
|
||||||
private final KeycloakSession session;
|
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.session = session;
|
||||||
this.mongoStore = mongoStore;
|
|
||||||
this.invocationContext = invocationContext;
|
this.invocationContext = invocationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,6 +309,7 @@ public class MongoUserProvider implements UserProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||||
|
federatedUser = getUserById(federatedUser.getId(), realm);
|
||||||
MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
|
MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
|
||||||
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
|
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class MongoUserProviderFactory implements UserProviderFactory {
|
||||||
@Override
|
@Override
|
||||||
public UserProvider create(KeycloakSession session) {
|
public UserProvider create(KeycloakSession session) {
|
||||||
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
|
MongoConnectionProvider connection = session.getProvider(MongoConnectionProvider.class);
|
||||||
return new MongoUserProvider(session, connection.getMongoStore(), connection.getInvocationContext());
|
return new MongoUserProvider(session, connection.getInvocationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
36
pom.xml
36
pom.xml
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<aesh.version>0.33.12</aesh.version>
|
<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>
|
<base64.version>2.3.8</base64.version>
|
||||||
<bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
|
<bouncycastle.crypto.version>1.50</bouncycastle.crypto.version>
|
||||||
<bouncycastle.mail.version>1.50</bouncycastle.mail.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.0.Final</undertow.version> -->
|
||||||
<undertow.version>1.1.1.Final</undertow.version>
|
<undertow.version>1.1.1.Final</undertow.version>
|
||||||
<picketlink.version>2.7.0.CR3</picketlink.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>
|
<mongo.driver.version>2.11.3</mongo.driver.version>
|
||||||
<jboss.logging.version>3.1.4.GA</jboss.logging.version>
|
<jboss.logging.version>3.1.4.GA</jboss.logging.version>
|
||||||
<syslog4j.version>0.9.30</syslog4j.version>
|
<syslog4j.version>0.9.30</syslog4j.version>
|
||||||
|
@ -307,17 +308,6 @@
|
||||||
<artifactId>picketlink-impl</artifactId>
|
<artifactId>picketlink-impl</artifactId>
|
||||||
<version>${picketlink.version}</version>
|
<version>${picketlink.version}</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
@ -422,6 +412,28 @@
|
||||||
<version>${winzipaes.version}</version>
|
<version>${winzipaes.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Selenium -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
|
|
@ -27,11 +27,10 @@ public class LDAPConnectionTestManager {
|
||||||
try {
|
try {
|
||||||
Hashtable<String, Object> env = new Hashtable<String, Object>();
|
Hashtable<String, Object> env = new Hashtable<String, Object>();
|
||||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
|
||||||
|
|
||||||
env.put(Context.PROVIDER_URL, connectionUrl);
|
env.put(Context.PROVIDER_URL, connectionUrl);
|
||||||
|
|
||||||
if (TEST_AUTHENTICATION.equals(action)) {
|
if (TEST_AUTHENTICATION.equals(action)) {
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, "simple");
|
||||||
env.put(Context.SECURITY_PRINCIPAL, bindDn);
|
env.put(Context.SECURITY_PRINCIPAL, bindDn);
|
||||||
|
|
||||||
char[] bindCredentialChar = null;
|
char[] bindCredentialChar = null;
|
||||||
|
|
|
@ -91,3 +91,33 @@ 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
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -113,10 +113,6 @@
|
||||||
<groupId>com.google.zxing</groupId>
|
<groupId>com.google.zxing</groupId>
|
||||||
<artifactId>javase</artifactId>
|
<artifactId>javase</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
|
@ -212,15 +208,31 @@
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
<artifactId>selenium-chrome-driver</artifactId>
|
<artifactId>selenium-chrome-driver</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache DS -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.picketbox</groupId>
|
<groupId>org.apache.directory.server</groupId>
|
||||||
<artifactId>picketbox-ldap</artifactId>
|
<artifactId>apacheds-core-annotations</artifactId>
|
||||||
<type>test-jar</type>
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.picketbox</groupId>
|
<groupId>org.apache.directory.server</groupId>
|
||||||
<artifactId>picketbox-ldap</artifactId>
|
<artifactId>apacheds-interceptor-kerberos</artifactId>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.picketlink</groupId>
|
<groupId>org.picketlink</groupId>
|
||||||
<artifactId>picketlink-wildfly-common</artifactId>
|
<artifactId>picketlink-wildfly-common</artifactId>
|
||||||
|
@ -270,6 +282,12 @@
|
||||||
<workingDirectory>${project.basedir}</workingDirectory>
|
<workingDirectory>${project.basedir}</workingDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<inherited>true</inherited>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -316,6 +334,34 @@
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</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>
|
<profile>
|
||||||
<id>jpa</id>
|
<id>jpa</id>
|
||||||
|
|
|
@ -104,27 +104,8 @@ public class KeycloakServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
//bootstrapLdap(); Can't seem to get this to work.
|
|
||||||
bootstrapKeycloakServer(args);
|
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 {
|
public static KeycloakServer bootstrapKeycloakServer(String[] args) throws Throwable {
|
||||||
KeycloakServerConfig config = new KeycloakServerConfig();
|
KeycloakServerConfig config = new KeycloakServerConfig();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,16 @@
|
||||||
package org.keycloak.testutils;
|
package org.keycloak.testutils.ldap;
|
||||||
|
|
||||||
import org.keycloak.models.LDAPConstants;
|
|
||||||
import org.picketbox.test.ldap.AbstractLDAPTest;
|
|
||||||
|
|
||||||
import javax.naming.CompositeName;
|
|
||||||
import javax.naming.Context;
|
|
||||||
import javax.naming.ContextNotEmptyException;
|
|
||||||
import javax.naming.Name;
|
|
||||||
import javax.naming.NameClassPair;
|
|
||||||
import javax.naming.NamingEnumeration;
|
|
||||||
import javax.naming.NamingException;
|
|
||||||
import javax.naming.directory.DirContext;
|
|
||||||
import javax.naming.directory.InitialDirContext;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forked from Picketlink project
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*
|
|
||||||
* Abstract base for all LDAP test suites. It handles
|
|
||||||
* @author Peter Skopek: pskopek at redhat dot com
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
public class LDAPConfiguration {
|
||||||
|
|
||||||
public static final String CONNECTION_PROPERTIES = "ldap/ldap-connection.properties";
|
public static final String CONNECTION_PROPERTIES = "ldap/ldap-connection.properties";
|
||||||
|
|
||||||
|
@ -64,10 +48,10 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
||||||
public static String IDM_TEST_LDAP_USER_OBJECT_CLASSES = "idm.test.ldap.user.object.classes";
|
public static String IDM_TEST_LDAP_USER_OBJECT_CLASSES = "idm.test.ldap.user.object.classes";
|
||||||
public static String IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "idm.test.ldap.user.account.controls.after.password.update";
|
public static String IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "idm.test.ldap.user.account.controls.after.password.update";
|
||||||
|
|
||||||
|
public static LDAPConfiguration readConfiguration() {
|
||||||
public LDAPEmbeddedServer() {
|
LDAPConfiguration ldapConfiguration = new LDAPConfiguration();
|
||||||
super();
|
ldapConfiguration.loadConnectionProperties();
|
||||||
loadConnectionProperties();
|
return ldapConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadConnectionProperties() {
|
protected void loadConnectionProperties() {
|
||||||
|
@ -98,44 +82,6 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
||||||
userAccountControlsAfterPasswordUpdate = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE));
|
userAccountControlsAfterPasswordUpdate = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setup() throws Exception {
|
|
||||||
// suppress emb. LDAP server start
|
|
||||||
if (isStartEmbeddedLdapLerver()) {
|
|
||||||
// On Windows, the directory may not be fully deleted from previous test
|
|
||||||
String tempDir = System.getProperty("java.io.tmpdir");
|
|
||||||
File workDir = new File(tempDir + File.separator + "server-work");
|
|
||||||
if (workDir.exists()) {
|
|
||||||
recursiveDeleteDir(workDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.setup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
// suppress emb. LDAP server stop
|
|
||||||
if (isStartEmbeddedLdapLerver()) {
|
|
||||||
|
|
||||||
// clear data left in LDAP
|
|
||||||
DirContext ctx = getDirContext();
|
|
||||||
clearSubContexts(ctx, new CompositeName(baseDn));
|
|
||||||
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DirContext getDirContext() throws NamingException {
|
|
||||||
Hashtable<String, String> env = new Hashtable<String, String>();
|
|
||||||
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
|
||||||
env.put(Context.PROVIDER_URL, connectionUrl);
|
|
||||||
env.put(Context.SECURITY_PRINCIPAL, bindDn);
|
|
||||||
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
|
|
||||||
DirContext ctx = new InitialDirContext(env);
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String,String> getLDAPConfig() {
|
public Map<String,String> getLDAPConfig() {
|
||||||
Map<String,String> ldapConfig = new HashMap<String,String>();
|
Map<String,String> ldapConfig = new HashMap<String,String>();
|
||||||
ldapConfig.put(LDAPConstants.CONNECTION_URL, getConnectionUrl());
|
ldapConfig.put(LDAPConstants.CONNECTION_URL, getConnectionUrl());
|
||||||
|
@ -153,37 +99,6 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
||||||
return ldapConfig;
|
return ldapConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void clearSubContexts(DirContext ctx, Name name) throws NamingException {
|
|
||||||
|
|
||||||
NamingEnumeration<NameClassPair> enumeration = null;
|
|
||||||
try {
|
|
||||||
enumeration = ctx.list(name);
|
|
||||||
while (enumeration.hasMore()) {
|
|
||||||
NameClassPair pair = enumeration.next();
|
|
||||||
Name childName = ctx.composeName(new CompositeName(pair.getName()), name);
|
|
||||||
try {
|
|
||||||
ctx.destroySubcontext(childName);
|
|
||||||
}
|
|
||||||
catch (ContextNotEmptyException e) {
|
|
||||||
clearSubContexts(ctx, childName);
|
|
||||||
ctx.destroySubcontext(childName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (NamingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
enumeration.close();
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// Never mind this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getConnectionUrl() {
|
public String getConnectionUrl() {
|
||||||
return connectionUrl;
|
return connectionUrl;
|
||||||
}
|
}
|
||||||
|
@ -247,18 +162,4 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
||||||
public boolean isUserAccountControlsAfterPasswordUpdate() {
|
public boolean isUserAccountControlsAfterPasswordUpdate() {
|
||||||
return userAccountControlsAfterPasswordUpdate;
|
return userAccountControlsAfterPasswordUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importLDIF(String fileName) throws Exception {
|
|
||||||
// import LDIF only in case we are running against embedded LDAP server
|
|
||||||
if (isStartEmbeddedLdapLerver()) {
|
|
||||||
super.importLDIF(fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void createBaseDN() throws Exception {
|
|
||||||
ds.createBaseDN("keycloak", "dc=keycloak,dc=org");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
testsuite/integration/src/main/resources/kerberos/http.keytab
Normal file
BIN
testsuite/integration/src/main/resources/kerberos/http.keytab
Normal file
Binary file not shown.
|
@ -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
|
|
@ -19,6 +19,11 @@ log4j.logger.org.keycloak=info
|
||||||
# Enable to view kerberos/spnego logging
|
# Enable to view kerberos/spnego logging
|
||||||
# log4j.logger.org.keycloak.broker.kerberos=trace
|
# 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.xnio=off
|
||||||
log4j.logger.org.hibernate=off
|
log4j.logger.org.hibernate=off
|
||||||
log4j.logger.org.jboss.resteasy=warn
|
log4j.logger.org.jboss.resteasy=warn
|
||||||
|
log4j.logger.org.apache.directory.api=warn
|
||||||
|
log4j.logger.org.apache.directory.server.core=warn
|
|
@ -33,7 +33,6 @@ import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.LDAPRule;
|
import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.keycloak.testutils.LDAPEmbeddedServer;
|
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.picketlink.idm.PartitionManager;
|
import org.picketlink.idm.PartitionManager;
|
||||||
import org.picketlink.idm.model.basic.User;
|
import org.picketlink.idm.model.basic.User;
|
||||||
|
@ -56,8 +55,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
|
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
|
||||||
|
|
||||||
LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
|
Map<String,String> ldapConfig = ldapRule.getLdapConfig();
|
||||||
Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
|
|
||||||
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
|
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
|
||||||
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.keycloak.services.managers.UsersSyncManager;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.LDAPRule;
|
import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
import org.keycloak.testutils.DummyUserFederationProviderFactory;
|
import org.keycloak.testutils.DummyUserFederationProviderFactory;
|
||||||
import org.keycloak.testutils.LDAPEmbeddedServer;
|
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
import org.picketlink.idm.PartitionManager;
|
import org.picketlink.idm.PartitionManager;
|
||||||
|
@ -49,8 +48,7 @@ public class SyncProvidersTest {
|
||||||
// Other tests may left Time offset uncleared, which could cause issues
|
// Other tests may left Time offset uncleared, which could cause issues
|
||||||
Time.setOffset(0);
|
Time.setOffset(0);
|
||||||
|
|
||||||
LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
|
Map<String,String> ldapConfig = ldapRule.getLdapConfig();
|
||||||
Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
|
|
||||||
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
|
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
|
||||||
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
|
ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,46 @@
|
||||||
package org.keycloak.testsuite.rule;
|
package org.keycloak.testsuite.rule;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.rules.ExternalResource;
|
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>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class LDAPRule extends ExternalResource {
|
public class LDAPRule extends ExternalResource {
|
||||||
|
|
||||||
private LDAPEmbeddedServer embeddedServer;
|
private LDAPConfiguration ldapConfiguration;
|
||||||
|
private LDAPEmbeddedServer ldapEmbeddedServer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void before() throws Throwable {
|
protected void before() throws Throwable {
|
||||||
try {
|
ldapConfiguration = LDAPConfiguration.readConfiguration();
|
||||||
embeddedServer = new LDAPEmbeddedServer();
|
|
||||||
embeddedServer.setup();
|
if (ldapConfiguration.isStartEmbeddedLdapLerver()) {
|
||||||
embeddedServer.importLDIF("ldap/users.ldif");
|
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
|
||||||
} catch (Exception e) {
|
ldapEmbeddedServer = factory.createLdapServer();
|
||||||
throw new RuntimeException("Error starting Embedded LDAP server.", e);
|
ldapEmbeddedServer.init();
|
||||||
|
ldapEmbeddedServer.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void after() {
|
protected void after() {
|
||||||
try {
|
try {
|
||||||
embeddedServer.tearDown();
|
if (ldapEmbeddedServer != null) {
|
||||||
embeddedServer = null;
|
ldapEmbeddedServer.stop();
|
||||||
|
ldapEmbeddedServer = null;
|
||||||
|
ldapConfiguration = null;
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
|
throw new RuntimeException("Error tearDown Embedded LDAP server.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LDAPEmbeddedServer getEmbeddedServer() {
|
public Map<String, String> getLdapConfig() {
|
||||||
return embeddedServer;
|
return ldapConfiguration.getLDAPConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue