LDAP testing improvements. Support for embedded Kerberos server in testsuite

This commit is contained in:
mposolda 2015-02-13 21:05:17 +01:00
parent 6097ecb53f
commit 5da05aa62a
18 changed files with 976 additions and 169 deletions

36
pom.xml
View file

@ -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>
@ -307,17 +308,6 @@
<artifactId>picketlink-impl</artifactId>
<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>
@ -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>

View file

@ -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;

View file

@ -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

View file

@ -113,10 +113,6 @@
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
@ -212,15 +208,31 @@
<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>
@ -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>

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import org.keycloak.models.LDAPConstants;
/**
* Forked from Picketlink project
*
* Abstract base for all LDAP test suites. It handles
* @author Peter Skopek: pskopek at redhat dot com
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPEmbeddedServer extends AbstractLDAPTest {
public class LDAPConfiguration {
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_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "idm.test.ldap.user.account.controls.after.password.update";
public LDAPEmbeddedServer() {
super();
loadConnectionProperties();
public static LDAPConfiguration readConfiguration() {
LDAPConfiguration ldapConfiguration = new LDAPConfiguration();
ldapConfiguration.loadConnectionProperties();
return ldapConfiguration;
}
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));
}
@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() {
Map<String,String> ldapConfig = new HashMap<String,String>();
ldapConfig.put(LDAPConstants.CONNECTION_URL, getConnectionUrl());
@ -153,37 +99,6 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
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() {
return connectionUrl;
}
@ -247,18 +162,4 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
public boolean isUserAccountControlsAfterPasswordUpdate() {
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");
}
}

View file

@ -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());
}
}

View file

@ -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

View file

@ -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
log4j.logger.org.jboss.resteasy=warn
log4j.logger.org.apache.directory.api=warn
log4j.logger.org.apache.directory.server.core=warn

View file

@ -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());

View file

@ -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());

View file

@ -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();
}
}