Merge pull request #1428 from mposolda/master
Embedded LDAP for examples. LDAP sync bugfixing
This commit is contained in:
commit
f3aeb5ec9f
44 changed files with 1555 additions and 533 deletions
|
@ -89,13 +89,6 @@ public class BasicDBObjectMapper<S> implements Mapper<BasicDBObject, S> {
|
|||
Type[] genericTypeArguments = parameterized.getActualTypeArguments();
|
||||
|
||||
List<Type> genericTypes = Arrays.asList(genericTypeArguments);
|
||||
/*for (Type genericType : genericTypeArguments) {
|
||||
if (genericType instanceof Class) {
|
||||
genericTypes.add((Class<?>) genericType);
|
||||
} else {
|
||||
System.out.println("foo");
|
||||
}
|
||||
}*/
|
||||
|
||||
Class<?> expectedReturnType = (Class<?>)parameterized.getRawType();
|
||||
context = new MapperContext<Object, Object>(valueFromDB, expectedReturnType, genericTypes);
|
||||
|
|
|
@ -19,7 +19,7 @@ cp http.keytab /tmp/http.keytab
|
|||
```
|
||||
|
||||
Alternative is to configure different location for `keyTab` property in `kerberosrealm.json` configuration file (On Windows this will be needed).
|
||||
Note that in production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
|
||||
WARNING: In production, keytab file should be in secured location accessible just to the user under which is Keycloak server running.
|
||||
|
||||
|
||||
**3)** Run Keycloak server and import `kerberosrealm.json` into it through admin console. This will import realm with sample application
|
||||
|
@ -37,12 +37,13 @@ Also if you are on Linux, make sure that record like:
|
|||
```
|
||||
is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid issues related to incompatible reverse lookup (Ensure the similar for other OS as well)
|
||||
|
||||
**4)** Install kerberos client. This is platform dependent. If you are on Fedora, Ubuntu or RHEL, you can install package `freeipa-client`, which contains Kerberos client and bunch of other stuff.
|
||||
|
||||
**4)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm and enable `forwardable` flag, which is needed
|
||||
**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed
|
||||
for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
|
||||
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
|
||||
|
||||
**5)** Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer
|
||||
**6)** Run ApacheDS based Kerberos server embedded in Keycloak. Easiest is to checkout keycloak sources, build and then run KerberosEmbeddedServer
|
||||
as shown here:
|
||||
|
||||
```
|
||||
|
@ -55,12 +56,12 @@ mvn exec:java -Pkerberos
|
|||
More details about embedded Kerberos server in [testsuite README](https://github.com/keycloak/keycloak/blob/master/misc/Testsuite.md#kerberos-server).
|
||||
|
||||
|
||||
**6)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` .
|
||||
**7)** Configure browser (Firefox, Chrome or other) and enable SPNEGO authentication and credential delegation for `localhost` .
|
||||
In Firefox it can be done by adding `localhost` to both `network.negotiate-auth.trusted-uris` and `network.negotiate-auth.delegation-uris` .
|
||||
More info in [testsuite README](https://github.com/keycloak/keycloak/blob/master/misc/Testsuite.md#kerberos-server).
|
||||
|
||||
|
||||
**7)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
|
||||
**8)** Test the example. Obtain kerberos ticket by running command from CMD (on linux):
|
||||
```
|
||||
kinit hnelson@KEYCLOAK.ORG
|
||||
```
|
||||
|
|
0
examples/kerberos/users.ldif
Normal file
0
examples/kerberos/users.ldif
Normal file
21
examples/ldap/embedded-ldap/assembly.xml
Normal file
21
examples/ldap/embedded-ldap/assembly.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<assembly>
|
||||
<id>embedded-ldap</id>
|
||||
|
||||
<formats>
|
||||
<format>dir</format>
|
||||
</formats>
|
||||
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<unpack>false</unpack>
|
||||
<useTransitiveDependencies>true</useTransitiveDependencies>
|
||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
||||
<includes>
|
||||
<include>org.keycloak:keycloak-util-embedded-ldap</include>
|
||||
<include>org.slf4j:slf4j-log4j12</include>
|
||||
</includes>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
82
examples/ldap/embedded-ldap/pom.xml
Normal file
82
examples/ldap/embedded-ldap/pom.xml
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-ldap-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>keycloak-examples-embedded-ldap</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>LDAP Demo Application</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-util-embedded-ldap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>embedded-ldap</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<outputDirectory>
|
||||
target
|
||||
</outputDirectory>
|
||||
<workDirectory>
|
||||
target/assembly/work
|
||||
</workDirectory>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>org.keycloak.example.ldap.embedded.EmbeddedLDAPLauncher</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,127 @@
|
|||
package org.keycloak.example.ldap.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This is supposed to be executed from JAR file (java -jar target/embedded-ldap.jar ). For executing from IDE or Maven use directly
|
||||
* the proper class (LDAPEmbeddedServer, KerberosEmbeddedServer or KerberosKeytabCreator)
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class EmbeddedLDAPLauncher {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String arg = args.length == 0 ? null : args[0];
|
||||
if (arg == null) {
|
||||
System.err.println("Missing argument: either 'kerberos', 'ldap' or 'keytabCreator' must be passed as argument");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String clazz = null;
|
||||
File home = getHome();
|
||||
Properties defaultProperties = new Properties();
|
||||
if (arg.equalsIgnoreCase("ldap")) {
|
||||
|
||||
clazz = "org.keycloak.util.ldap.LDAPEmbeddedServer";
|
||||
File ldapLdif = file(home, "..", "ldap-app", "users.ldif");
|
||||
defaultProperties.put("ldap.ldif", ldapLdif.getAbsolutePath());
|
||||
} else if (arg.equalsIgnoreCase("kerberos")) {
|
||||
|
||||
clazz = "org.keycloak.util.ldap.KerberosEmbeddedServer";
|
||||
File kerberosLdif = file(home, "..", "..", "kerberos", "users.ldif");
|
||||
defaultProperties.put("ldap.ldif", kerberosLdif.getAbsolutePath());
|
||||
} else if (arg.equalsIgnoreCase("keytabCreator")) {
|
||||
|
||||
clazz = "org.keycloak.util.ldap.KerberosKeytabCreator";
|
||||
} else {
|
||||
|
||||
System.err.println("Invalid argument: '" + arg + "' . Either 'kerberos', 'ldap' or 'keytabCreator' must be passed as argument");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Remove first argument
|
||||
String[] newArgs = new String[args.length - 1];
|
||||
for (int i=0 ; i<(args.length - 1) ; i++) {
|
||||
newArgs[i] = args[i + 1];
|
||||
}
|
||||
|
||||
System.out.println("Executing " + clazz);
|
||||
runClass(clazz, newArgs, defaultProperties);
|
||||
}
|
||||
|
||||
|
||||
private static void runClass(String className, String[] args, Properties defaultProperties) throws Exception {
|
||||
File home = getHome();
|
||||
File lib = file(home, "target", "embedded-ldap");
|
||||
|
||||
if (!lib.exists()) {
|
||||
System.err.println("Could not find lib directory: " + lib.toString());
|
||||
System.exit(1);
|
||||
} else {
|
||||
System.out.println("Found directory to load jars: " + lib.getAbsolutePath());
|
||||
}
|
||||
|
||||
List<URL> jars = new ArrayList<URL>();
|
||||
for (File file : lib.listFiles()) {
|
||||
jars.add(file.toURI().toURL());
|
||||
}
|
||||
URL[] urls = jars.toArray(new URL[jars.size()]);
|
||||
URLClassLoader loader = new URLClassLoader(urls, EmbeddedLDAPLauncher.class.getClassLoader());
|
||||
|
||||
Class mainClass = loader.loadClass(className);
|
||||
Method executeMethod = null;
|
||||
for (Method m : mainClass.getMethods()) if (m.getName().equals("execute")) { executeMethod = m; break; }
|
||||
Object obj = args;
|
||||
executeMethod.invoke(null, obj, defaultProperties);
|
||||
}
|
||||
|
||||
|
||||
private static File getHome() {
|
||||
String launcherPath = EmbeddedLDAPLauncher.class.getName().replace('.', '/') + ".class";
|
||||
URL jarfile = EmbeddedLDAPLauncher.class.getClassLoader().getResource(launcherPath);
|
||||
if (jarfile != null) {
|
||||
Matcher m = Pattern.compile("jar:(file:.*)!/" + launcherPath).matcher(jarfile.toString());
|
||||
if (m.matches()) {
|
||||
try {
|
||||
File jarPath = new File(new URI(m.group(1)));
|
||||
File libPath = jarPath.getParentFile().getParentFile();
|
||||
System.out.println("Home directory: " + libPath.toString());
|
||||
if (!libPath.exists()) {
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
return libPath;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.err.println("jar file null: " + launcherPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static File file(File home, String... pathItems) {
|
||||
File current = home;
|
||||
|
||||
for (String item : pathItems) {
|
||||
if (item.equals("..")) {
|
||||
current = current.getParentFile();
|
||||
} else {
|
||||
current = new File(current, item);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
68
examples/ldap/ldap-app/pom.xml
Normal file
68
examples/ldap/ldap-app/pom.xml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-ldap-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>keycloak-examples-ldap-app</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>LDAP Demo Application</name>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss</id>
|
||||
<name>jboss repo</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>ldap-portal</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
0
examples/ldap/ldap-app/users.ldif
Normal file
0
examples/ldap/ldap-app/users.ldif
Normal file
20
examples/ldap/pom.xml
Normal file
20
examples/ldap/pom.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
<name>Keycloak LDAP Examples - Parent</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-examples-ldap-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>embedded-ldap</module>
|
||||
<module>ldap-app</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
|
@ -44,5 +44,6 @@
|
|||
<module>kerberos</module>
|
||||
<module>themes</module>
|
||||
<module>saml</module>
|
||||
<module>ldap</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
@ -13,7 +13,9 @@ import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
|||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
@ -26,6 +28,7 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||
import org.keycloak.models.UserFederationSyncResult;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -176,7 +179,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
for (LDAPObject ldapUser : ldapUsers) {
|
||||
String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
|
||||
if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
|
||||
UserModel imported = importUserFromLDAP(realm, ldapUser);
|
||||
UserModel imported = importUserFromLDAP(session, realm, ldapUser);
|
||||
searchResults.add(imported);
|
||||
}
|
||||
}
|
||||
|
@ -249,10 +252,10 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
return importUserFromLDAP(realm, ldapUser);
|
||||
return importUserFromLDAP(session, realm, ldapUser);
|
||||
}
|
||||
|
||||
protected UserModel importUserFromLDAP(RealmModel realm, LDAPObject ldapUser) {
|
||||
protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) {
|
||||
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
|
||||
|
||||
if (ldapUsername == null) {
|
||||
|
@ -298,7 +301,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
return importUserFromLDAP(realm, ldapUser);
|
||||
return importUserFromLDAP(session, realm, ldapUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -383,38 +386,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
|||
public void close() {
|
||||
}
|
||||
|
||||
protected UserFederationSyncResult importLDAPUsers(RealmModel realm, List<LDAPObject> ldapUsers, UserFederationProviderModel fedModel) {
|
||||
UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||
|
||||
for (LDAPObject ldapUser : ldapUsers) {
|
||||
String username = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
|
||||
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
|
||||
|
||||
if (currentUser == null) {
|
||||
// Add new user to Keycloak
|
||||
importUserFromLDAP(realm, ldapUser);
|
||||
syncResult.increaseAdded();
|
||||
} else {
|
||||
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
|
||||
|
||||
// Update keycloak user
|
||||
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
|
||||
for (UserFederationMapperModel mapperModel : federationMappers) {
|
||||
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
|
||||
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false);
|
||||
}
|
||||
|
||||
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
||||
syncResult.increaseUpdated();
|
||||
} else {
|
||||
logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after successful kerberos authentication
|
||||
*
|
||||
|
|
|
@ -14,12 +14,15 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
|
|||
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
|
||||
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
|
||||
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
|
||||
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationEventAwareProviderFactory;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
|
@ -94,7 +97,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
// CN is typically used as RDN for Active Directory deployments
|
||||
|
@ -107,7 +111,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
} else {
|
||||
|
@ -118,14 +123,16 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
} else {
|
||||
|
||||
|
@ -141,7 +148,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
|
@ -149,14 +157,16 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
||||
|
@ -167,7 +177,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
|
||||
// map modifyTimeStamp as read-only
|
||||
|
@ -175,7 +186,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP);
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
|
@ -226,29 +238,14 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
userQuery.setLimit(pageSize);
|
||||
final List<LDAPObject> users = userQuery.getResultList();
|
||||
nextPage = userQuery.getPaginationContext() != null;
|
||||
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
UserFederationSyncResult currentPageSync = importLdapUsers(session, realmId, fedModel, users);
|
||||
syncResult.add(currentPageSync);
|
||||
}
|
||||
|
||||
});
|
||||
UserFederationSyncResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
|
||||
syncResult.add(currentPageSync);
|
||||
}
|
||||
} else {
|
||||
// LDAP pagination not available. Do everything in single transaction
|
||||
final List<LDAPObject> users = userQuery.getResultList();
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
UserFederationSyncResult currentSync = importLdapUsers(session, realmId, fedModel, users);
|
||||
syncResult.add(currentSync);
|
||||
}
|
||||
|
||||
});
|
||||
UserFederationSyncResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
|
||||
syncResult.add(currentSync);
|
||||
}
|
||||
|
||||
return syncResult;
|
||||
|
@ -273,11 +270,81 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
return queryHolder.query;
|
||||
}
|
||||
|
||||
protected UserFederationSyncResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
|
||||
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
|
||||
|
||||
protected UserFederationSyncResult importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
||||
return ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
|
||||
class BooleanHolder {
|
||||
private boolean value = true;
|
||||
}
|
||||
final BooleanHolder exists = new BooleanHolder();
|
||||
|
||||
for (final LDAPObject ldapUser : ldapUsers) {
|
||||
|
||||
try {
|
||||
|
||||
// Process each user in it's own transaction to avoid global fail
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
||||
RealmModel currentRealm = session.realms().getRealm(realmId);
|
||||
|
||||
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
|
||||
UserModel currentUser = session.userStorage().getUserByUsername(username, currentRealm);
|
||||
|
||||
if (currentUser == null) {
|
||||
|
||||
// Add new user to Keycloak
|
||||
exists.value = false;
|
||||
ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser);
|
||||
syncResult.increaseAdded();
|
||||
|
||||
} else {
|
||||
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
|
||||
|
||||
// Update keycloak user
|
||||
Set<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
|
||||
for (UserFederationMapperModel mapperModel : federationMappers) {
|
||||
LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
|
||||
ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
|
||||
}
|
||||
|
||||
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
||||
syncResult.increaseUpdated();
|
||||
} else {
|
||||
logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getDisplayName());
|
||||
syncResult.increaseFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} catch (ModelException me) {
|
||||
logger.error("Failed during import user from LDAP", me);
|
||||
syncResult.increaseFailed();
|
||||
|
||||
// Remove user if we already added him during this transaction
|
||||
if (!exists.value) {
|
||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
||||
RealmModel currentRealm = session.realms().getRealm(realmId);
|
||||
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
|
||||
UserModel existing = session.userStorage().getUserByUsername(username, currentRealm);
|
||||
if (existing != null) {
|
||||
session.userStorage().removeUser(currentRealm, existing);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.federation.ldap.idm.store.ldap;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -437,18 +438,26 @@ public class LDAPIdentityStore implements IdentityStore {
|
|||
|
||||
// ldapObject.getReadOnlyAttributeNames() are lower-cased
|
||||
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName.toLowerCase()) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
|
||||
BasicAttribute attr = new BasicAttribute(attrName);
|
||||
|
||||
if (attrValue == null) {
|
||||
// Adding empty value as we don't know if attribute is mandatory in LDAP
|
||||
attr.add(LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
for (String val : attrValue) {
|
||||
if (val == null || val.toString().trim().length() == 0) {
|
||||
val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
|
||||
}
|
||||
attr.add(val);
|
||||
}
|
||||
// Shouldn't happen
|
||||
logger.warnf("Attribute '%s' is null on LDAP object '%s' . Using empty value to be saved to LDAP", attrName, ldapObject.getDn().toString());
|
||||
attrValue = Collections.emptySet();
|
||||
}
|
||||
|
||||
// Ignore empty attributes during create
|
||||
if (isCreate && attrValue.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicAttribute attr = new BasicAttribute(attrName);
|
||||
for (String val : attrValue) {
|
||||
if (val == null || val.toString().trim().length() == 0) {
|
||||
val = LDAPConstants.EMPTY_ATTRIBUTE_VALUE;
|
||||
}
|
||||
attr.add(val);
|
||||
}
|
||||
|
||||
entryAttributes.put(attr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
|
||||
memberships.remove(ldapUser.getDn().toString());
|
||||
|
||||
// Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But on active directory! (Empty membership is not allowed here)
|
||||
// Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But not on active directory! (Empty membership is not allowed here)
|
||||
if (memberships.size() == 0 && !ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
|
||||
memberships.add(LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.federation.ldap.mappers;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -15,6 +16,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
|||
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
|
@ -58,6 +60,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
public static final String LDAP_ATTRIBUTE = "ldap.attribute";
|
||||
public static final String READ_ONLY = "read.only";
|
||||
public static final String ALWAYS_READ_VALUE_FROM_LDAP = "always.read.value.from.ldap";
|
||||
public static final String IS_MANDATORY_IN_LDAP = "is.mandatory.in.ldap";
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -88,6 +91,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||
String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP);
|
||||
|
||||
Property<Object> userModelProperty = userModelProperties.get(userModelAttrName.toLowerCase());
|
||||
|
||||
|
@ -95,15 +99,27 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
|
||||
// we have java property on UserModel. Assuming we support just properties of simple types
|
||||
Object attrValue = userModelProperty.getValue(localUser);
|
||||
String valueAsString = (attrValue == null) ? null : attrValue.toString();
|
||||
ldapUser.setSingleAttribute(ldapAttrName, valueAsString);
|
||||
|
||||
if (attrValue == null) {
|
||||
if (isMandatoryInLdap) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
|
||||
}
|
||||
} else {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, attrValue.toString());
|
||||
}
|
||||
} else {
|
||||
|
||||
// we don't have java property. Let's set attribute
|
||||
List<String> attrValues = localUser.getAttribute(userModelAttrName);
|
||||
|
||||
if (attrValues.size() == 0) {
|
||||
ldapUser.setAttribute(ldapAttrName, null);
|
||||
if (isMandatoryInLdap) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
|
||||
}
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(attrValues));
|
||||
}
|
||||
|
@ -119,6 +135,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
final String userModelAttrName = mapperModel.getConfig().get(USER_MODEL_ATTRIBUTE);
|
||||
final String ldapAttrName = mapperModel.getConfig().get(LDAP_ATTRIBUTE);
|
||||
boolean isAlwaysReadValueFromLDAP = parseBooleanParameter(mapperModel, ALWAYS_READ_VALUE_FROM_LDAP);
|
||||
final boolean isMandatoryInLdap = parseBooleanParameter(mapperModel, IS_MANDATORY_IN_LDAP);
|
||||
|
||||
// For writable mode, we want to propagate writing of attribute to LDAP as well
|
||||
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && !isReadOnly(mapperModel)) {
|
||||
|
@ -170,12 +187,20 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
ensureTransactionStarted();
|
||||
|
||||
if (value == null) {
|
||||
ldapUser.setAttribute(ldapAttrName, null);
|
||||
if (isMandatoryInLdap) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<String>());
|
||||
}
|
||||
} else if (value instanceof String) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, (String) value);
|
||||
} else {
|
||||
List<String> asList = (List<String>) value;
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(asList));
|
||||
if (asList.isEmpty() && isMandatoryInLdap) {
|
||||
ldapUser.setSingleAttribute(ldapAttrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
|
||||
} else {
|
||||
ldapUser.setAttribute(ldapAttrName, new LinkedHashSet<>(asList));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +228,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
|
|||
if (name.equalsIgnoreCase(userModelAttrName)) {
|
||||
Collection<String> ldapAttrValue = ldapUser.getAttributeAsSet(ldapAttrName);
|
||||
if (ldapAttrValue == null) {
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return new ArrayList<>(ldapAttrValue);
|
||||
}
|
||||
|
|
|
@ -31,9 +31,13 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
|
|||
"Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, "false");
|
||||
configProperties.add(readOnly);
|
||||
|
||||
ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always read value from LDAP",
|
||||
ProviderConfigProperty alwaysReadValueFromLDAP = createConfigProperty(UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "Always Read Value From LDAP",
|
||||
"If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB", ProviderConfigProperty.BOOLEAN_TYPE, "false");
|
||||
configProperties.add(alwaysReadValueFromLDAP);
|
||||
|
||||
ProviderConfigProperty isMandatoryInLdap = createConfigProperty(UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "Is Mandatory In LDAP",
|
||||
"If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP", ProviderConfigProperty.BOOLEAN_TYPE, "false");
|
||||
configProperties.add(isMandatoryInLdap);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -128,7 +128,7 @@ public class UserFederationManager implements UserProvider {
|
|||
if (link != null) {
|
||||
UserModel validatedProxyUser = link.validateAndProxy(realm, user);
|
||||
if (validatedProxyUser != null) {
|
||||
managedUsers.put(user.getId(), user);
|
||||
managedUsers.put(user.getId(), validatedProxyUser);
|
||||
return validatedProxyUser;
|
||||
} else {
|
||||
deleteInvalidUser(realm, user);
|
||||
|
|
|
@ -8,6 +8,7 @@ public class UserFederationSyncResult {
|
|||
private int added;
|
||||
private int updated;
|
||||
private int removed;
|
||||
private int failed;
|
||||
|
||||
public int getAdded() {
|
||||
return added;
|
||||
|
@ -33,6 +34,14 @@ public class UserFederationSyncResult {
|
|||
this.removed = removed;
|
||||
}
|
||||
|
||||
public int getFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public void setFailed(int failed) {
|
||||
this.failed = failed;
|
||||
}
|
||||
|
||||
public void increaseAdded() {
|
||||
added++;
|
||||
}
|
||||
|
@ -45,14 +54,23 @@ public class UserFederationSyncResult {
|
|||
removed++;
|
||||
}
|
||||
|
||||
public void increaseFailed() {
|
||||
failed++;
|
||||
}
|
||||
|
||||
public void add(UserFederationSyncResult other) {
|
||||
added += other.added;
|
||||
updated += other.updated;
|
||||
removed += other.removed;
|
||||
failed += other.failed;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
|
||||
String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
|
||||
if (failed != 0) {
|
||||
status += String.format(", %d users failed sync! See server log for more details", failed);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
11
pom.xml
11
pom.xml
|
@ -155,6 +155,7 @@
|
|||
<module>testsuite</module>
|
||||
<module>timer</module>
|
||||
<module>export-import</module>
|
||||
<module>util</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -431,25 +432,21 @@
|
|||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-annotations</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-interceptor-kerberos</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-annotations</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-codec-standalone</artifactId>
|
||||
<version>${apacheds.codec.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Selenium -->
|
||||
|
@ -1120,6 +1117,11 @@
|
|||
<version>${project.version}</version>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-util-embedded-ldap</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-docs-dist</artifactId>
|
||||
|
@ -1184,6 +1186,7 @@
|
|||
<version>${project.version}</version>
|
||||
<classifier>classes</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>federation-properties-example</artifactId>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.services;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
|
||||
|
@ -11,6 +12,8 @@ import java.util.List;
|
|||
*/
|
||||
public class DefaultKeycloakTransactionManager implements KeycloakTransactionManager {
|
||||
|
||||
public static final Logger logger = Logger.getLogger(DefaultKeycloakTransactionManager.class);
|
||||
|
||||
private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
|
||||
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
||||
private boolean active;
|
||||
|
@ -57,13 +60,26 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
|||
exception = exception == null ? e : exception;
|
||||
}
|
||||
}
|
||||
for (KeycloakTransaction tx : afterCompletion) {
|
||||
try {
|
||||
tx.commit();
|
||||
} catch (RuntimeException e) {
|
||||
exception = exception == null ? e : exception;
|
||||
|
||||
// Don't commit "afterCompletion" if commit of some main transaction failed
|
||||
if (exception == null) {
|
||||
for (KeycloakTransaction tx : afterCompletion) {
|
||||
try {
|
||||
tx.commit();
|
||||
} catch (RuntimeException e) {
|
||||
exception = exception == null ? e : exception;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (KeycloakTransaction tx : afterCompletion) {
|
||||
try {
|
||||
tx.rollback();
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Exception during rollback", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active = false;
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
|
|
|
@ -29,7 +29,13 @@ public class AdminMessagesProvider implements MessagesProvider {
|
|||
@Override
|
||||
public String getMessage(String messageKey, Object... parameters) {
|
||||
String message = messagesBundle.getProperty(messageKey, messageKey);
|
||||
return new MessageFormat(message, locale).format(parameters);
|
||||
|
||||
try {
|
||||
return new MessageFormat(message, locale).format(parameters);
|
||||
} catch (Exception e) {
|
||||
logger.warnf("Failed to format message due to: %s", e.getMessage());
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -190,40 +190,8 @@
|
|||
|
||||
<!-- Apache DS -->
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-annotations</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.jdbm</groupId>
|
||||
<artifactId>apacheds-jdbm1</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-interceptor-kerberos</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-annotations</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.jdbm</groupId>
|
||||
<artifactId>apacheds-jdbm1</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-codec-standalone</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-util-embedded-ldap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -344,7 +312,7 @@
|
|||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>org.keycloak.testsuite.ldap.LDAPEmbeddedServer</mainClass>
|
||||
<mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
|
||||
<classpathScope>test</classpathScope>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
@ -359,7 +327,7 @@
|
|||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>org.keycloak.testsuite.ldap.KerberosEmbeddedServer</mainClass>
|
||||
<mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
|
||||
<classpathScope>test</classpathScope>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -68,10 +68,10 @@ public class FederationProvidersIntegrationTest {
|
|||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
|
||||
|
||||
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
|
||||
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
|
||||
|
||||
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", "5678");
|
||||
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -270,7 +270,7 @@ public class FederationProvidersIntegrationTest {
|
|||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", "12398");
|
||||
LDAPObject johnZip = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398");
|
||||
|
||||
// Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity
|
||||
UserFederationMapperModel currentZipMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "zipCodeMapper");
|
||||
|
@ -295,7 +295,7 @@ public class FederationProvidersIntegrationTest {
|
|||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", "12399");
|
||||
LDAPObject johnDirect = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399");
|
||||
|
||||
// Fetch user from LDAP and check that postalCode is filled
|
||||
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
|
@ -307,9 +307,18 @@ public class FederationProvidersIntegrationTest {
|
|||
johnDirect.setSingleAttribute(LDAPConstants.SN, "DirectLDAPUpdated");
|
||||
ldapFedProvider.getLdapIdentityStore().update(johnDirect);
|
||||
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
|
||||
// Verify that postalCode is still the same as we read it's value from Keycloak DB
|
||||
user = session.users().getUserByUsername("johndirect", appRealm);
|
||||
postalCode = user.getFirstAttribute("postal_code");
|
||||
String postalCode = user.getFirstAttribute("postal_code");
|
||||
Assert.assertEquals("12399", postalCode);
|
||||
|
||||
// Check user.getAttributes()
|
||||
|
@ -370,7 +379,7 @@ public class FederationProvidersIntegrationTest {
|
|||
|
||||
// Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", "4578");
|
||||
FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
|
||||
|
||||
// add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
|
||||
firstNameMapper = appRealm.getUserFederationMapperByName(ldapModel.getId(), "first name");
|
||||
|
@ -381,9 +390,6 @@ public class FederationProvidersIntegrationTest {
|
|||
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
|
||||
appRealm.addUserFederationMapper(fullNameMapperModel);
|
||||
|
||||
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
||||
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
@ -392,6 +398,9 @@ public class FederationProvidersIntegrationTest {
|
|||
try {
|
||||
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
|
||||
|
||||
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
||||
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
|
||||
|
||||
// Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
|
||||
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
|
||||
session.users().removeUser(appRealm, fullnameUser);
|
||||
|
@ -485,10 +494,10 @@ public class FederationProvidersIntegrationTest {
|
|||
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||
LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", "121");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", "122");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", "123");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", "124");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
|
||||
FederationTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
|
||||
|
||||
// Users are not at local store at this moment
|
||||
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
|||
class FederationTestUtils {
|
||||
|
||||
public static UserModel addLocalUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
|
||||
UserModel user = session.users().addUser(realm, username);
|
||||
UserModel user = session.userStorage().addUser(realm, username);
|
||||
user.setEmail(email);
|
||||
user.setEnabled(true);
|
||||
|
||||
|
@ -47,7 +47,7 @@ class FederationTestUtils {
|
|||
}
|
||||
|
||||
public static LDAPObject addLDAPUser(LDAPFederationProvider ldapProvider, RealmModel realm, final String username,
|
||||
final String firstName, final String lastName, final String email, final String postalCode) {
|
||||
final String firstName, final String lastName, final String email, final String street, final String... postalCode) {
|
||||
UserModel helperUser = new UserModelDelegate(null) {
|
||||
|
||||
@Override
|
||||
|
@ -72,8 +72,10 @@ class FederationTestUtils {
|
|||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
if ("postal_code".equals(name)) {
|
||||
if ("postal_code".equals(name) && postalCode != null && postalCode.length > 0) {
|
||||
return Arrays.asList(postalCode);
|
||||
} else if ("street".equals(name) && street != null) {
|
||||
return Arrays.asList(street);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -105,7 +107,8 @@ class FederationTestUtils {
|
|||
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
|
||||
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false",
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
|
||||
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
|
||||
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.keycloak.testsuite.federation;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -17,6 +20,7 @@ import org.junit.runners.MethodSorters;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
|
@ -60,6 +64,19 @@ public class LDAPMultipleAttributesTest {
|
|||
FederationTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
|
||||
FederationTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
|
||||
|
||||
// Remove current users and add default users
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
|
||||
|
||||
LDAPObject james = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", null, "88441");
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(james, "password");
|
||||
|
||||
// User for testing duplicating surname and postalCode
|
||||
LDAPObject bruce = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "bwilson", "Bruce", "Wilson", "bwilson@keycloak.org", "Elm 5", "88441", "77332");
|
||||
bruce.setAttribute("sn", new LinkedHashSet<>(Arrays.asList("Wilson", "Schneider")));
|
||||
ldapFedProvider.getLdapIdentityStore().update(bruce);
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(bruce, "password");
|
||||
|
||||
// Create ldap-portal client
|
||||
ClientModel ldapClient = appRealm.addClient("ldap-portal");
|
||||
ldapClient.addRedirectUri("/ldap-portal");
|
||||
|
|
|
@ -76,13 +76,13 @@ public class LDAPRoleMappingsTest {
|
|||
FederationTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper");
|
||||
|
||||
// Add some users for testing
|
||||
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", "1234");
|
||||
LDAPObject john = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
|
||||
|
||||
LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", "5678");
|
||||
LDAPObject mary = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(mary, "Password1");
|
||||
|
||||
LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", "8910");
|
||||
LDAPObject rob = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(rob, "Password1");
|
||||
|
||||
// Add some roles for testing
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
package org.keycloak.testsuite.federation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
|
@ -61,7 +61,7 @@ public class SyncProvidersTest {
|
|||
FederationTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
|
||||
|
||||
for (int i=1 ; i<=5 ; i++) {
|
||||
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", "12" + i);
|
||||
LDAPObject ldapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org", null, "12" + i);
|
||||
ldapFedProvider.getLdapIdentityStore().updatePassword(ldapUser, "Password1");
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ public class SyncProvidersTest {
|
|||
// }
|
||||
|
||||
@Test
|
||||
public void testLDAPSync() {
|
||||
public void test01LDAPSync() {
|
||||
UsersSyncManager usersSyncManager = new UsersSyncManager();
|
||||
|
||||
// wait a bit
|
||||
|
@ -91,7 +91,7 @@ public class SyncProvidersTest {
|
|||
try {
|
||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
UserFederationSyncResult syncResult = usersSyncManager.syncAllUsers(sessionFactory, "test", ldapModel);
|
||||
assertSyncEquals(syncResult, 5, 0, 0);
|
||||
assertSyncEquals(syncResult, 5, 0, 0, 0);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ public class SyncProvidersTest {
|
|||
|
||||
// Add user to LDAP and update 'user5' in LDAP
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", "126");
|
||||
FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org", null, "126");
|
||||
LDAPObject ldapUser5 = ldapFedProvider.loadLDAPUserByUsername(testRealm, "user5");
|
||||
// NOTE: Changing LDAP attributes directly here
|
||||
ldapUser5.setSingleAttribute(LDAPConstants.EMAIL, "user5Updated@email.org");
|
||||
|
@ -137,7 +137,7 @@ public class SyncProvidersTest {
|
|||
// Trigger partial sync
|
||||
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
|
||||
UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, "test", ldapModel);
|
||||
assertSyncEquals(syncResult, 1, 1, 0);
|
||||
assertSyncEquals(syncResult, 1, 1, 0, 0);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, false);
|
||||
}
|
||||
|
@ -154,6 +154,67 @@ public class SyncProvidersTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02duplicateUsernameSync() {
|
||||
LDAPObject duplicatedLdapUser;
|
||||
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel testRealm = session.realms().getRealm("test");
|
||||
|
||||
FederationTestUtils.addLocalUser(session, testRealm, "user7", "user7@email.org", "password");
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
|
||||
// Add user to LDAP with duplicated username "user7"
|
||||
duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7", "User7FN", "User7LN", "user7-something@email.org", null, "126");
|
||||
|
||||
// Add user to LDAP with duplicated email "user7@email.org"
|
||||
//FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel testRealm = session.realms().getRealm("test");
|
||||
|
||||
// Assert syncing from LDAP fails due to duplicated username
|
||||
UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
|
||||
Assert.assertEquals(1, result.getFailed());
|
||||
|
||||
// Remove "user7" from LDAP
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
ldapFedProvider.getLdapIdentityStore().remove(duplicatedLdapUser);
|
||||
|
||||
// Add user to LDAP with duplicated email "user7@email.org"
|
||||
duplicatedLdapUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user7-something", "User7FNN", "User7LNL", "user7@email.org", null, "126");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
try {
|
||||
RealmModel testRealm = session.realms().getRealm("test");
|
||||
|
||||
// Assert syncing from LDAP fails due to duplicated email
|
||||
UserFederationSyncResult result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
|
||||
Assert.assertEquals(1, result.getFailed());
|
||||
Assert.assertNull(session.userStorage().getUserByUsername("user7-something", testRealm));
|
||||
|
||||
// Update LDAP user to avoid duplicated email
|
||||
duplicatedLdapUser.setSingleAttribute(LDAPConstants.EMAIL, "user7-changed@email.org");
|
||||
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
|
||||
ldapFedProvider.getLdapIdentityStore().update(duplicatedLdapUser);
|
||||
|
||||
// Assert user successfully synced now
|
||||
result = new UsersSyncManager().syncAllUsers(session.getKeycloakSessionFactory(), "test", ldapModel);
|
||||
Assert.assertEquals(0, result.getFailed());
|
||||
FederationTestUtils.assertUserImported(session.userStorage(), testRealm, "user7-something", "User7FNN", "User7LNL", "user7-changed@email.org", "126");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPeriodicSync() {
|
||||
KeycloakSession session = keycloakRule.startSession();
|
||||
|
@ -193,9 +254,10 @@ public class SyncProvidersTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved) {
|
||||
private void assertSyncEquals(UserFederationSyncResult syncResult, int expectedAdded, int expectedUpdated, int expectedRemoved, int expectedFailed) {
|
||||
Assert.assertEquals(syncResult.getAdded(), expectedAdded);
|
||||
Assert.assertEquals(syncResult.getUpdated(), expectedUpdated);
|
||||
Assert.assertEquals(syncResult.getRemoved(), expectedRemoved);
|
||||
Assert.assertEquals(syncResult.getFailed(), expectedFailed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.keycloak.util.KerberosSerializationUtils;
|
||||
import sun.security.jgss.GSSNameImpl;
|
||||
import sun.security.jgss.krb5.Krb5NameElement;
|
||||
|
||||
/**
|
||||
* 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 static final String DEFAULT_KDC_ENCRYPTION_TYPES = "aes128-cts-hmac-sha1-96, des-cbc-md5, des3-cbc-sha1-kd";
|
||||
|
||||
private String baseDN;
|
||||
private String bindHost;
|
||||
private int bindPort;
|
||||
private String ldapSaslPrincipal;
|
||||
private String ldifFile;
|
||||
private String kerberosRealm;
|
||||
private int kdcPort;
|
||||
private String kdcEncryptionTypes;
|
||||
|
||||
|
||||
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.ldapSaslPrincipal = System.getProperty("ldap.saslPrincipal");
|
||||
|
||||
this.kerberosRealm = System.getProperty("kerberos.realm");
|
||||
String kdcPort = System.getProperty("kerberos.port");
|
||||
this.kdcEncryptionTypes = System.getProperty("kerberos.encTypes");
|
||||
|
||||
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);
|
||||
if (kdcEncryptionTypes == null || kdcEncryptionTypes.isEmpty()) {
|
||||
kdcEncryptionTypes = DEFAULT_KDC_ENCRYPTION_TYPES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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, ldapSaslPrincipal);
|
||||
}
|
||||
|
||||
|
||||
public KerberosEmbeddedServer createKerberosServer() {
|
||||
|
||||
// Override LDIF file with default for embedded Kerberos
|
||||
if (ldifFile.equals(DEFAULT_LDIF_FILE)) {
|
||||
ldifFile = DEFAULT_KERBEROS_LDIF_FILE;
|
||||
}
|
||||
|
||||
// Init ldap sasl principal just when creating kerberos server
|
||||
if (ldapSaslPrincipal == null || ldapSaslPrincipal.isEmpty()) {
|
||||
try {
|
||||
// Same algorithm like sun.security.krb5.PrincipalName constructor
|
||||
GSSName gssName = GSSManager.getInstance().createName("ldap@" + bindHost, GSSName.NT_HOSTBASED_SERVICE);
|
||||
GSSNameImpl gssName1 = (GSSNameImpl) gssName;
|
||||
Krb5NameElement krb5NameElement = (Krb5NameElement) gssName1.getElement(KerberosSerializationUtils.KRB5_OID);
|
||||
this.ldapSaslPrincipal = krb5NameElement.getKrb5PrincipalName().toString();
|
||||
} catch (GSSException uhe) {
|
||||
throw new RuntimeException(uhe);
|
||||
}
|
||||
}
|
||||
|
||||
return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal, kerberosRealm, kdcPort, kdcEncryptionTypes);
|
||||
}
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
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 final String ldapSaslPrincipal;
|
||||
|
||||
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, String ldapSaslPrincipal) {
|
||||
this.baseDN = baseDN;
|
||||
this.bindHost = bindHost;
|
||||
this.bindPort = bindPort;
|
||||
this.ldifFile = ldifFile;
|
||||
this.ldapSaslPrincipal = ldapSaslPrincipal;
|
||||
}
|
||||
|
||||
|
||||
public void init() throws Exception {
|
||||
log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort +
|
||||
", ldapSaslPrincipal=" + ldapSaslPrincipal);
|
||||
|
||||
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");
|
||||
ldapServer.setSearchBaseDn(this.baseDN);
|
||||
|
||||
// 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);
|
||||
|
||||
return ldapServer;
|
||||
}
|
||||
|
||||
|
||||
private void importLdif() throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("hostname", this.bindHost);
|
||||
if (this.ldapSaslPrincipal != null) {
|
||||
map.put("ldapSaslPrincipal", this.ldapSaslPrincipal);
|
||||
}
|
||||
|
||||
// For now, assume that LDIF file is on classpath
|
||||
InputStream is;
|
||||
if (ldifFile.startsWith("file:")) {
|
||||
is = new URL(ldifFile).openStream();
|
||||
} else {
|
||||
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("Content of 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("Stopping LDAP server.");
|
||||
ldapServer.stop();
|
||||
}
|
||||
|
||||
|
||||
protected void shutdownDirectoryService() throws Exception {
|
||||
log.info("Stopping Directory service.");
|
||||
directoryService.shutdown();
|
||||
|
||||
log.info("Removing Directory service workfiles.");
|
||||
FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory());
|
||||
}
|
||||
|
||||
}
|
|
@ -2,11 +2,12 @@ package org.keycloak.testsuite.rule;
|
|||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
|
||||
import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
|
||||
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
|
||||
import org.keycloak.testsuite.federation.LDAPTestConfiguration;
|
||||
import org.keycloak.util.ldap.KerberosEmbeddedServer;
|
||||
import org.keycloak.util.ldap.LDAPEmbeddedServer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -33,7 +34,11 @@ public class KerberosRule extends LDAPRule {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) {
|
||||
return factory.createKerberosServer();
|
||||
protected LDAPEmbeddedServer createServer() {
|
||||
Properties defaultProperties = new Properties();
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:kerberos/users-kerberos.ldif");
|
||||
|
||||
return new KerberosEmbeddedServer(defaultProperties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.keycloak.testsuite.rule;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.keycloak.testsuite.ldap.EmbeddedServersFactory;
|
||||
import org.keycloak.testsuite.ldap.LDAPTestConfiguration;
|
||||
import org.keycloak.testsuite.ldap.LDAPEmbeddedServer;
|
||||
import org.keycloak.testsuite.federation.LDAPTestConfiguration;
|
||||
import org.keycloak.util.ldap.LDAPEmbeddedServer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -23,8 +23,7 @@ public class LDAPRule extends ExternalResource {
|
|||
ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation);
|
||||
|
||||
if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) {
|
||||
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
|
||||
ldapEmbeddedServer = createServer(factory);
|
||||
ldapEmbeddedServer = createServer();
|
||||
ldapEmbeddedServer.init();
|
||||
ldapEmbeddedServer.start();
|
||||
}
|
||||
|
@ -47,8 +46,12 @@ public class LDAPRule extends ExternalResource {
|
|||
return LDAP_CONNECTION_PROPERTIES_LOCATION;
|
||||
}
|
||||
|
||||
protected LDAPEmbeddedServer createServer(EmbeddedServersFactory factory) {
|
||||
return factory.createLdapServer();
|
||||
protected LDAPEmbeddedServer createServer() {
|
||||
Properties defaultProperties = new Properties();
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
|
||||
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
|
||||
|
||||
return new LDAPEmbeddedServer(defaultProperties);
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
|
|
|
@ -18,30 +18,3 @@ dn: ou=FinanceRoles,dc=keycloak,dc=org
|
|||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: FinanceRoles
|
||||
|
||||
dn: uid=jbrown,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: jbrown
|
||||
cn: James
|
||||
sn: Brown
|
||||
mail: jbrown@keycloak.org
|
||||
postalCode: 88441
|
||||
userPassword: password
|
||||
|
||||
dn: uid=bwilson,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: bwilson
|
||||
cn: Bruce
|
||||
sn: Wilson
|
||||
sn: Schneider
|
||||
mail: bwilson@keycloak.org
|
||||
postalCode: 88441
|
||||
postalCode: 77332
|
||||
street: Elm 5
|
||||
userPassword: password
|
||||
|
|
91
util/embedded-ldap/pom.xml
Normal file
91
util/embedded-ldap/pom.xml
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-util-embedded-ldap</artifactId>
|
||||
<name>Keycloak Util Embedded LDAP</name>
|
||||
<description/>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-annotations</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.jdbm</groupId>
|
||||
<artifactId>apacheds-jdbm1</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-interceptor-kerberos</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-annotations</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.jdbm</groupId>
|
||||
<artifactId>apacheds-jdbm1</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-codec-standalone</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,267 @@
|
|||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
|
||||
import org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException;
|
||||
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||
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.schemaextractor.SchemaLdifExtractor;
|
||||
import org.apache.directory.api.ldap.schemaextractor.impl.DefaultSchemaLdifExtractor;
|
||||
import org.apache.directory.api.ldap.schemaloader.LdifSchemaLoader;
|
||||
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.interceptor.context.AddOperationContext;
|
||||
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.DefaultDirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.factory.DirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
|
||||
import org.apache.directory.server.core.factory.LdifPartitionFactory;
|
||||
import org.apache.directory.server.core.factory.PartitionFactory;
|
||||
import org.apache.directory.server.core.partition.ldif.LdifPartition;
|
||||
import org.apache.directory.server.i18n.I18n;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Slightly modified version of {@link DefaultDirectoryServiceFactory} which allows persistence among restarts and uses LDIF partitions by default
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
class FileDirectoryServiceFactory implements DirectoryServiceFactory {
|
||||
|
||||
/** A logger for this class */
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileDirectoryServiceFactory.class);
|
||||
|
||||
/** The directory service. */
|
||||
private DirectoryService directoryService;
|
||||
|
||||
/** The partition factory. */
|
||||
private PartitionFactory partitionFactory;
|
||||
|
||||
|
||||
public FileDirectoryServiceFactory()
|
||||
{
|
||||
try
|
||||
{
|
||||
// creating the instance here so that
|
||||
// we we can set some properties like accesscontrol, anon access
|
||||
// before starting up the service
|
||||
directoryService = new DefaultDirectoryService();
|
||||
|
||||
// no need to register a shutdown hook during tests because this
|
||||
// starts a lot of threads and slows down test execution
|
||||
directoryService.setShutdownHookEnabled( false );
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
String typeName = System.getProperty( "apacheds.partition.factory" );
|
||||
|
||||
if ( typeName != null )
|
||||
{
|
||||
Class<? extends PartitionFactory> type = ( Class<? extends PartitionFactory> ) Class.forName( typeName );
|
||||
partitionFactory = type.newInstance();
|
||||
}
|
||||
else
|
||||
{
|
||||
// partitionFactory = new JdbmPartitionFactory();
|
||||
partitionFactory = new LdifPartitionFactory();
|
||||
}
|
||||
}
|
||||
catch ( Exception e )
|
||||
{
|
||||
LOG.error( "Error instantiating custom partiton factory", e );
|
||||
throw new RuntimeException( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FileDirectoryServiceFactory( DirectoryService directoryService, PartitionFactory partitionFactory )
|
||||
{
|
||||
this.directoryService = directoryService;
|
||||
this.partitionFactory = partitionFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void init( String name ) throws Exception
|
||||
{
|
||||
if ( ( directoryService != null ) && directoryService.isStarted() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
build(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the working directory
|
||||
*/
|
||||
private void buildInstanceDirectory( String name ) throws IOException
|
||||
{
|
||||
String instanceDirectory = System.getProperty( "workingDirectory" );
|
||||
|
||||
if ( instanceDirectory == null )
|
||||
{
|
||||
instanceDirectory = System.getProperty( "java.io.tmpdir" ) + "/server-work-" + name;
|
||||
}
|
||||
|
||||
InstanceLayout instanceLayout = new InstanceLayout( instanceDirectory );
|
||||
|
||||
/*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 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inits the schema and schema partition.
|
||||
*/
|
||||
private void initSchema() throws Exception
|
||||
{
|
||||
File workingDirectory = directoryService.getInstanceLayout().getPartitionsDirectory();
|
||||
|
||||
// Extract the schema on disk (a brand new one) and load the registries
|
||||
File schemaRepository = new File( workingDirectory, "schema" );
|
||||
SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor( workingDirectory );
|
||||
|
||||
try
|
||||
{
|
||||
extractor.extractOrCopy();
|
||||
}
|
||||
catch ( IOException ioe )
|
||||
{
|
||||
// The schema has already been extracted, bypass
|
||||
}
|
||||
|
||||
SchemaLoader loader = new LdifSchemaLoader( schemaRepository );
|
||||
SchemaManager schemaManager = new DefaultSchemaManager( loader );
|
||||
|
||||
// We have to load the schema now, otherwise we won't be able
|
||||
// to initialize the Partitions, as we won't be able to parse
|
||||
// and normalize their suffix Dn
|
||||
schemaManager.loadAllEnabled();
|
||||
|
||||
// Tell all the normalizer comparators that they should not normalize anything
|
||||
ComparatorRegistry comparatorRegistry = schemaManager.getComparatorRegistry();
|
||||
|
||||
for ( LdapComparator<?> comparator : comparatorRegistry )
|
||||
{
|
||||
if ( comparator instanceof NormalizingComparator)
|
||||
{
|
||||
( ( NormalizingComparator ) comparator ).setOnServer();
|
||||
}
|
||||
}
|
||||
|
||||
directoryService.setSchemaManager( schemaManager );
|
||||
|
||||
// Init the LdifPartition
|
||||
LdifPartition ldifPartition = new LdifPartition( schemaManager, directoryService.getDnFactory() );
|
||||
ldifPartition.setPartitionPath( new File( workingDirectory, "schema" ).toURI() );
|
||||
SchemaPartition schemaPartition = new SchemaPartition( schemaManager );
|
||||
schemaPartition.setWrappedPartition( ldifPartition );
|
||||
directoryService.setSchemaPartition( schemaPartition );
|
||||
|
||||
List<Throwable> errors = schemaManager.getErrors();
|
||||
|
||||
if ( errors.size() != 0 )
|
||||
{
|
||||
throw new Exception( I18n.err(I18n.ERR_317, Exceptions.printErrors(errors)) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inits the system partition.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
private void initSystemPartition() throws Exception
|
||||
{
|
||||
// change the working directory to something that is unique
|
||||
// on the system and somewhere either under target directory
|
||||
// or somewhere in a temp area of the machine.
|
||||
|
||||
// Inject the 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 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the directory server instance.
|
||||
*
|
||||
* @param name the instance name
|
||||
*/
|
||||
private void build( String name ) throws Exception
|
||||
{
|
||||
directoryService.setInstanceId( name );
|
||||
buildInstanceDirectory( name );
|
||||
|
||||
CacheService cacheService = new CacheService();
|
||||
cacheService.initialize( directoryService.getInstanceLayout() );
|
||||
|
||||
directoryService.setCacheService( cacheService );
|
||||
|
||||
// Init the service now
|
||||
initSchema();
|
||||
initSystemPartition();
|
||||
|
||||
directoryService.startup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public DirectoryService getDirectoryService() throws Exception
|
||||
{
|
||||
return directoryService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public PartitionFactory getPartitionFactory() throws Exception
|
||||
{
|
||||
return partitionFactory;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -76,7 +76,7 @@ class InMemoryDirectoryServiceFactory implements DirectoryServiceFactory {
|
|||
directoryService.setInstanceId(name);
|
||||
|
||||
// instance layout
|
||||
InstanceLayout instanceLayout = new InstanceLayout(System.getProperty("java.io.tmpdir") + "/server-work-" + name);
|
||||
InstanceLayout instanceLayout = new InstanceLayout(System.getProperty("java.io.tmpdir") + "/server-work-inmemory-" + name);
|
||||
if (instanceLayout.getInstanceDirectory().exists()) {
|
||||
try {
|
||||
FileUtils.deleteDirectory(instanceLayout.getInstanceDirectory());
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
@ -61,7 +61,7 @@ class InMemorySchemaPartition extends AbstractLdifPartition {
|
|||
|
||||
// add mandatory attributes
|
||||
if (entry.get(SchemaConstants.ENTRY_CSN_AT) == null) {
|
||||
entry.add(SchemaConstants.ENTRY_CSN_AT, defaultCSNFactory.newInstance().toString());
|
||||
entry.add(SchemaConstants.ENTRY_CSN_AT, AbstractLdifPartition.defaultCSNFactory.newInstance().toString());
|
||||
}
|
||||
if (entry.get(SchemaConstants.ENTRY_UUID_AT) == null) {
|
||||
entry.add(SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString());
|
|
@ -1,9 +1,10 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.kerberos.KerberosPrincipal;
|
||||
|
@ -25,7 +26,13 @@ import org.apache.directory.server.protocol.shared.transport.UdpTransport;
|
|||
import org.apache.directory.shared.kerberos.KerberosTime;
|
||||
import org.apache.directory.shared.kerberos.KerberosUtils;
|
||||
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.util.KerberosSerializationUtils;
|
||||
import sun.security.jgss.GSSNameImpl;
|
||||
import sun.security.jgss.krb5.Krb5NameElement;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -34,6 +41,16 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
|
|||
|
||||
private static final Logger log = Logger.getLogger(KerberosEmbeddedServer.class);
|
||||
|
||||
public static final String PROPERTY_KERBEROS_REALM = "kerberos.realm";
|
||||
public static final String PROPERTY_KDC_PORT = "kerberos.port";
|
||||
public static final String PROPERTY_KDC_ENCTYPES = "kerberos.encTypes";
|
||||
|
||||
private static final String DEFAULT_KERBEROS_LDIF_FILE = "classpath:kerberos/default-users.ldif";
|
||||
|
||||
private static final String DEFAULT_KERBEROS_REALM = "KEYCLOAK.ORG";
|
||||
private static final String DEFAULT_KDC_PORT = "6088";
|
||||
private static final String DEFAULT_KDC_ENCRYPTION_TYPES = "aes128-cts-hmac-sha1-96, des-cbc-md5, des3-cbc-sha1-kd";
|
||||
|
||||
private final String kerberosRealm;
|
||||
private final int kdcPort;
|
||||
private final String kdcEncryptionTypes;
|
||||
|
@ -42,18 +59,53 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
|
|||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
EmbeddedServersFactory factory = EmbeddedServersFactory.readConfiguration();
|
||||
KerberosEmbeddedServer kerberosEmbeddedServer = factory.createKerberosServer();
|
||||
Properties defaultProperties = new Properties();
|
||||
defaultProperties.put(PROPERTY_DSF, DSF_FILE);
|
||||
|
||||
execute(args, defaultProperties);
|
||||
}
|
||||
|
||||
public static void execute(String[] args, Properties defaultProperties) throws Exception {
|
||||
final KerberosEmbeddedServer kerberosEmbeddedServer = new KerberosEmbeddedServer(defaultProperties);
|
||||
kerberosEmbeddedServer.init();
|
||||
kerberosEmbeddedServer.start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
kerberosEmbeddedServer.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String ldapSaslPrincipal, String kerberosRealm, int kdcPort, String kdcEncryptionTypes) {
|
||||
super(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal);
|
||||
this.kdcEncryptionTypes = kdcEncryptionTypes;
|
||||
this.kerberosRealm = kerberosRealm;
|
||||
this.kdcPort = kdcPort;
|
||||
public KerberosEmbeddedServer(Properties defaultProperties) {
|
||||
super(defaultProperties);
|
||||
|
||||
this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_KERBEROS_LDIF_FILE);
|
||||
|
||||
this.kerberosRealm = readProperty(PROPERTY_KERBEROS_REALM, DEFAULT_KERBEROS_REALM);
|
||||
String kdcPort = readProperty(PROPERTY_KDC_PORT, DEFAULT_KDC_PORT);
|
||||
this.kdcPort = Integer.parseInt(kdcPort);
|
||||
this.kdcEncryptionTypes = readProperty(PROPERTY_KDC_ENCTYPES, DEFAULT_KDC_ENCRYPTION_TYPES);
|
||||
|
||||
if (ldapSaslPrincipal == null || ldapSaslPrincipal.isEmpty()) {
|
||||
try {
|
||||
// Same algorithm like sun.security.krb5.PrincipalName constructor
|
||||
GSSName gssName = GSSManager.getInstance().createName("ldap@" + bindHost, GSSName.NT_HOSTBASED_SERVICE);
|
||||
GSSNameImpl gssName1 = (GSSNameImpl) gssName;
|
||||
Krb5NameElement krb5NameElement = (Krb5NameElement) gssName1.getElement(KerberosSerializationUtils.KRB5_OID);
|
||||
this.ldapSaslPrincipal = krb5NameElement.getKrb5PrincipalName().toString();
|
||||
} catch (GSSException uhe) {
|
||||
throw new RuntimeException(uhe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,7 +131,7 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer {
|
|||
protected LdapServer createLdapServer() {
|
||||
LdapServer ldapServer = super.createLdapServer();
|
||||
|
||||
ldapServer.setSaslHost( this.bindHost );
|
||||
ldapServer.setSaslHost(this.bindHost);
|
||||
ldapServer.setSaslPrincipal( this.ldapSaslPrincipal);
|
||||
ldapServer.setSaslRealms(new ArrayList<String>());
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package org.keycloak.testsuite.ldap;
|
||||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
|
||||
import org.apache.directory.server.kerberos.shared.keytab.Keytab;
|
||||
|
@ -34,7 +35,7 @@ public class KerberosKeytabCreator {
|
|||
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.testsuite.ldap.KerberosKeytabCreator\" -Dexec.args=\"HTTP/localhost@KEYCLOAK.ORG httppwd src/main/resources/kerberos/http.keytab\"");
|
||||
System.out.println("java -jar embedded-ldap/target/embedded-ldap.jar keytabCreator HTTP/localhost@KEYCLOAK.ORG httppassword /tmp/http.keytab");
|
||||
} else {
|
||||
final File keytabFile = new File(args[2]);
|
||||
createKeytab(args[0], args[1], keytabFile);
|
||||
|
@ -42,6 +43,11 @@ public class KerberosKeytabCreator {
|
|||
}
|
||||
}
|
||||
|
||||
// Just for the reflection purposes
|
||||
public static void execute(String[] args, Properties defaultProperties) throws Exception {
|
||||
main(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a keytab file for given principal.
|
||||
*
|
|
@ -0,0 +1,272 @@
|
|||
package org.keycloak.util.ldap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
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.DirectoryServiceFactory;
|
||||
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.FindFile;
|
||||
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);
|
||||
|
||||
public static final String PROPERTY_BASE_DN = "ldap.baseDN";
|
||||
public static final String PROPERTY_BIND_HOST = "ldap.host";
|
||||
public static final String PROPERTY_BIND_PORT = "ldap.port";
|
||||
public static final String PROPERTY_LDIF_FILE = "ldap.ldif";
|
||||
public static final String PROPERTY_SASL_PRINCIPAL = "ldap.saslPrincipal";
|
||||
public static final String PROPERTY_DSF = "ldap.dsf";
|
||||
|
||||
private static final String DEFAULT_BASE_DN = "dc=keycloak,dc=org";
|
||||
private static final String DEFAULT_BIND_HOST = "localhost";
|
||||
private static final String DEFAULT_BIND_PORT = "10389";
|
||||
private static final String DEFAULT_LDIF_FILE = "classpath:ldap/default-users.ldif";
|
||||
|
||||
public static final String DSF_INMEMORY = "mem";
|
||||
public static final String DSF_FILE = "file";
|
||||
public static final String DEFAULT_DSF = DSF_FILE;
|
||||
|
||||
protected Properties defaultProperties;
|
||||
|
||||
protected String baseDN;
|
||||
protected String bindHost;
|
||||
protected int bindPort;
|
||||
protected String ldifFile;
|
||||
protected String ldapSaslPrincipal;
|
||||
protected String directoryServiceFactory;
|
||||
|
||||
protected DirectoryService directoryService;
|
||||
protected LdapServer ldapServer;
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Properties defaultProperties = new Properties();
|
||||
defaultProperties.put(PROPERTY_DSF, DSF_FILE);
|
||||
|
||||
execute(args, defaultProperties);
|
||||
}
|
||||
|
||||
public static void execute(String[] args, Properties defaultProperties) throws Exception {
|
||||
final LDAPEmbeddedServer ldapEmbeddedServer = new LDAPEmbeddedServer(defaultProperties);
|
||||
ldapEmbeddedServer.init();
|
||||
ldapEmbeddedServer.start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ldapEmbeddedServer.stop();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public LDAPEmbeddedServer(Properties defaultProperties) {
|
||||
this.defaultProperties = defaultProperties;
|
||||
|
||||
this.baseDN = readProperty(PROPERTY_BASE_DN, DEFAULT_BASE_DN);
|
||||
this.bindHost = readProperty(PROPERTY_BIND_HOST, DEFAULT_BIND_HOST);
|
||||
String bindPort = readProperty(PROPERTY_BIND_PORT, DEFAULT_BIND_PORT);
|
||||
this.bindPort = Integer.parseInt(bindPort);
|
||||
this.ldifFile = readProperty(PROPERTY_LDIF_FILE, DEFAULT_LDIF_FILE);
|
||||
this.ldapSaslPrincipal = readProperty(PROPERTY_SASL_PRINCIPAL, null);
|
||||
this.directoryServiceFactory = readProperty(PROPERTY_DSF, DEFAULT_DSF);
|
||||
}
|
||||
|
||||
protected String readProperty(String propertyName, String defaultValue) {
|
||||
String value = System.getProperty(propertyName);
|
||||
|
||||
if (value == null || value.isEmpty()) {
|
||||
value = (String) this.defaultProperties.get(propertyName);
|
||||
}
|
||||
|
||||
if (value == null || value.isEmpty()) {
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public void init() throws Exception {
|
||||
log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort +
|
||||
", ldapSaslPrincipal=" + ldapSaslPrincipal + ", directoryServiceFactory=" + directoryServiceFactory + ", ldif=" + ldifFile);
|
||||
|
||||
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];
|
||||
dcName = dcName.substring(dcName.indexOf("=") + 1);
|
||||
|
||||
DirectoryServiceFactory dsf;
|
||||
if (this.directoryServiceFactory.equals(DSF_INMEMORY)) {
|
||||
dsf = new InMemoryDirectoryServiceFactory();
|
||||
} else if (this.directoryServiceFactory.equals(DSF_FILE)) {
|
||||
dsf = new FileDirectoryServiceFactory();
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown value of directoryServiceFactory: " + this.directoryServiceFactory);
|
||||
}
|
||||
|
||||
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";
|
||||
importLdifContent(service, entryLdif);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
|
||||
protected LdapServer createLdapServer() {
|
||||
LdapServer ldapServer = new LdapServer();
|
||||
|
||||
ldapServer.setServiceName("DefaultLdapServer");
|
||||
ldapServer.setSearchBaseDn(this.baseDN);
|
||||
|
||||
// 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);
|
||||
|
||||
return ldapServer;
|
||||
}
|
||||
|
||||
|
||||
private void importLdif() throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put("hostname", this.bindHost);
|
||||
if (this.ldapSaslPrincipal != null) {
|
||||
map.put("ldapSaslPrincipal", this.ldapSaslPrincipal);
|
||||
}
|
||||
|
||||
// Find LDIF file on filesystem or classpath ( if it's like classpath:ldap/users.ldif )
|
||||
InputStream is = FindFile.findFile(ldifFile);
|
||||
if (is == null) {
|
||||
throw new IllegalStateException("LDIF file not found on classpath or on file system. Location was: " + ldifFile);
|
||||
}
|
||||
|
||||
final String ldifContent = StrSubstitutor.replace(StreamUtil.readString(is), map);
|
||||
log.info("Content of LDIF: " + ldifContent);
|
||||
final SchemaManager schemaManager = directoryService.getSchemaManager();
|
||||
|
||||
importLdifContent(directoryService, ldifContent);
|
||||
}
|
||||
|
||||
private static void importLdifContent(DirectoryService directoryService, String ldifContent) throws Exception {
|
||||
LdifReader ldifReader = new LdifReader(IOUtils.toInputStream(ldifContent));
|
||||
|
||||
try {
|
||||
for (LdifEntry ldifEntry : ldifReader) {
|
||||
try {
|
||||
directoryService.getAdminSession().add(new DefaultEntry(directoryService.getSchemaManager(), ldifEntry.getEntry()));
|
||||
} catch (LdapEntryAlreadyExistsException ignore) {
|
||||
log.info("Entry " + ldifEntry.getDn() + " already exists. Ignoring");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ldifReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void stop() throws Exception {
|
||||
stopLdapServer();
|
||||
shutdownDirectoryService();
|
||||
}
|
||||
|
||||
|
||||
protected void stopLdapServer() {
|
||||
log.info("Stopping LDAP server.");
|
||||
ldapServer.stop();
|
||||
}
|
||||
|
||||
|
||||
protected void shutdownDirectoryService() throws Exception {
|
||||
log.info("Stopping Directory service.");
|
||||
directoryService.shutdown();
|
||||
|
||||
// Delete workfiles just for 'inmemory' implementation used in tests. Normally we want LDAP data to persist
|
||||
File instanceDir = directoryService.getInstanceLayout().getInstanceDirectory();
|
||||
if (this.directoryServiceFactory.equals(DSF_INMEMORY)) {
|
||||
log.infof("Removing Directory service workfiles: %s", instanceDir.getAbsolutePath());
|
||||
FileUtils.deleteDirectory(instanceDir);
|
||||
} else {
|
||||
log.info("Working LDAP directory not deleted. Delete it manually if you want to start with fresh LDAP data. Directory location: " + instanceDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
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: ${ldapSaslPrincipal}
|
||||
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
|
||||
sn: Nelson
|
||||
mail: hnelson@keycloak.org
|
||||
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
|
||||
sn: Duke
|
||||
mail: jduke@keycloak.org
|
||||
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
|
|
@ -0,0 +1,47 @@
|
|||
dn: dc=keycloak,dc=org
|
||||
objectclass: dcObject
|
||||
objectclass: organization
|
||||
o: Keycloak
|
||||
dc: Keycloak
|
||||
|
||||
dn: ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: People
|
||||
|
||||
dn: ou=RealmRoles,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: RealmRoles
|
||||
|
||||
dn: ou=FinanceRoles,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: FinanceRoles
|
||||
|
||||
dn: uid=jbrown,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: jbrown
|
||||
cn: James
|
||||
sn: Brown
|
||||
mail: jbrown@keycloak.org
|
||||
postalCode: 88441
|
||||
userPassword: password
|
||||
|
||||
dn: uid=bwilson,ou=People,dc=keycloak,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
uid: bwilson
|
||||
cn: Bruce
|
||||
sn: Wilson
|
||||
sn: Schneider
|
||||
mail: bwilson@keycloak.org
|
||||
postalCode: 88441
|
||||
postalCode: 77332
|
||||
street: Elm 5
|
||||
userPassword: password
|
9
util/embedded-ldap/src/main/resources/log4j.properties
Normal file
9
util/embedded-ldap/src/main/resources/log4j.properties
Normal file
|
@ -0,0 +1,9 @@
|
|||
log4j.rootLogger=info, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
|
||||
|
||||
log4j.logger.org.keycloak=info
|
||||
log4j.logger.org.apache.directory.api=warn
|
||||
log4j.logger.org.apache.directory.server.core=warn
|
22
util/pom.xml
Normal file
22
util/pom.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.4.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>Keycloak Util Parent</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-util-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>embedded-ldap</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
Loading…
Reference in a new issue