Merge pull request #1126 from mposolda/master
KEYCLOAK-1007 Fork Picketlink LDAP code. Remove picketlink dependencies ...
This commit is contained in:
commit
b92a178142
61 changed files with 4042 additions and 1013 deletions
28
dependencies/server-all/pom.xml
vendored
28
dependencies/server-all/pom.xml
vendored
|
@ -144,34 +144,6 @@
|
||||||
<artifactId>keycloak-kerberos-federation</artifactId>
|
<artifactId>keycloak-kerberos-federation</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-common</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-impl</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-simple-schema</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- picketlink -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-picketlink-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-picketlink-ldap</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- saml -->
|
<!-- saml -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -259,14 +259,6 @@
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-ldap-federation"/>
|
<maven-resource group="org.keycloak" artifact="keycloak-ldap-federation"/>
|
||||||
</module-def>
|
</module-def>
|
||||||
|
|
||||||
<module-def name="org.keycloak.keycloak-picketlink-api">
|
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-picketlink-api"/>
|
|
||||||
</module-def>
|
|
||||||
|
|
||||||
<module-def name="org.keycloak.keycloak-picketlink-ldap">
|
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-picketlink-ldap"/>
|
|
||||||
</module-def>
|
|
||||||
|
|
||||||
<module-def name="org.keycloak.keycloak-saml-core">
|
<module-def name="org.keycloak.keycloak-saml-core">
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-saml-core"/>
|
<maven-resource group="org.keycloak" artifact="keycloak-saml-core"/>
|
||||||
</module-def>
|
</module-def>
|
||||||
|
|
|
@ -10,14 +10,9 @@
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
<module name="org.keycloak.keycloak-core"/>
|
||||||
<module name="org.keycloak.keycloak-model-api"/>
|
<module name="org.keycloak.keycloak-model-api"/>
|
||||||
<module name="org.keycloak.keycloak-kerberos-federation"/>
|
<module name="org.keycloak.keycloak-kerberos-federation"/>
|
||||||
<module name="org.keycloak.keycloak-picketlink-api"/>
|
|
||||||
<module name="javax.ws.rs.api"/>
|
<module name="javax.ws.rs.api"/>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
<module name="org.picketlink.common"/>
|
|
||||||
<module name="org.picketlink.idm.api"/>
|
|
||||||
<module name="org.picketlink.idm"/>
|
|
||||||
<module name="org.picketlink.idm.schema"/>
|
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-picketlink-api">
|
|
||||||
<resources>
|
|
||||||
<!-- Insert resources here -->
|
|
||||||
</resources>
|
|
||||||
<dependencies>
|
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
|
||||||
<module name="org.keycloak.keycloak-model-api"/>
|
|
||||||
<module name="org.keycloak.keycloak-picketlink-api"/>
|
|
||||||
<module name="org.jboss.logging"/>
|
|
||||||
<module name="org.picketlink.common"/>
|
|
||||||
<module name="org.picketlink.idm.api"/>
|
|
||||||
<module name="org.picketlink.idm"/>
|
|
||||||
<module name="org.picketlink.idm.schema"/>
|
|
||||||
<module name="javax.api"/>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</module>
|
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-picketlink-ldap">
|
|
||||||
<resources>
|
|
||||||
<!-- Insert resources here -->
|
|
||||||
</resources>
|
|
||||||
<dependencies>
|
|
||||||
<module name="org.keycloak.keycloak-core"/>
|
|
||||||
<module name="org.keycloak.keycloak-model-api"/>
|
|
||||||
<module name="org.keycloak.keycloak-picketlink-api"/>
|
|
||||||
<module name="org.jboss.logging"/>
|
|
||||||
<module name="org.picketlink.common"/>
|
|
||||||
<module name="org.picketlink.idm.api"/>
|
|
||||||
<module name="org.picketlink.idm"/>
|
|
||||||
<module name="org.picketlink.idm.schema"/>
|
|
||||||
<module name="javax.api"/>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</module>
|
|
|
@ -48,8 +48,6 @@
|
||||||
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-picketlink-api" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
||||||
|
|
|
@ -49,10 +49,8 @@
|
||||||
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-picketlink-api" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
|
||||||
|
|
||||||
<module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
||||||
|
|
|
@ -40,9 +40,6 @@
|
||||||
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-jpa" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mem" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
<module name="org.keycloak.keycloak-model-sessions-mongo" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-picketlink-api" services="import"/>
|
|
||||||
|
|
||||||
<module name="org.keycloak.keycloak-picketlink-ldap" services="import"/>
|
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
<module name="org.keycloak.keycloak-social-core" services="import"/>
|
||||||
|
|
|
@ -31,12 +31,6 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-picketlink-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
@ -61,27 +55,6 @@
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-common</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-impl</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-simple-schema</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -3,6 +3,10 @@ package org.keycloak.federation.ldap;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -16,12 +20,6 @@ import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
import org.picketlink.idm.IdentityManagementException;
|
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.model.basic.BasicModel;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
import org.picketlink.idm.query.IdentityQuery;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -38,23 +36,21 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class LDAPFederationProvider implements UserFederationProvider {
|
public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
|
private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
|
||||||
public static final String LDAP_ID = "LDAP_ID";
|
|
||||||
public static final String SYNC_REGISTRATIONS = "syncRegistrations";
|
|
||||||
|
|
||||||
protected LDAPFederationProviderFactory factory;
|
protected LDAPFederationProviderFactory factory;
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
protected UserFederationProviderModel model;
|
protected UserFederationProviderModel model;
|
||||||
protected PartitionManager partitionManager;
|
protected LDAPIdentityStore ldapIdentityStore;
|
||||||
protected EditMode editMode;
|
protected EditMode editMode;
|
||||||
protected LDAPProviderKerberosConfig kerberosConfig;
|
protected LDAPProviderKerberosConfig kerberosConfig;
|
||||||
|
|
||||||
protected final Set<String> supportedCredentialTypes = new HashSet<String>();
|
protected final Set<String> supportedCredentialTypes = new HashSet<String>();
|
||||||
|
|
||||||
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, PartitionManager partitionManager) {
|
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.partitionManager = partitionManager;
|
this.ldapIdentityStore = ldapIdentityStore;
|
||||||
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
|
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
|
||||||
String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE);
|
String editModeString = model.getConfig().get(LDAPConstants.EDIT_MODE);
|
||||||
if (editModeString == null) {
|
if (editModeString == null) {
|
||||||
|
@ -69,16 +65,6 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModelException convertIDMException(IdentityManagementException ie) {
|
|
||||||
Throwable realCause = ie;
|
|
||||||
while (realCause.getCause() != null) {
|
|
||||||
realCause = realCause.getCause();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the message from the realCause
|
|
||||||
return new ModelException(realCause.getMessage(), ie);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeycloakSession getSession() {
|
public KeycloakSession getSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
@ -87,8 +73,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartitionManager getPartitionManager() {
|
public LDAPIdentityStore getLdapIdentityStore() {
|
||||||
return partitionManager;
|
return this.ldapIdentityStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -125,22 +111,18 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean synchronizeRegistrations() {
|
public boolean synchronizeRegistrations() {
|
||||||
return "true".equalsIgnoreCase(model.getConfig().get(SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
|
return "true".equalsIgnoreCase(model.getConfig().get(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel register(RealmModel realm, UserModel user) {
|
public UserModel register(RealmModel realm, UserModel user) {
|
||||||
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");;
|
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");
|
||||||
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
|
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
|
||||||
|
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.addUser(this.ldapIdentityStore, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
|
||||||
User picketlinkUser = LDAPUtils.addUser(this.partitionManager, user.getUsername(), user.getFirstName(), user.getLastName(), user.getEmail());
|
user.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
|
||||||
user.setAttribute(LDAP_ID, picketlinkUser.getId());
|
user.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
|
||||||
return proxy(user);
|
return proxy(user);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -150,58 +132,53 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return LDAPUtils.removeUser(this.ldapIdentityStore, user.getUsername());
|
||||||
return LDAPUtils.removeUser(partitionManager, user.getUsername());
|
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
|
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
|
||||||
List<UserModel> searchResults =new LinkedList<UserModel>();
|
List<UserModel> searchResults =new LinkedList<UserModel>();
|
||||||
try {
|
|
||||||
Map<String, User> plUsers = searchPicketlink(attributes, maxResults);
|
Map<String, LDAPUser> ldapUsers = searchLDAP(attributes, maxResults);
|
||||||
for (User user : plUsers.values()) {
|
for (LDAPUser ldapUser : ldapUsers.values()) {
|
||||||
if (session.userStorage().getUserByUsername(user.getLoginName(), realm) == null) {
|
if (session.userStorage().getUserByUsername(ldapUser.getLoginName(), realm) == null) {
|
||||||
UserModel imported = importUserFromPicketlink(realm, user);
|
UserModel imported = importUserFromLDAP(realm, ldapUser);
|
||||||
searchResults.add(imported);
|
searchResults.add(imported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
return searchResults;
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<String, User> searchPicketlink(Map<String, String> attributes, int maxResults) {
|
protected Map<String, LDAPUser> searchLDAP(Map<String, String> attributes, int maxResults) {
|
||||||
IdentityManager identityManager = getIdentityManager();
|
|
||||||
Map<String, User> results = new HashMap<String, User>();
|
Map<String, LDAPUser> results = new HashMap<String, LDAPUser>();
|
||||||
if (attributes.containsKey(USERNAME)) {
|
if (attributes.containsKey(USERNAME)) {
|
||||||
User user = BasicModel.getUser(identityManager, attributes.get(USERNAME));
|
LDAPUser user = LDAPUtils.getUser(this.ldapIdentityStore, attributes.get(USERNAME));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
results.put(user.getLoginName(), user);
|
results.put(user.getLoginName(), user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.containsKey(EMAIL)) {
|
if (attributes.containsKey(EMAIL)) {
|
||||||
User user = queryByEmail(identityManager, attributes.get(EMAIL));
|
LDAPUser user = queryByEmail(attributes.get(EMAIL));
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
results.put(user.getLoginName(), user);
|
results.put(user.getLoginName(), user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
|
||||||
IdentityQuery<User> query = identityManager.createIdentityQuery(User.class);
|
IdentityQueryBuilder queryBuilder = this.ldapIdentityStore.createQueryBuilder();
|
||||||
|
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class);
|
||||||
if (attributes.containsKey(FIRST_NAME)) {
|
if (attributes.containsKey(FIRST_NAME)) {
|
||||||
query.setParameter(User.FIRST_NAME, attributes.get(FIRST_NAME));
|
query.where(queryBuilder.equal(LDAPUser.FIRST_NAME, attributes.get(FIRST_NAME)));
|
||||||
}
|
}
|
||||||
if (attributes.containsKey(LAST_NAME)) {
|
if (attributes.containsKey(LAST_NAME)) {
|
||||||
query.setParameter(User.LAST_NAME, attributes.get(LAST_NAME));
|
query.where(queryBuilder.equal(LDAPUser.LAST_NAME, attributes.get(LAST_NAME)));
|
||||||
}
|
}
|
||||||
query.setLimit(maxResults);
|
query.setLimit(maxResults);
|
||||||
List<User> agents = query.getResultList();
|
List<LDAPUser> users = query.getResultList();
|
||||||
for (User user : agents) {
|
for (LDAPUser user : users) {
|
||||||
results.put(user.getLoginName(), user);
|
results.put(user.getLoginName(), user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,85 +188,69 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(UserModel local) {
|
public boolean isValid(UserModel local) {
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, local.getUsername());
|
||||||
User picketlinkUser = LDAPUtils.getUser(partitionManager, local.getUsername());
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return picketlinkUser.getId().equals(local.getAttribute(LDAP_ID));
|
return ldapUser.getId().equals(local.getAttribute(LDAPConstants.LDAP_ID));
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByUsername(RealmModel realm, String username) {
|
public UserModel getUserByUsername(RealmModel realm, String username) {
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(this.ldapIdentityStore, username);
|
||||||
User picketlinkUser = LDAPUtils.getUser(partitionManager, username);
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
|
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
|
||||||
if (!username.equals(picketlinkUser.getLoginName())) {
|
if (!username.equals(ldapUser.getLoginName())) {
|
||||||
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, picketlinkUser.getLoginName());
|
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUser.getLoginName());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return importUserFromPicketlink(realm, picketlinkUser);
|
return importUserFromLDAP(realm, ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IdentityManager getIdentityManager() {
|
protected UserModel importUserFromLDAP(RealmModel realm, LDAPUser ldapUser) {
|
||||||
return partitionManager.createIdentityManager();
|
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
|
||||||
|
|
||||||
|
if (ldapUser.getLoginName() == null) {
|
||||||
|
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + ldapUser.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
|
UserModel imported = session.userStorage().addUser(realm, ldapUser.getLoginName());
|
||||||
String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
|
|
||||||
|
|
||||||
if (picketlinkUser.getLoginName() == null) {
|
|
||||||
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. ID of user from LDAP: " + picketlinkUser.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
UserModel imported = session.userStorage().addUser(realm, picketlinkUser.getLoginName());
|
|
||||||
imported.setEnabled(true);
|
imported.setEnabled(true);
|
||||||
imported.setEmail(email);
|
imported.setEmail(email);
|
||||||
imported.setFirstName(picketlinkUser.getFirstName());
|
imported.setFirstName(ldapUser.getFirstName());
|
||||||
imported.setLastName(picketlinkUser.getLastName());
|
imported.setLastName(ldapUser.getLastName());
|
||||||
imported.setFederationLink(model.getId());
|
imported.setFederationLink(model.getId());
|
||||||
imported.setAttribute(LDAP_ID, picketlinkUser.getId());
|
imported.setAttribute(LDAPConstants.LDAP_ID, ldapUser.getId());
|
||||||
|
imported.setAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapUser.getEntryDN());
|
||||||
|
|
||||||
logger.debugf("Added new user from LDAP. Username: " + imported.getUsername() + ", Email: ", imported.getEmail() + ", LDAP_ID: " + picketlinkUser.getId());
|
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
|
||||||
|
ldapUser.getId(), ldapUser.getEntryDN());
|
||||||
return proxy(imported);
|
return proxy(imported);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
|
protected LDAPUser queryByEmail(String email) {
|
||||||
return LDAPUtils.getUserByEmail(identityManager, email);
|
return LDAPUtils.getUserByEmail(this.ldapIdentityStore, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByEmail(RealmModel realm, String email) {
|
public UserModel getUserByEmail(RealmModel realm, String email) {
|
||||||
IdentityManager identityManager = getIdentityManager();
|
LDAPUser ldapUser = queryByEmail(email);
|
||||||
|
if (ldapUser == null) {
|
||||||
try {
|
|
||||||
User picketlinkUser = queryByEmail(identityManager, email);
|
|
||||||
if (picketlinkUser == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
|
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
|
||||||
if (!email.equals(picketlinkUser.getEmail())) {
|
if (!email.equals(ldapUser.getEmail())) {
|
||||||
logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, picketlinkUser.getEmail());
|
logger.warnf("User found in LDAP but with different email. LDAP email: %s, Searched email: %s", email, ldapUser.getEmail());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return importUserFromPicketlink(realm, picketlinkUser);
|
return importUserFromLDAP(realm, ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -302,18 +263,14 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
// complete I don't think we have to do anything here
|
// complete I don't think we have to do anything here
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validPassword(String username, String password) {
|
public boolean validPassword(UserModel user, String password) {
|
||||||
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
|
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
|
||||||
// Use Kerberos JAAS (Krb5LoginModule)
|
// Use Kerberos JAAS (Krb5LoginModule)
|
||||||
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
|
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
|
||||||
return authenticator.validUser(username, password);
|
return authenticator.validUser(user.getUsername(), password);
|
||||||
} else {
|
} else {
|
||||||
// Use Naming LDAP API
|
// Use Naming LDAP API
|
||||||
try {
|
return LDAPUtils.validatePassword(this.ldapIdentityStore, user, password);
|
||||||
return LDAPUtils.validatePassword(partitionManager, username, password);
|
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw convertIDMException(ie);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +279,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||||
for (UserCredentialModel cred : input) {
|
for (UserCredentialModel cred : input) {
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
return validPassword(user.getUsername(), cred.getValue());
|
return validPassword(user, cred.getValue());
|
||||||
} else {
|
} else {
|
||||||
return false; // invalid cred type
|
return false; // invalid cred type
|
||||||
}
|
}
|
||||||
|
@ -353,7 +310,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
UserModel user = findOrCreateAuthenticatedUser(realm, username);
|
UserModel user = findOrCreateAuthenticatedUser(realm, username);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
logger.warn("Kerberos/SPNEGO authentication succeeded with username [" + username + "], but couldn't find or create user with federation provider [" + model.getDisplayName() + "]");
|
logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getDisplayName());
|
||||||
return CredentialValidationOutput.failed();
|
return CredentialValidationOutput.failed();
|
||||||
} else {
|
} else {
|
||||||
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
|
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
|
||||||
|
@ -375,24 +332,23 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
//To change body of implemented methods use File | Settings | File Templates.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void importPicketlinkUsers(RealmModel realm, List<User> users, UserFederationProviderModel fedModel) {
|
protected void importLDAPUsers(RealmModel realm, List<LDAPUser> ldapUsers, UserFederationProviderModel fedModel) {
|
||||||
for (User picketlinkUser : users) {
|
for (LDAPUser ldapUser : ldapUsers) {
|
||||||
String username = picketlinkUser.getLoginName();
|
String username = ldapUser.getLoginName();
|
||||||
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
|
UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
|
||||||
|
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
// Add new user to Keycloak
|
// Add new user to Keycloak
|
||||||
importUserFromPicketlink(realm, picketlinkUser);
|
importUserFromLDAP(realm, ldapUser);
|
||||||
} else {
|
} else {
|
||||||
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (picketlinkUser.getId().equals(currentUser.getAttribute(LDAPFederationProvider.LDAP_ID)))) {
|
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getId().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) {
|
||||||
// Update keycloak user
|
// Update keycloak user
|
||||||
String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
|
String email = (ldapUser.getEmail() != null && ldapUser.getEmail().trim().length() > 0) ? ldapUser.getEmail() : null;
|
||||||
currentUser.setEmail(email);
|
currentUser.setEmail(email);
|
||||||
currentUser.setFirstName(picketlinkUser.getFirstName());
|
currentUser.setFirstName(ldapUser.getFirstName());
|
||||||
currentUser.setLastName(picketlinkUser.getLastName());
|
currentUser.setLastName(ldapUser.getLastName());
|
||||||
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
|
||||||
} else {
|
} else {
|
||||||
logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
|
logger.warnf("User '%s' is not updated during sync as he is not linked to federation provider '%s'", username, fedModel.getDisplayName());
|
||||||
|
@ -404,29 +360,29 @@ public class LDAPFederationProvider implements UserFederationProvider {
|
||||||
/**
|
/**
|
||||||
* Called after successful kerberos authentication
|
* Called after successful kerberos authentication
|
||||||
*
|
*
|
||||||
* @param realm
|
* @param realm realm
|
||||||
* @param username username without realm prefix
|
* @param username username without realm prefix
|
||||||
* @return
|
* @return finded or newly created user
|
||||||
*/
|
*/
|
||||||
protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
|
protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
|
||||||
UserModel user = session.userStorage().getUserByUsername(username, realm);
|
UserModel user = session.userStorage().getUserByUsername(username, realm);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
logger.debug("Kerberos authenticated user " + username + " found in Keycloak storage");
|
logger.debugf("Kerberos authenticated user [%s] found in Keycloak storage", username);
|
||||||
if (!model.getId().equals(user.getFederationLink())) {
|
if (!model.getId().equals(user.getFederationLink())) {
|
||||||
logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
|
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
|
||||||
return null;
|
return null;
|
||||||
} else if (isValid(user)) {
|
} else if (isValid(user)) {
|
||||||
return proxy(user);
|
return proxy(user);
|
||||||
} else {
|
} else {
|
||||||
logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
|
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
|
||||||
"] but is not valid. Stale LDAP_ID on local user is: " + user.getAttribute(LDAP_ID));
|
username, model.getDisplayName(), user.getAttribute(LDAPConstants.LDAP_ID));
|
||||||
logger.warn("Will re-create user");
|
logger.warn("Will re-create user");
|
||||||
session.userStorage().removeUser(realm, user);
|
session.userStorage().removeUser(realm, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating user to local storage
|
// Creating user to local storage
|
||||||
logger.debug("Kerberos authenticated user " + username + " not in Keycloak storage. Creating him");
|
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
|
||||||
return getUserByUsername(realm, username);
|
return getUserByUsername(realm, username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,15 @@ package org.keycloak.federation.ldap;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||||
import org.keycloak.federation.kerberos.KerberosConfig;
|
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
|
||||||
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakSessionTask;
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
@ -16,16 +21,6 @@ import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderFactory;
|
import org.keycloak.models.UserFederationProviderFactory;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.picketlink.PartitionManagerProvider;
|
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.model.IdentityType;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
import org.picketlink.idm.query.AttributeParameter;
|
|
||||||
import org.picketlink.idm.query.Condition;
|
|
||||||
import org.picketlink.idm.query.IdentityQuery;
|
|
||||||
import org.picketlink.idm.query.IdentityQueryBuilder;
|
|
||||||
import org.picketlink.idm.query.QueryParameter;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -41,6 +36,8 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
|
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
|
||||||
public static final String PROVIDER_NAME = "ldap";
|
public static final String PROVIDER_NAME = "ldap";
|
||||||
|
|
||||||
|
private LDAPIdentityStoreRegistry ldapStoreRegistry;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserFederationProvider create(KeycloakSession session) {
|
public UserFederationProvider create(KeycloakSession session) {
|
||||||
throw new IllegalAccessError("Illegal to call this method");
|
throw new IllegalAccessError("Illegal to call this method");
|
||||||
|
@ -48,13 +45,13 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
|
public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
|
||||||
PartitionManagerProvider idmProvider = session.getProvider(PartitionManagerProvider.class);
|
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
|
||||||
PartitionManager partition = idmProvider.getPartitionManager(model);
|
return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
|
||||||
return new LDAPFederationProvider(this, session, model, partition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,7 +61,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
this.ldapStoreRegistry = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,9 +78,8 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
|
public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
|
||||||
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
|
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
|
||||||
|
|
||||||
PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
|
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
|
||||||
PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
|
IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
|
||||||
IdentityQuery<User> userQuery = partitionMgr.createIdentityManager().createIdentityQuery(User.class);
|
|
||||||
syncImpl(sessionFactory, userQuery, realmId, model);
|
syncImpl(sessionFactory, userQuery, realmId, model);
|
||||||
|
|
||||||
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
|
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
|
||||||
|
@ -91,26 +87,23 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
|
public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
|
||||||
logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date() + ", last sync time: " + lastSync, realmId, model.getDisplayName());
|
logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: %s, last sync time: " + lastSync, realmId, model.getDisplayName(), new Date().toString());
|
||||||
|
|
||||||
PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
|
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
|
||||||
PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
|
|
||||||
|
|
||||||
// Sync newly created users
|
// Sync newly created users
|
||||||
IdentityManager identityManager = partitionMgr.createIdentityManager();
|
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
|
||||||
IdentityQueryBuilder queryBuilder = identityManager.getQueryBuilder();
|
|
||||||
Condition condition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
|
Condition condition = queryBuilder.greaterThanOrEqualTo(IdentityType.CREATED_DATE, lastSync);
|
||||||
IdentityQuery<User> userQuery = queryBuilder.createIdentityQuery(User.class).where(condition);
|
IdentityQuery<LDAPUser> userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
|
||||||
syncImpl(sessionFactory, userQuery, realmId, model);
|
syncImpl(sessionFactory, userQuery, realmId, model);
|
||||||
|
|
||||||
// Sync updated users
|
// Sync updated users
|
||||||
queryBuilder = identityManager.getQueryBuilder();
|
|
||||||
condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
|
condition = queryBuilder.greaterThanOrEqualTo(LDAPUtils.MODIFY_DATE, lastSync);
|
||||||
userQuery = queryBuilder.createIdentityQuery(User.class).where(condition);
|
userQuery = queryBuilder.createIdentityQuery(LDAPUser.class).where(condition);
|
||||||
syncImpl(sessionFactory, userQuery, realmId, model);
|
syncImpl(sessionFactory, userQuery, realmId, model);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<User> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
|
protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<LDAPUser> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
|
||||||
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
|
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
|
||||||
|
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
|
@ -119,36 +112,36 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
|
||||||
boolean nextPage = true;
|
boolean nextPage = true;
|
||||||
while (nextPage) {
|
while (nextPage) {
|
||||||
userQuery.setLimit(pageSize);
|
userQuery.setLimit(pageSize);
|
||||||
final List<User> users = userQuery.getResultList();
|
final List<LDAPUser> users = userQuery.getResultList();
|
||||||
nextPage = userQuery.getPaginationContext() != null;
|
nextPage = userQuery.getPaginationContext() != null;
|
||||||
|
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(KeycloakSession session) {
|
public void run(KeycloakSession session) {
|
||||||
importPicketlinkUsers(session, realmId, fedModel, users);
|
importLdapUsers(session, realmId, fedModel, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// LDAP pagination not available. Do everything in single transaction
|
// LDAP pagination not available. Do everything in single transaction
|
||||||
final List<User> users = userQuery.getResultList();
|
final List<LDAPUser> users = userQuery.getResultList();
|
||||||
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(KeycloakSession session) {
|
public void run(KeycloakSession session) {
|
||||||
importPicketlinkUsers(session, realmId, fedModel, users);
|
importLdapUsers(session, realmId, fedModel, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void importPicketlinkUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<User> users) {
|
protected void importLdapUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<LDAPUser> ldapUsers) {
|
||||||
RealmModel realm = session.realms().getRealm(realmId);
|
RealmModel realm = session.realms().getRealm(realmId);
|
||||||
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
|
||||||
ldapFedProvider.importPicketlinkUsers(realm, users, fedModel);
|
ldapFedProvider.importLDAPUsers(realm, ldapUsers, fedModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
|
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPMappingConfiguration;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class LDAPIdentityStoreRegistry {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(LDAPIdentityStoreRegistry.class);
|
||||||
|
|
||||||
|
private Map<String, LDAPIdentityStoreContext> ldapStores = new ConcurrentHashMap<String, LDAPIdentityStoreContext>();
|
||||||
|
|
||||||
|
public LDAPIdentityStore getLdapStore(UserFederationProviderModel model) {
|
||||||
|
LDAPIdentityStoreContext context = ldapStores.get(model.getId());
|
||||||
|
|
||||||
|
// Ldap config might have changed for the realm. In this case, we must re-initialize
|
||||||
|
Map<String, String> config = model.getConfig();
|
||||||
|
if (context == null || !config.equals(context.config)) {
|
||||||
|
logLDAPConfig(model.getId(), config);
|
||||||
|
|
||||||
|
LDAPIdentityStore store = createLdapIdentityStore(config);
|
||||||
|
context = new LDAPIdentityStoreContext(config, store);
|
||||||
|
ldapStores.put(model.getId(), context);
|
||||||
|
}
|
||||||
|
return context.store;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't log LDAP password
|
||||||
|
private void logLDAPConfig(String fedProviderId, Map<String, String> ldapConfig) {
|
||||||
|
Map<String, String> copy = new HashMap<String, String>(ldapConfig);
|
||||||
|
copy.remove(LDAPConstants.BIND_CREDENTIAL);
|
||||||
|
logger.infof("Creating new LDAP based partition manager for the Federation provider: " + fedProviderId + ", LDAP Configuration: " + copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ldapConfig from realm
|
||||||
|
* @return PartitionManager instance based on LDAP store
|
||||||
|
*/
|
||||||
|
public static LDAPIdentityStore createLdapIdentityStore(Map<String,String> ldapConfig) {
|
||||||
|
Properties connectionProps = new Properties();
|
||||||
|
if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
|
||||||
|
connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1000");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
|
||||||
|
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
|
||||||
|
|
||||||
|
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
|
||||||
|
|
||||||
|
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
|
||||||
|
|
||||||
|
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
|
||||||
|
if (ldapLoginNameMapping == null) {
|
||||||
|
ldapLoginNameMapping = activeDirectory ? LDAPConstants.CN : LDAPConstants.UID;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ldapFirstNameMapping = activeDirectory ? "givenName" : LDAPConstants.CN;
|
||||||
|
String createTimestampMapping = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
|
||||||
|
String modifyTimestampMapping = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
|
||||||
|
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
|
||||||
|
|
||||||
|
boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
|
||||||
|
boolean userAccountControlsAfterPasswordUpdate = ldapConfig.containsKey(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE) ?
|
||||||
|
Boolean.parseBoolean(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)) : false;
|
||||||
|
|
||||||
|
// Differences of unique attribute among various vendors
|
||||||
|
String uniqueIdentifierAttributeName = LDAPConstants.ENTRY_UUID;
|
||||||
|
if (vendor != null) {
|
||||||
|
switch (vendor) {
|
||||||
|
case LDAPConstants.VENDOR_RHDS:
|
||||||
|
uniqueIdentifierAttributeName = "nsuniqueid";
|
||||||
|
break;
|
||||||
|
case LDAPConstants.VENDOR_TIVOLI:
|
||||||
|
uniqueIdentifierAttributeName = "uniqueidentifier";
|
||||||
|
break;
|
||||||
|
case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
|
||||||
|
uniqueIdentifierAttributeName = LDAPConstants.OBJECT_GUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LDAPIdentityStoreConfiguration ldapStoreConfig = new LDAPIdentityStoreConfiguration()
|
||||||
|
.setConnectionProperties(connectionProps)
|
||||||
|
.setBaseDN(ldapConfig.get(LDAPConstants.BASE_DN))
|
||||||
|
.setBindDN(ldapConfig.get(LDAPConstants.BIND_DN))
|
||||||
|
.setBindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
|
||||||
|
.setLdapURL(ldapConfig.get(LDAPConstants.CONNECTION_URL))
|
||||||
|
.setActiveDirectory(activeDirectory)
|
||||||
|
.setPagination(pagination)
|
||||||
|
.setUniqueIdentifierAttributeName(uniqueIdentifierAttributeName)
|
||||||
|
.setFactoryName("com.sun.jndi.ldap.LdapCtxFactory")
|
||||||
|
.setAuthType("simple")
|
||||||
|
.setUserAccountControlsAfterPasswordUpdate(userAccountControlsAfterPasswordUpdate);
|
||||||
|
|
||||||
|
LDAPMappingConfiguration ldapUserMappingConfig = ldapStoreConfig
|
||||||
|
.mappingConfig(LDAPUser.class)
|
||||||
|
.setBaseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
|
||||||
|
.setObjectClasses(new HashSet<String>(Arrays.asList(userObjectClasses)))
|
||||||
|
.setIdPropertyName("loginName")
|
||||||
|
.addAttributeMapping("loginName", ldapLoginNameMapping)
|
||||||
|
.addAttributeMapping("firstName", ldapFirstNameMapping)
|
||||||
|
.addAttributeMapping("lastName", LDAPConstants.SN)
|
||||||
|
.addAttributeMapping("email", LDAPConstants.EMAIL)
|
||||||
|
.addReadOnlyAttributeMapping("createdDate", createTimestampMapping)
|
||||||
|
.addReadOnlyAttributeMapping("modifyDate", modifyTimestampMapping);
|
||||||
|
|
||||||
|
if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
|
||||||
|
ldapUserMappingConfig.setBindingPropertyName("fullName");
|
||||||
|
ldapUserMappingConfig.addAttributeMapping("fullName", LDAPConstants.CN);
|
||||||
|
logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LDAPIdentityStore(ldapStoreConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkSystemProperty(String name, String defaultValue) {
|
||||||
|
if (System.getProperty(name) == null) {
|
||||||
|
System.setProperty(name, defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
|
||||||
|
private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
|
||||||
|
String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
|
||||||
|
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
|
||||||
|
|
||||||
|
String[] objectClasses = objClassesStr.split(",");
|
||||||
|
|
||||||
|
// Trim them
|
||||||
|
String[] userObjectClasses = new String[objectClasses.length];
|
||||||
|
for (int i=0 ; i<objectClasses.length ; i++) {
|
||||||
|
userObjectClasses[i] = objectClasses[i].trim();
|
||||||
|
}
|
||||||
|
return userObjectClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LDAPIdentityStoreContext {
|
||||||
|
|
||||||
|
private LDAPIdentityStoreContext(Map<String,String> config, LDAPIdentityStore store) {
|
||||||
|
this.config = config;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String,String> config;
|
||||||
|
private LDAPIdentityStore store;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,21 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.Attribute;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.picketlink.idm.IdentityManagementException;
|
import org.keycloak.models.UserModel;
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.credential.Credentials;
|
|
||||||
import org.picketlink.idm.credential.Password;
|
|
||||||
import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
|
||||||
import org.picketlink.idm.model.Attribute;
|
|
||||||
import org.picketlink.idm.model.basic.BasicModel;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
import org.picketlink.idm.query.AttributeParameter;
|
|
||||||
import org.picketlink.idm.query.QueryParameter;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow to directly call some operations against Picketlink IDM PartitionManager (hence LDAP).
|
* Allow to directly call some operations against LDAPIdentityStore.
|
||||||
|
* TODO: Is this class still needed?
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
|
@ -24,99 +23,102 @@ public class LDAPUtils {
|
||||||
|
|
||||||
public static QueryParameter MODIFY_DATE = new AttributeParameter("modifyDate");
|
public static QueryParameter MODIFY_DATE = new AttributeParameter("modifyDate");
|
||||||
|
|
||||||
public static User addUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
|
public static LDAPUser addUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
|
||||||
IdentityManager identityManager = getIdentityManager(partitionManager);
|
if (getUser(ldapIdentityStore, username) != null) {
|
||||||
|
|
||||||
if (BasicModel.getUser(identityManager, username) != null) {
|
|
||||||
throw new ModelDuplicateException("User with same username already exists");
|
throw new ModelDuplicateException("User with same username already exists");
|
||||||
}
|
}
|
||||||
if (getUserByEmail(identityManager, email) != null) {
|
if (getUserByEmail(ldapIdentityStore, email) != null) {
|
||||||
throw new ModelDuplicateException("User with same email already exists");
|
throw new ModelDuplicateException("User with same email already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
User picketlinkUser = new User(username);
|
LDAPUser ldapUser = new LDAPUser(username);
|
||||||
picketlinkUser.setFirstName(firstName);
|
ldapUser.setFirstName(firstName);
|
||||||
picketlinkUser.setLastName(lastName);
|
ldapUser.setLastName(lastName);
|
||||||
picketlinkUser.setEmail(email);
|
ldapUser.setEmail(email);
|
||||||
picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
|
ldapUser.setAttribute(new Attribute<String>("fullName", getFullName(username, firstName, lastName)));
|
||||||
identityManager.add(picketlinkUser);
|
ldapIdentityStore.add(ldapUser);
|
||||||
return picketlinkUser;
|
return ldapUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static User updateUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
|
public static LDAPUser updateUser(LDAPIdentityStore ldapIdentityStore, String username, String firstName, String lastName, String email) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
LDAPUser ldapUser = getUser(ldapIdentityStore, username);
|
||||||
User picketlinkUser = BasicModel.getUser(idmManager, username);
|
ldapUser.setFirstName(firstName);
|
||||||
picketlinkUser.setFirstName(firstName);
|
ldapUser.setLastName(lastName);
|
||||||
picketlinkUser.setLastName(lastName);
|
ldapUser.setEmail(email);
|
||||||
picketlinkUser.setEmail(email);
|
ldapIdentityStore.update(ldapUser);
|
||||||
idmManager.update(picketlinkUser);
|
return ldapUser;
|
||||||
return picketlinkUser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updatePassword(PartitionManager partitionManager, User picketlinkUser, String password) {
|
public static void updatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
LDAPUser ldapUser = convertUserForPasswordUpdate(user);
|
||||||
idmManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
|
|
||||||
|
ldapIdentityStore.updatePassword(ldapUser, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean validatePassword(PartitionManager partitionManager, String username, String password) {
|
public static void updatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
ldapIdentityStore.updatePassword(user, password);
|
||||||
|
|
||||||
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
|
|
||||||
credential.setUsername(username);
|
|
||||||
credential.setPassword(new Password(password.toCharArray()));
|
|
||||||
idmManager.validateCredentials(credential);
|
|
||||||
if (credential.getStatus() == Credentials.Status.VALID) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static User getUser(PartitionManager partitionManager, String username) {
|
public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, UserModel user, String password) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
LDAPUser ldapUser = convertUserForPasswordUpdate(user);
|
||||||
return BasicModel.getUser(idmManager, username);
|
|
||||||
|
return ldapIdentityStore.validatePassword(ldapUser, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validatePassword(LDAPIdentityStore ldapIdentityStore, LDAPUser user, String password) {
|
||||||
|
return ldapIdentityStore.validatePassword(user, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LDAPUser getUser(LDAPIdentityStore ldapIdentityStore, String username) {
|
||||||
|
return ldapIdentityStore.getUser(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put just username and entryDN as these are needed by LDAPIdentityStore for passwordUpdate
|
||||||
|
private static LDAPUser convertUserForPasswordUpdate(UserModel kcUser) {
|
||||||
|
LDAPUser ldapUser = new LDAPUser(kcUser.getUsername());
|
||||||
|
String ldapEntryDN = kcUser.getAttribute(LDAPConstants.LDAP_ENTRY_DN);
|
||||||
|
if (ldapEntryDN != null) {
|
||||||
|
ldapUser.setEntryDN(ldapEntryDN);
|
||||||
|
}
|
||||||
|
return ldapUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static User getUserByEmail(IdentityManager idmManager, String email) throws IdentityManagementException {
|
public static LDAPUser getUserByEmail(LDAPIdentityStore ldapIdentityStore, String email) {
|
||||||
List<User> agents = idmManager.createIdentityQuery(User.class)
|
IdentityQueryBuilder queryBuilder = ldapIdentityStore.createQueryBuilder();
|
||||||
.setParameter(User.EMAIL, email).getResultList();
|
IdentityQuery<LDAPUser> query = queryBuilder.createIdentityQuery(LDAPUser.class)
|
||||||
|
.where(queryBuilder.equal(LDAPUser.EMAIL, email));
|
||||||
|
List<LDAPUser> users = query.getResultList();
|
||||||
|
|
||||||
if (agents.isEmpty()) {
|
if (users.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
} else if (agents.size() == 1) {
|
} else if (users.size() == 1) {
|
||||||
return agents.get(0);
|
return users.get(0);
|
||||||
} else {
|
} else {
|
||||||
throw new IdentityManagementException("Error - multiple users found with same email");
|
throw new ModelDuplicateException("Error - multiple users found with same email " + email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean removeUser(PartitionManager partitionManager, String username) {
|
public static boolean removeUser(LDAPIdentityStore ldapIdentityStore, String username) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
LDAPUser ldapUser = getUser(ldapIdentityStore, username);
|
||||||
User picketlinkUser = BasicModel.getUser(idmManager, username);
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
idmManager.remove(picketlinkUser);
|
ldapIdentityStore.remove(ldapUser);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeAllUsers(PartitionManager partitionManager) {
|
public static void removeAllUsers(LDAPIdentityStore ldapIdentityStore) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
List<LDAPUser> allUsers = getAllUsers(ldapIdentityStore);
|
||||||
List<User> users = idmManager.createIdentityQuery(User.class).getResultList();
|
|
||||||
|
|
||||||
for (User user : users) {
|
for (LDAPUser user : allUsers) {
|
||||||
idmManager.remove(user);
|
ldapIdentityStore.remove(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<User> getAllUsers(PartitionManager partitionManager) {
|
public static List<LDAPUser> getAllUsers(LDAPIdentityStore ldapIdentityStore) {
|
||||||
IdentityManager idmManager = getIdentityManager(partitionManager);
|
IdentityQuery<LDAPUser> userQuery = ldapIdentityStore.createQueryBuilder().createIdentityQuery(LDAPUser.class);
|
||||||
return idmManager.createIdentityQuery(User.class).getResultList();
|
return userQuery.getResultList();
|
||||||
}
|
|
||||||
|
|
||||||
private static IdentityManager getIdentityManager(PartitionManager partitionManager) {
|
|
||||||
return partitionManager.createIdentityManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for ActiveDirectory updates
|
// Needed for ActiveDirectory updates
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
package org.keycloak.federation.ldap;
|
package org.keycloak.federation.ldap;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.UserModelDelegate;
|
import org.keycloak.models.utils.UserModelDelegate;
|
||||||
import org.picketlink.idm.IdentityManagementException;
|
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.credential.Password;
|
|
||||||
import org.picketlink.idm.credential.TOTPCredential;
|
|
||||||
import org.picketlink.idm.model.basic.BasicModel;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -28,52 +23,43 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUsername(String username) {
|
public void setUsername(String username) {
|
||||||
IdentityManager identityManager = provider.getIdentityManager();
|
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
|
||||||
|
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
throw new IllegalStateException("User not found in LDAP storage!");
|
throw new IllegalStateException("User not found in LDAP storage!");
|
||||||
}
|
}
|
||||||
picketlinkUser.setLoginName(username);
|
ldapUser.setLoginName(username);
|
||||||
identityManager.update(picketlinkUser);
|
ldapIdentityStore.update(ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw new ModelException(ie);
|
|
||||||
}
|
|
||||||
delegate.setUsername(username);
|
delegate.setUsername(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLastName(String lastName) {
|
public void setLastName(String lastName) {
|
||||||
IdentityManager identityManager = provider.getIdentityManager();
|
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
|
||||||
|
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
throw new IllegalStateException("User not found in LDAP storage!");
|
throw new IllegalStateException("User not found in LDAP storage!");
|
||||||
}
|
}
|
||||||
picketlinkUser.setLastName(lastName);
|
ldapUser.setLastName(lastName);
|
||||||
identityManager.update(picketlinkUser);
|
ldapIdentityStore.update(ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw new ModelException(ie);
|
|
||||||
}
|
|
||||||
delegate.setLastName(lastName);
|
delegate.setLastName(lastName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFirstName(String first) {
|
public void setFirstName(String first) {
|
||||||
IdentityManager identityManager = provider.getIdentityManager();
|
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
|
||||||
|
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
throw new IllegalStateException("User not found in LDAP storage!");
|
throw new IllegalStateException("User not found in LDAP storage!");
|
||||||
}
|
}
|
||||||
picketlinkUser.setFirstName(first);
|
ldapUser.setFirstName(first);
|
||||||
identityManager.update(picketlinkUser);
|
ldapIdentityStore.update(ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw new ModelException(ie);
|
|
||||||
}
|
|
||||||
delegate.setFirstName(first);
|
delegate.setFirstName(first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,41 +69,31 @@ public class WritableLDAPUserModelDelegate extends UserModelDelegate implements
|
||||||
delegate.updateCredential(cred);
|
delegate.updateCredential(cred);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IdentityManager identityManager = provider.getIdentityManager();
|
|
||||||
|
|
||||||
try {
|
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, getUsername());
|
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
|
||||||
if (picketlinkUser == null) {
|
if (ldapUser == null) {
|
||||||
logger.debugf("User '%s' doesn't exists. Skip password update", getUsername());
|
throw new IllegalStateException("User " + delegate.getUsername() + " not found in LDAP storage!");
|
||||||
throw new IllegalStateException("User doesn't exist in LDAP storage");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
identityManager.updateCredential(picketlinkUser, new Password(cred.getValue().toCharArray()));
|
LDAPUtils.updatePassword(ldapIdentityStore, delegate, cred.getValue());
|
||||||
} else if (cred.getType().equals(UserCredentialModel.TOTP)) {
|
} else {
|
||||||
TOTPCredential credential = new TOTPCredential(cred.getValue());
|
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
|
||||||
credential.setDevice(cred.getDevice());
|
|
||||||
identityManager.updateCredential(picketlinkUser, credential);
|
|
||||||
}
|
}
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw new ModelException(ie);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEmail(String email) {
|
public void setEmail(String email) {
|
||||||
IdentityManager identityManager = provider.getIdentityManager();
|
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
|
||||||
|
|
||||||
try {
|
LDAPUser ldapUser = LDAPUtils.getUser(ldapIdentityStore, delegate.getUsername());
|
||||||
User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
|
if (ldapUser == null) {
|
||||||
if (picketlinkUser == null) {
|
|
||||||
throw new IllegalStateException("User not found in LDAP storage!");
|
throw new IllegalStateException("User not found in LDAP storage!");
|
||||||
}
|
}
|
||||||
picketlinkUser.setEmail(email);
|
ldapUser.setEmail(email);
|
||||||
identityManager.update(picketlinkUser);
|
ldapIdentityStore.update(ldapUser);
|
||||||
} catch (IdentityManagementException ie) {
|
|
||||||
throw new ModelException(ie);
|
|
||||||
}
|
|
||||||
delegate.setEmail(email);
|
delegate.setEmail(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableCollection;
|
||||||
|
import static java.util.Collections.unmodifiableMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for all AttributedType implementations
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAttributedType implements AttributedType {
|
||||||
|
private static final long serialVersionUID = -6118293036241099199L;
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String entryDN;
|
||||||
|
|
||||||
|
private Map<String, Attribute<? extends Serializable>> attributes =
|
||||||
|
new HashMap<String, Attribute<? extends Serializable>>();
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntryDN() {
|
||||||
|
return entryDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntryDN(String entryDN) {
|
||||||
|
this.entryDN = entryDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttribute(Attribute<? extends Serializable> attribute) {
|
||||||
|
attributes.put(attribute.getName(), attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
attributes.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends Serializable> Attribute<T> getAttribute(String name) {
|
||||||
|
return (Attribute<T>) attributes.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Attribute<? extends Serializable>> getAttributes() {
|
||||||
|
return unmodifiableCollection(attributes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String,Attribute<? extends Serializable>> getAttributesMap() {
|
||||||
|
return unmodifiableMap(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getClass().isInstance(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributedType other = (AttributedType) obj;
|
||||||
|
|
||||||
|
return getId() != null && other.getId() != null && getId().equals(other.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = getId() != null ? getId().hashCode() : 0;
|
||||||
|
result = 31 * result + (getId() != null ? getId().hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for IdentityType implementations
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
public abstract class AbstractIdentityType extends AbstractAttributedType implements IdentityType {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2843998332737143820L;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
private Date createdDate = new Date();
|
||||||
|
private Date expirationDate = null;
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@AttributeProperty
|
||||||
|
public Date getExpirationDate() {
|
||||||
|
return this.expirationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExpirationDate(Date expirationDate) {
|
||||||
|
this.expirationDate = expirationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@AttributeProperty
|
||||||
|
public Date getCreatedDate() {
|
||||||
|
return this.createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreatedDate(Date createdDate) {
|
||||||
|
this.createdDate = createdDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getClass().isInstance(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityType other = (IdentityType) obj;
|
||||||
|
|
||||||
|
return (getId() != null && other.getId() != null)
|
||||||
|
&& (getId().equals(other.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an attribute value, a type of metadata that can be associated with an IdentityType
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public class Attribute<T extends Serializable> implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 237211288303510728L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the attribute
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attribute value.
|
||||||
|
*/
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether this Attribute has a read-only value
|
||||||
|
*/
|
||||||
|
private boolean readOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the Attribute value has been loaded
|
||||||
|
*/
|
||||||
|
private boolean loaded = false;
|
||||||
|
|
||||||
|
public Attribute(String name, T value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attribute(String name, T value, boolean readOnly) {
|
||||||
|
this(name, value);
|
||||||
|
this.readOnly = readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLoaded() {
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoaded(boolean value) {
|
||||||
|
this.loaded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value for this attribute. If the Attribute value is readOnly, a RuntimeException is thrown.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void setValue(T value) {
|
||||||
|
if (readOnly) {
|
||||||
|
throw new RuntimeException("Error setting Attribute value [" + name + " ] - value is read only.");
|
||||||
|
}
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks a property of an IdentityType, Partition or Relationship as being an attribute of that
|
||||||
|
* IdentityType, Partition or Relationship.
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
@Target({METHOD, FIELD})
|
||||||
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
public @interface AttributeProperty {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Managed properties are stored as ad-hoc attributes and mapped from and to a specific property of a type.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean managed() default false;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface AttributedType extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the id value.
|
||||||
|
*/
|
||||||
|
QueryParameter ID = new AttributeParameter("id");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unique identifier for this instance
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the unique identifier for this instance
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
void setId(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the specified attribute. This operation will overwrite any previous value.
|
||||||
|
*
|
||||||
|
* @param attribute to be set
|
||||||
|
*/
|
||||||
|
void setAttribute(Attribute<? extends Serializable> attribute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the attribute with given name
|
||||||
|
*
|
||||||
|
* @param name of attribute
|
||||||
|
*/
|
||||||
|
void removeAttribute(String name);
|
||||||
|
|
||||||
|
|
||||||
|
// LDAP specific stuff
|
||||||
|
void setEntryDN(String entryDN);
|
||||||
|
String getEntryDN();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the attribute value with the specified name
|
||||||
|
*
|
||||||
|
* @param name of attribute
|
||||||
|
* @return attribute value or null if attribute with given name doesn't exist. If given attribute has many values method
|
||||||
|
* will return first one
|
||||||
|
*/
|
||||||
|
<T extends Serializable> Attribute<T> getAttribute(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Map containing all attribute values for this IdentityType instance.
|
||||||
|
*
|
||||||
|
* @return map of attribute names and their values
|
||||||
|
*/
|
||||||
|
Collection<Attribute<? extends Serializable>> getAttributes();
|
||||||
|
|
||||||
|
public final class QUERY_ATTRIBUTE {
|
||||||
|
public static AttributeParameter byName(String name) {
|
||||||
|
return new AttributeParameter(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface is the base for all identity model objects. It declares a number of
|
||||||
|
* properties that must be supported by all identity types, in addition to defining the API
|
||||||
|
* for identity attribute management.
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
public interface IdentityType extends AttributedType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the enabled value.
|
||||||
|
*/
|
||||||
|
QueryParameter ENABLED = new AttributeParameter("enabled");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the createdDate value
|
||||||
|
*/
|
||||||
|
QueryParameter CREATED_DATE = new AttributeParameter("createdDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the created after date
|
||||||
|
*/
|
||||||
|
QueryParameter CREATED_AFTER = new AttributeParameter("createdDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the modified after date
|
||||||
|
*/
|
||||||
|
QueryParameter MODIFIED_AFTER = new AttributeParameter("modifyDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the created before date
|
||||||
|
*/
|
||||||
|
QueryParameter CREATED_BEFORE = new AttributeParameter("createdDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the expiryDate value
|
||||||
|
*/
|
||||||
|
QueryParameter EXPIRY_DATE = new AttributeParameter("expirationDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the expiration after date
|
||||||
|
*/
|
||||||
|
QueryParameter EXPIRY_AFTER = new AttributeParameter("expirationDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the expiration before date
|
||||||
|
*/
|
||||||
|
QueryParameter EXPIRY_BEFORE = new AttributeParameter("expirationDate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the current enabled status of this IdentityType.
|
||||||
|
*
|
||||||
|
* @return A boolean value indicating whether this IdentityType is enabled.
|
||||||
|
*/
|
||||||
|
boolean isEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the current enabled status of this {@link IdentityType}.</p>
|
||||||
|
*
|
||||||
|
* @param enabled
|
||||||
|
*/
|
||||||
|
void setEnabled(boolean enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the date that this IdentityType instance was created.
|
||||||
|
*
|
||||||
|
* @return Date value representing the creation date
|
||||||
|
*/
|
||||||
|
Date getCreatedDate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the date that this {@link IdentityType} was created.</p>
|
||||||
|
*
|
||||||
|
* @param createdDate
|
||||||
|
*/
|
||||||
|
void setCreatedDate(Date createdDate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the date that this IdentityType expires, or null if there is no expiry date.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Date getExpirationDate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the date that this {@link IdentityType} expires.</p>
|
||||||
|
*
|
||||||
|
* @param expirationDate
|
||||||
|
*/
|
||||||
|
void setExpirationDate(Date expirationDate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.model;
|
||||||
|
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a User; a human agent that may authenticate with the application
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
public class LDAPUser extends AbstractIdentityType {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4117586097100398485L;
|
||||||
|
|
||||||
|
public static final QueryParameter LOGIN_NAME = AttributedType.QUERY_ATTRIBUTE.byName("loginName");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the firstName value.
|
||||||
|
*/
|
||||||
|
public static final QueryParameter FIRST_NAME = QUERY_ATTRIBUTE.byName("firstName");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the lastName value.
|
||||||
|
*/
|
||||||
|
public static final QueryParameter LAST_NAME = QUERY_ATTRIBUTE.byName("lastName");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query parameter used to set the email value.
|
||||||
|
*/
|
||||||
|
public static final QueryParameter EMAIL = QUERY_ATTRIBUTE.byName("email");
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
private String loginName;
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
@AttributeProperty
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
public LDAPUser() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPUser(String loginName) {
|
||||||
|
this.loginName = loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginName() {
|
||||||
|
return loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginName(String loginName) {
|
||||||
|
this.loginName = loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return this.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This class can be used to define a query parameter for properties annotated with
|
||||||
|
* {@link org.keycloak.federation.ldap.idm.model.AttributeProperty}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author pedroigor
|
||||||
|
*/
|
||||||
|
public class AttributeParameter implements QueryParameter {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public AttributeParameter(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@link Condition} is used to specify how a specific {@link QueryParameter}
|
||||||
|
* is defined in order to filter query results.</p>
|
||||||
|
*
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public interface Condition {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The {@link QueryParameter} restricted by this condition.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
QueryParameter getParameter();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>An {@link IdentityQuery} is responsible for querying the underlying identity stores for instances of
|
||||||
|
* a given {@link IdentityType}.</p>
|
||||||
|
*
|
||||||
|
* <p>Instances of this class are obtained using the {@link IdentityQueryBuilder#createIdentityQuery(Class)}
|
||||||
|
* method.</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* IdentityManager identityManager = getIdentityManager();
|
||||||
|
*
|
||||||
|
* // here we get the query builder
|
||||||
|
* IdentityQueryBuilder builder = identityManager.getQueryBuilder();
|
||||||
|
*
|
||||||
|
* // create a condition
|
||||||
|
* Condition condition = builder.equal(User.LOGIN_NAME, "john");
|
||||||
|
*
|
||||||
|
* // create a query for a specific identity type using the previously created condition
|
||||||
|
* IdentityQuery query = builder.createIdentityQuery(User.class).where(condition);
|
||||||
|
*
|
||||||
|
* // execute the query
|
||||||
|
* List<User> result = query.getResultList();
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>When preparing a query you may want to create conditions to filter its results and configure how they must be retrieved.
|
||||||
|
* For that, you can use the {@link IdentityQueryBuilder}, which provides useful methods for creating
|
||||||
|
* different expressions and conditions.</p>
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public interface IdentityQuery<T extends IdentityType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #setPaginationContext(Object object)
|
||||||
|
*/
|
||||||
|
Object getPaginationContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for pagination models like LDAP when search will return some object (like cookie) for searching on next page
|
||||||
|
*
|
||||||
|
* @param object to be used for search next page
|
||||||
|
*
|
||||||
|
* @return this query
|
||||||
|
*/
|
||||||
|
IdentityQuery<T> setPaginationContext(Object object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Will be removed soon.
|
||||||
|
*
|
||||||
|
* @see #setSortParameters(QueryParameter...)
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
QueryParameter[] getSortParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters used to sort the results. First parameter has biggest priority. For example: setSortParameter(User.LAST_NAME,
|
||||||
|
* User.FIRST_NAME) means that results will be sorted primarily by lastName and firstName will be used to sort only records with
|
||||||
|
* same lastName
|
||||||
|
*
|
||||||
|
* @param sortParameters parameters to specify sort criteria
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions
|
||||||
|
* from the {@link IdentityQueryBuilder}.
|
||||||
|
*
|
||||||
|
* @return this query
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
IdentityQuery<T> setSortParameters(QueryParameter... sortParameters);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link IdentityQuery#getSorting()} for a list of sorting conditions. Will be removed soon.
|
||||||
|
*
|
||||||
|
* @return true if sorting will be ascending
|
||||||
|
*
|
||||||
|
* @see #setSortAscending(boolean)
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
boolean isSortAscending();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify if sorting will be ascending (true) or descending (false)
|
||||||
|
*
|
||||||
|
* @param sortAscending to specify if sorting will be ascending or descending
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link IdentityQuery#sortBy(Sort...)} instead. Where you can create sort conditions
|
||||||
|
* from the {@link IdentityQueryBuilder}.
|
||||||
|
*
|
||||||
|
* @return this query
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
IdentityQuery<T> setSortAscending(boolean sortAscending);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Set a query parameter to this query in order to filter the results.</p>
|
||||||
|
*
|
||||||
|
* <p>This method always create an equality condition. For more conditions options take a look at {@link
|
||||||
|
* IdentityQueryBuilder} and use the {@link IdentityQuery#where(Condition...)}
|
||||||
|
* instead.</p>
|
||||||
|
*
|
||||||
|
* @param param The query parameter.
|
||||||
|
* @param value The value to match for equality.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link IdentityQuery#where(Condition...)} to specify query conditions.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
IdentityQuery<T> setParameter(QueryParameter param, Object... value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Add to this query the conditions that will be used to filter results.</p>
|
||||||
|
*
|
||||||
|
* <p>Any condition previously added to this query will be preserved and the new conditions added. If you want to clear the
|
||||||
|
* conditions you must create a new query instance.</p>
|
||||||
|
*
|
||||||
|
* @param condition One or more conditions created from {@link IdentityQueryBuilder}.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IdentityQuery<T> where(Condition... condition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Add to this query the sorting conditions to be applied to the results.</p>
|
||||||
|
*
|
||||||
|
* @param sorts The ordering conditions.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IdentityQuery<T> sortBy(Sort... sorts);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The type used to create this query.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Class<T> getIdentityType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns a map with all the parameter set for this query.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link IdentityQuery#getConditions()} instead. Will be removed.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Map<QueryParameter, Object[]> getParameters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns a set containing all conditions used by this query to filter its results.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Set<Condition> getConditions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns a set containing all sorting conditions used to filter the results.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Set<Sort> getSorting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns the value used to restrict the given query parameter.</p>
|
||||||
|
*
|
||||||
|
* @param queryParameter
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
Object[] getParameter(QueryParameter queryParameter);
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
Map<QueryParameter, Object[]> getParameters(Class<?> type);
|
||||||
|
|
||||||
|
int getOffset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Set the position of the first result to retrieve.</p>
|
||||||
|
*
|
||||||
|
* @param offset
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IdentityQuery<T> setOffset(int offset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Returns the number of instances to retrieve.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getLimit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Set the maximum number of results to retrieve.</p>
|
||||||
|
*
|
||||||
|
* @param limit the number of instances to retrieve.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
IdentityQuery<T> setLimit(int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Execute the query against the underlying identity stores and returns a list containing all instances of
|
||||||
|
* the type (defined when creating this query instance) that match the conditions previously specified.</p>
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<T> getResultList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of all query results. It takes into account query parameters, but it doesn't take into account pagination parameter
|
||||||
|
* like offset and limit
|
||||||
|
*
|
||||||
|
* @return count of all query results
|
||||||
|
*/
|
||||||
|
int getResultCount();
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>The {@link IdentityQueryBuilder} is responsible for creating {@link IdentityQuery} instances and also
|
||||||
|
* provide methods to create conditions, orderings, sorting, etc.</p>
|
||||||
|
*
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public interface IdentityQueryBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing the whether the query parameter satisfies the given pattern..</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param pattern The pattern to match.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition like(QueryParameter parameter, String pattern);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing the arguments for equality.</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param value The value to compare.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition equal(QueryParameter parameter, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is grater than the given value..</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param x The value to compare.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition greaterThan(QueryParameter parameter, Object x);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is grater than or equal to the given value..</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param x The value to compare.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition greaterThanOrEqualTo(QueryParameter parameter, Object x);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is less than the given value..</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param x The value to compare.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition lessThan(QueryParameter parameter, Object x);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is less than or equal to the given value..</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param x The value to compare.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition lessThanOrEqualTo(QueryParameter parameter, Object x);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is between the given values.</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param x The first value.
|
||||||
|
* @param x The second value.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition between(QueryParameter parameter, Object x, Object y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a condition for testing whether the query parameter is contained in a list of values.</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter.
|
||||||
|
* @param values A list of values.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Condition in(QueryParameter parameter, Object... values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create an ascending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
|
||||||
|
* query.</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter to sort.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Sort asc(QueryParameter parameter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create an descending order for the given <code>parameter</code>. Once created, you can use it to sort the results of a
|
||||||
|
* query.</p>
|
||||||
|
*
|
||||||
|
* @param parameter The query parameter to sort.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Sort desc(QueryParameter parameter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p> Create an {@link IdentityQuery} that can be used to query for {@link
|
||||||
|
* IdentityType} instances of a the given <code>identityType</code>. </p>
|
||||||
|
*
|
||||||
|
* @param identityType The type to search. If you provide the {@link IdentityType}
|
||||||
|
* base interface any of its sub-types will be returned.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
<T extends IdentityType> IdentityQuery<T> createIdentityQuery(Class<T> identityType);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A marker interface indicating that the implementing class can be used as a
|
||||||
|
* parameter within an IdentityQuery or RelationshipQuery
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface QueryParameter {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class Sort {
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final boolean asc;
|
||||||
|
|
||||||
|
public Sort(QueryParameter parameter, boolean asc) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.asc = asc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAscending() {
|
||||||
|
return asc;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class BetweenCondition implements Condition {
|
||||||
|
|
||||||
|
private final Comparable x;
|
||||||
|
private final Comparable y;
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
|
||||||
|
public BetweenCondition(QueryParameter parameter, Comparable x, Comparable y) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparable getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparable getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Sort;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.IdentityStore;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default IdentityQuery implementation.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
public class DefaultIdentityQuery<T extends IdentityType> implements IdentityQuery<T> {
|
||||||
|
|
||||||
|
private final Map<QueryParameter, Object[]> parameters = new LinkedHashMap<QueryParameter, Object[]>();
|
||||||
|
private final Class<T> identityType;
|
||||||
|
private final IdentityStore identityStore;
|
||||||
|
private final IdentityQueryBuilder queryBuilder;
|
||||||
|
private int offset;
|
||||||
|
private int limit;
|
||||||
|
private Object paginationContext;
|
||||||
|
private QueryParameter[] sortParameters;
|
||||||
|
private boolean sortAscending = true;
|
||||||
|
private final Set<Condition> conditions = new LinkedHashSet<Condition>();
|
||||||
|
private final Set<Sort> ordering = new LinkedHashSet<Sort>();
|
||||||
|
|
||||||
|
public DefaultIdentityQuery(IdentityQueryBuilder queryBuilder, Class<T> identityType, IdentityStore identityStore) {
|
||||||
|
this.queryBuilder = queryBuilder;
|
||||||
|
this.identityStore = identityStore;
|
||||||
|
this.identityType = identityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setParameter(QueryParameter queryParameter, Object... value) {
|
||||||
|
if (value == null || value.length == 0) {
|
||||||
|
throw new ModelException("Query Parameter values null or empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.put(queryParameter, value);
|
||||||
|
|
||||||
|
if (IdentityType.CREATED_AFTER.equals(queryParameter) || IdentityType.EXPIRY_AFTER.equals(queryParameter)) {
|
||||||
|
this.conditions.add(queryBuilder.greaterThanOrEqualTo(queryParameter, value[0]));
|
||||||
|
} else if (IdentityType.CREATED_BEFORE.equals(queryParameter) || IdentityType.EXPIRY_BEFORE.equals(queryParameter)) {
|
||||||
|
this.conditions.add(queryBuilder.lessThanOrEqualTo(queryParameter, value[0]));
|
||||||
|
} else {
|
||||||
|
this.conditions.add(queryBuilder.equal(queryParameter, value[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> where(Condition... condition) {
|
||||||
|
this.conditions.addAll(Arrays.asList(condition));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> sortBy(Sort... sorts) {
|
||||||
|
this.ordering.addAll(Arrays.asList(sorts));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Sort> getSorting() {
|
||||||
|
return unmodifiableSet(this.ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<T> getIdentityType() {
|
||||||
|
return identityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<QueryParameter, Object[]> getParameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getParameter(QueryParameter queryParameter) {
|
||||||
|
return this.parameters.get(queryParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<QueryParameter, Object[]> getParameters(Class<?> type) {
|
||||||
|
Map<QueryParameter, Object[]> typedParameters = new HashMap<QueryParameter, Object[]>();
|
||||||
|
|
||||||
|
Set<Map.Entry<QueryParameter, Object[]>> entrySet = this.parameters.entrySet();
|
||||||
|
|
||||||
|
for (Map.Entry<QueryParameter, Object[]> entry : entrySet) {
|
||||||
|
if (type.isInstance(entry.getKey())) {
|
||||||
|
typedParameters.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typedParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPaginationContext() {
|
||||||
|
return paginationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter[] getSortParameters() {
|
||||||
|
return sortParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSortAscending() {
|
||||||
|
return sortAscending;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> getResultList() {
|
||||||
|
|
||||||
|
// remove this statement once deprecated methods on IdentityQuery are removed
|
||||||
|
if (this.sortParameters != null) {
|
||||||
|
for (QueryParameter parameter : this.sortParameters) {
|
||||||
|
if (isSortAscending()) {
|
||||||
|
sortBy(this.queryBuilder.asc(parameter));
|
||||||
|
} else {
|
||||||
|
sortBy(this.queryBuilder.desc(parameter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<T> result = new ArrayList<T>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (T identityType : identityStore.fetchQueryResults(this)) {
|
||||||
|
result.add(identityType);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("LDAP Query failed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResultCount() {
|
||||||
|
return identityStore.countQueryResults(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setOffset(int offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setLimit(int limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setSortParameters(QueryParameter... sortParameters) {
|
||||||
|
this.sortParameters = sortParameters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setSortAscending(boolean sortAscending) {
|
||||||
|
this.sortAscending = sortAscending;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityQuery<T> setPaginationContext(Object object) {
|
||||||
|
this.paginationContext = object;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Condition> getConditions() {
|
||||||
|
return unmodifiableSet(this.conditions);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Sort;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.IdentityStore;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class DefaultQueryBuilder implements IdentityQueryBuilder {
|
||||||
|
|
||||||
|
private final IdentityStore identityStore;
|
||||||
|
|
||||||
|
public DefaultQueryBuilder(IdentityStore identityStore) {
|
||||||
|
this.identityStore = identityStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition like(QueryParameter parameter, String pattern) {
|
||||||
|
return new LikeCondition(parameter, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition equal(QueryParameter parameter, Object value) {
|
||||||
|
return new EqualCondition(parameter, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition greaterThan(QueryParameter parameter, Object x) {
|
||||||
|
throwExceptionIfNotComparable(x);
|
||||||
|
return new GreaterThanCondition(parameter, (Comparable) x, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition greaterThanOrEqualTo(QueryParameter parameter, Object x) {
|
||||||
|
throwExceptionIfNotComparable(x);
|
||||||
|
return new GreaterThanCondition(parameter, (Comparable) x, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition lessThan(QueryParameter parameter, Object x) {
|
||||||
|
throwExceptionIfNotComparable(x);
|
||||||
|
return new LessThanCondition(parameter, (Comparable) x, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition lessThanOrEqualTo(QueryParameter parameter, Object x) {
|
||||||
|
throwExceptionIfNotComparable(x);
|
||||||
|
return new LessThanCondition(parameter, (Comparable) x, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition between(QueryParameter parameter, Object x, Object y) {
|
||||||
|
throwExceptionIfNotComparable(x);
|
||||||
|
throwExceptionIfNotComparable(y);
|
||||||
|
return new BetweenCondition(parameter, (Comparable) x, (Comparable) y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Condition in(QueryParameter parameter, Object... x) {
|
||||||
|
return new InCondition(parameter, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort asc(QueryParameter parameter) {
|
||||||
|
return new Sort(parameter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sort desc(QueryParameter parameter) {
|
||||||
|
return new Sort(parameter, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends IdentityType> IdentityQuery createIdentityQuery(Class<T> identityType) {
|
||||||
|
return new DefaultIdentityQuery(this, identityType, this.identityStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwExceptionIfNotComparable(Object x) {
|
||||||
|
if (!Comparable.class.isInstance(x)) {
|
||||||
|
throw new ModelException("Query parameter value [" + x + "] must be " + Comparable.class + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class EqualCondition implements Condition {
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
public EqualCondition(QueryParameter parameter, Object value) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EqualCondition{" +
|
||||||
|
"parameter=" + ((AttributeParameter) parameter).getName() +
|
||||||
|
", value=" + value +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class GreaterThanCondition implements Condition {
|
||||||
|
|
||||||
|
private final boolean orEqual;
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final Comparable value;
|
||||||
|
|
||||||
|
public GreaterThanCondition(QueryParameter parameter, Comparable value, boolean orEqual) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
|
this.orEqual = orEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparable getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOrEqual() {
|
||||||
|
return this.orEqual;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class InCondition implements Condition {
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final Object[] value;
|
||||||
|
|
||||||
|
public InCondition(QueryParameter parameter, Object[] value) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class LessThanCondition implements Condition {
|
||||||
|
|
||||||
|
private final boolean orEqual;
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final Comparable value;
|
||||||
|
|
||||||
|
public LessThanCondition(QueryParameter parameter, Comparable value, boolean orEqual) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
|
this.orEqual = orEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparable getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOrEqual() {
|
||||||
|
return this.orEqual;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.query.internal;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class LikeCondition implements Condition {
|
||||||
|
|
||||||
|
private final QueryParameter parameter;
|
||||||
|
private final Object value;
|
||||||
|
|
||||||
|
public LikeCondition(QueryParameter parameter, Object value) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryParameter getParameter() {
|
||||||
|
return this.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStoreConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IdentityStore representation providing minimal SPI
|
||||||
|
*
|
||||||
|
* TODO: Rather remove this abstraction
|
||||||
|
*
|
||||||
|
* @author Boleslaw Dawidowicz
|
||||||
|
* @author Shane Bryzak
|
||||||
|
*/
|
||||||
|
public interface IdentityStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configuration for this IdentityStore instance
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
LDAPIdentityStoreConfiguration getConfig();
|
||||||
|
|
||||||
|
// General
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the specified IdentityType
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
void add(AttributedType value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the specified IdentityType
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
void update(AttributedType value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified IdentityType
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
void remove(AttributedType value);
|
||||||
|
|
||||||
|
// Identity query
|
||||||
|
|
||||||
|
<V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery);
|
||||||
|
|
||||||
|
<V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery);
|
||||||
|
|
||||||
|
// // Relationship query
|
||||||
|
//
|
||||||
|
// <V extends Relationship> List<V> fetchQueryResults(RelationshipQuery<V> query);
|
||||||
|
//
|
||||||
|
// <V extends Relationship> int countQueryResults(RelationshipQuery<V> query);
|
||||||
|
|
||||||
|
// Credentials
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the specified credentials.
|
||||||
|
*
|
||||||
|
* @param user Keycloak user
|
||||||
|
* @param password Ldap password
|
||||||
|
*/
|
||||||
|
boolean validatePassword(LDAPUser user, String password);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the specified credential value.
|
||||||
|
*
|
||||||
|
* @param user Keycloak user
|
||||||
|
* @param password Ldap password
|
||||||
|
*/
|
||||||
|
void updatePassword(LDAPUser user, String password);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,761 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.BasicAttribute;
|
||||||
|
import javax.naming.directory.BasicAttributes;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.ModificationItem;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.AttributeParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.Condition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.QueryParameter;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.BetweenCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.DefaultQueryBuilder;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.GreaterThanCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.InCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.LessThanCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.internal.LikeCondition;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.IdentityStore;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
|
||||||
|
import org.keycloak.models.utils.reflection.Property;
|
||||||
|
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||||
|
import org.keycloak.models.utils.reflection.TypedPropertyCriteria;
|
||||||
|
import org.keycloak.util.reflections.Reflections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IdentityStore implementation backed by an LDAP directory
|
||||||
|
*
|
||||||
|
* @author Shane Bryzak
|
||||||
|
* @author Anil Saldhana
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
|
||||||
|
*/
|
||||||
|
public class LDAPIdentityStore implements IdentityStore {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(LDAPIdentityStore.class);
|
||||||
|
|
||||||
|
public static final String EMPTY_ATTRIBUTE_VALUE = " ";
|
||||||
|
|
||||||
|
private final LDAPIdentityStoreConfiguration config;
|
||||||
|
private final LDAPOperationManager operationManager;
|
||||||
|
|
||||||
|
public LDAPIdentityStore(LDAPIdentityStoreConfiguration config) {
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.operationManager = new LDAPOperationManager(getConfig());
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new ModelException("Couldn't init operation manager", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LDAPIdentityStoreConfiguration getConfig() {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(AttributedType attributedType) {
|
||||||
|
// id will be assigned by the ldap server
|
||||||
|
attributedType.setId(null);
|
||||||
|
|
||||||
|
String entryDN = getBindingDN(attributedType, true);
|
||||||
|
this.operationManager.createSubContext(entryDN, extractAttributes(attributedType, true));
|
||||||
|
addToParentAsMember(attributedType);
|
||||||
|
attributedType.setId(getEntryIdentifier(attributedType));
|
||||||
|
|
||||||
|
attributedType.setEntryDN(entryDN);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Type with identifier [%s] successfully added to identity store [%s].", attributedType.getId(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(AttributedType attributedType) {
|
||||||
|
BasicAttributes updatedAttributes = extractAttributes(attributedType, false);
|
||||||
|
NamingEnumeration<Attribute> attributes = updatedAttributes.getAll();
|
||||||
|
|
||||||
|
this.operationManager.modifyAttributes(getBindingDN(attributedType, true), attributes);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Type with identifier [%s] successfully updated to identity store [%s].", attributedType.getId(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(AttributedType attributedType) {
|
||||||
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
|
this.operationManager.removeEntryById(getBaseDN(attributedType), attributedType.getId(), mappingConfig);
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Type with identifier [%s] successfully removed from identity store [%s].", attributedType.getId(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V extends IdentityType> List<V> fetchQueryResults(IdentityQuery<V> identityQuery) {
|
||||||
|
List<V> results = new ArrayList<V>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
|
||||||
|
throw new ModelException("LDAP Identity Store does not support sorted queries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Condition condition : identityQuery.getConditions()) {
|
||||||
|
|
||||||
|
if (IdentityType.ID.equals(condition.getParameter())) {
|
||||||
|
if (EqualCondition.class.isInstance(condition)) {
|
||||||
|
EqualCondition equalCondition = (EqualCondition) condition;
|
||||||
|
SearchResult search = this.operationManager
|
||||||
|
.lookupById(getConfig().getBaseDN(), equalCondition.getValue().toString(), null);
|
||||||
|
|
||||||
|
if (search != null) {
|
||||||
|
results.add((V) populateAttributedType(search, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IdentityType.class.equals(identityQuery.getIdentityType())) {
|
||||||
|
// the ldap store does not support queries based on root types. Except if based on the identifier.
|
||||||
|
LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(identityQuery.getIdentityType());
|
||||||
|
StringBuilder filter = createIdentityTypeSearchFilter(identityQuery, ldapEntryConfig);
|
||||||
|
String baseDN = getBaseDN(ldapEntryConfig);
|
||||||
|
List<SearchResult> search;
|
||||||
|
|
||||||
|
if (getConfig().isPagination() && identityQuery.getLimit() > 0) {
|
||||||
|
search = this.operationManager.searchPaginated(baseDN, filter.toString(), ldapEntryConfig, identityQuery);
|
||||||
|
} else {
|
||||||
|
search = this.operationManager.search(baseDN, filter.toString(), ldapEntryConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SearchResult result : search) {
|
||||||
|
if (!result.getNameInNamespace().equals(baseDN)) {
|
||||||
|
results.add((V) populateAttributedType(result, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("Querying of identity type failed " + identityQuery, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V extends IdentityType> int countQueryResults(IdentityQuery<V> identityQuery) {
|
||||||
|
int limit = identityQuery.getLimit();
|
||||||
|
int offset = identityQuery.getOffset();
|
||||||
|
|
||||||
|
identityQuery.setLimit(0);
|
||||||
|
identityQuery.setOffset(0);
|
||||||
|
|
||||||
|
int resultCount = identityQuery.getResultList().size();
|
||||||
|
|
||||||
|
identityQuery.setLimit(limit);
|
||||||
|
identityQuery.setOffset(offset);
|
||||||
|
|
||||||
|
return resultCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityQueryBuilder createQueryBuilder() {
|
||||||
|
return new DefaultQueryBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// *************** CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validatePassword(LDAPUser user, String password) {
|
||||||
|
String userDN = getEntryDNOfUser(user);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Using DN [%s] for authentication of user [%s]", userDN, user.getLoginName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operationManager.authenticate(userDN, password)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePassword(LDAPUser user, String password) {
|
||||||
|
String userDN = getEntryDNOfUser(user);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Using DN [%s] for updating LDAP password of user [%s]", userDN, user.getLoginName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getConfig().isActiveDirectory()) {
|
||||||
|
updateADPassword(userDN, password);
|
||||||
|
} else {
|
||||||
|
ModificationItem[] mods = new ModificationItem[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicAttribute mod0 = new BasicAttribute(LDAPConstants.USER_PASSWORD_ATTRIBUTE, password);
|
||||||
|
|
||||||
|
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
|
||||||
|
|
||||||
|
operationManager.modifyAttribute(userDN, mod0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("Error updating password.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void updateADPassword(String userDN, String password) {
|
||||||
|
try {
|
||||||
|
// Replace the "unicdodePwd" attribute with a new value
|
||||||
|
// Password must be both Unicode and a quoted string
|
||||||
|
String newQuotedPassword = "\"" + password + "\"";
|
||||||
|
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
|
||||||
|
|
||||||
|
BasicAttribute unicodePwd = new BasicAttribute("unicodePwd", newUnicodePassword);
|
||||||
|
|
||||||
|
List<ModificationItem> modItems = new ArrayList<ModificationItem>();
|
||||||
|
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
|
||||||
|
|
||||||
|
// Used in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
|
||||||
|
if (getConfig().isUserAccountControlsAfterPasswordUpdate()) {
|
||||||
|
BasicAttribute userAccountControl = new BasicAttribute("userAccountControl", "512");
|
||||||
|
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userAccountControl));
|
||||||
|
|
||||||
|
logger.debugf("Attribute userAccountControls will be switched to 512 after password update of user [%s]", userDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getEntryDNOfUser(LDAPUser user) {
|
||||||
|
// First try if user already has entryDN on him
|
||||||
|
String entryDN = user.getEntryDN();
|
||||||
|
if (entryDN != null) {
|
||||||
|
return entryDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to find user in LDAP
|
||||||
|
String username = user.getLoginName();
|
||||||
|
user = getUser(username);
|
||||||
|
if (user == null) {
|
||||||
|
throw new ModelException("No LDAP user found with username " + username);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.getEntryDN();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LDAPUser getUser(String username) {
|
||||||
|
|
||||||
|
if (isNullOrEmpty(username)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentityQueryBuilder queryBuilder = createQueryBuilder();
|
||||||
|
List<LDAPUser> agents = queryBuilder.createIdentityQuery(LDAPUser.class)
|
||||||
|
.where(queryBuilder.equal(LDAPUser.LOGIN_NAME, username)).getResultList();
|
||||||
|
|
||||||
|
if (agents.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else if (agents.size() == 1) {
|
||||||
|
return agents.get(0);
|
||||||
|
} else {
|
||||||
|
throw new ModelDuplicateException("Error - multiple Agent objects found with same login name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************ END CREDENTIALS AND USER SPECIFIC STUFF
|
||||||
|
|
||||||
|
|
||||||
|
private String getBaseDN(final LDAPMappingConfiguration ldapEntryConfig) {
|
||||||
|
String baseDN = getConfig().getBaseDN();
|
||||||
|
|
||||||
|
if (ldapEntryConfig.getBaseDN() != null) {
|
||||||
|
baseDN = ldapEntryConfig.getBaseDN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <V extends IdentityType> StringBuilder createIdentityTypeSearchFilter(final IdentityQuery<V> identityQuery, final LDAPMappingConfiguration ldapEntryConfig) {
|
||||||
|
StringBuilder filter = new StringBuilder();
|
||||||
|
|
||||||
|
for (Condition condition : identityQuery.getConditions()) {
|
||||||
|
QueryParameter queryParameter = condition.getParameter();
|
||||||
|
|
||||||
|
if (!IdentityType.ID.equals(queryParameter)) {
|
||||||
|
if (AttributeParameter.class.isInstance(queryParameter)) {
|
||||||
|
AttributeParameter attributeParameter = (AttributeParameter) queryParameter;
|
||||||
|
String attributeName = ldapEntryConfig.getMappedProperties().get(attributeParameter.getName());
|
||||||
|
|
||||||
|
if (attributeName != null) {
|
||||||
|
if (EqualCondition.class.isInstance(condition)) {
|
||||||
|
EqualCondition equalCondition = (EqualCondition) condition;
|
||||||
|
Object parameterValue = equalCondition.getValue();
|
||||||
|
|
||||||
|
if (Date.class.isInstance(parameterValue)) {
|
||||||
|
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(parameterValue).append(")");
|
||||||
|
} else if (LikeCondition.class.isInstance(condition)) {
|
||||||
|
LikeCondition likeCondition = (LikeCondition) condition;
|
||||||
|
String parameterValue = (String) likeCondition.getValue();
|
||||||
|
|
||||||
|
} else if (GreaterThanCondition.class.isInstance(condition)) {
|
||||||
|
GreaterThanCondition greaterThanCondition = (GreaterThanCondition) condition;
|
||||||
|
Comparable parameterValue = (Comparable) greaterThanCondition.getValue();
|
||||||
|
|
||||||
|
if (Date.class.isInstance(parameterValue)) {
|
||||||
|
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (greaterThanCondition.isOrEqual()) {
|
||||||
|
filter.append("(").append(attributeName).append(">=").append(parameterValue).append(")");
|
||||||
|
} else {
|
||||||
|
filter.append("(").append(attributeName).append(">").append(parameterValue).append(")");
|
||||||
|
}
|
||||||
|
} else if (LessThanCondition.class.isInstance(condition)) {
|
||||||
|
LessThanCondition lessThanCondition = (LessThanCondition) condition;
|
||||||
|
Comparable parameterValue = (Comparable) lessThanCondition.getValue();
|
||||||
|
|
||||||
|
if (Date.class.isInstance(parameterValue)) {
|
||||||
|
parameterValue = LDAPUtil.formatDate((Date) parameterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lessThanCondition.isOrEqual()) {
|
||||||
|
filter.append("(").append(attributeName).append("<=").append(parameterValue).append(")");
|
||||||
|
} else {
|
||||||
|
filter.append("(").append(attributeName).append("<").append(parameterValue).append(")");
|
||||||
|
}
|
||||||
|
} else if (BetweenCondition.class.isInstance(condition)) {
|
||||||
|
BetweenCondition betweenCondition = (BetweenCondition) condition;
|
||||||
|
Comparable x = betweenCondition.getX();
|
||||||
|
Comparable y = betweenCondition.getY();
|
||||||
|
|
||||||
|
if (Date.class.isInstance(x)) {
|
||||||
|
x = LDAPUtil.formatDate((Date) x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Date.class.isInstance(y)) {
|
||||||
|
y = LDAPUtil.formatDate((Date) y);
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.append("(").append(x).append("<=").append(attributeName).append("<=").append(y).append(")");
|
||||||
|
} else if (InCondition.class.isInstance(condition)) {
|
||||||
|
InCondition inCondition = (InCondition) condition;
|
||||||
|
Object[] valuesToCompare = inCondition.getValue();
|
||||||
|
|
||||||
|
filter.append("(&(");
|
||||||
|
|
||||||
|
for (int i = 0; i< valuesToCompare.length; i++) {
|
||||||
|
Object value = valuesToCompare[i];
|
||||||
|
|
||||||
|
filter.append("(").append(attributeName).append(LDAPConstants.EQUAL).append(value).append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.append("))");
|
||||||
|
} else {
|
||||||
|
throw new ModelException("Unsupported query condition [" + condition + "].");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
filter.insert(0, "(&(");
|
||||||
|
filter.append(getObjectClassesFilter(ldapEntryConfig));
|
||||||
|
filter.append("))");
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder getObjectClassesFilter(final LDAPMappingConfiguration ldapEntryConfig) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
if (ldapEntryConfig != null && !ldapEntryConfig.getObjectClasses().isEmpty()) {
|
||||||
|
for (String objectClass : ldapEntryConfig.getObjectClasses()) {
|
||||||
|
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append(objectClass).append(")");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.append("(").append(LDAPConstants.OBJECT_CLASS).append(LDAPConstants.EQUAL).append("*").append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType) {
|
||||||
|
return populateAttributedType(searchResult, attributedType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributedType populateAttributedType(SearchResult searchResult, AttributedType attributedType, int hierarchyDepthCount) {
|
||||||
|
try {
|
||||||
|
String entryDN = searchResult.getNameInNamespace();
|
||||||
|
Attributes attributes = searchResult.getAttributes();
|
||||||
|
|
||||||
|
if (attributedType == null) {
|
||||||
|
attributedType = Reflections.newInstance(getConfig().getSupportedTypeByBaseDN(entryDN, getEntryObjectClasses(attributes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
attributedType.setEntryDN(entryDN);
|
||||||
|
|
||||||
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
|
if (hierarchyDepthCount > mappingConfig.getHierarchySearchDepth()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Populating attributed type [%s] from DN [%s]", attributedType, entryDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
NamingEnumeration<? extends Attribute> ldapAttributes = attributes.getAll();
|
||||||
|
|
||||||
|
while (ldapAttributes.hasMore()) {
|
||||||
|
Attribute ldapAttribute = ldapAttributes.next();
|
||||||
|
Object attributeValue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
attributeValue = ldapAttribute.get();
|
||||||
|
} catch (NoSuchElementException nsee) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ldapAttributeName = ldapAttribute.getID();
|
||||||
|
|
||||||
|
if (ldapAttributeName.toLowerCase().equals(getConfig().getUniqueIdentifierAttributeName().toLowerCase())) {
|
||||||
|
attributedType.setId(this.operationManager.decodeEntryUUID(attributeValue));
|
||||||
|
} else {
|
||||||
|
String attributeName = findAttributeName(mappingConfig.getMappedProperties(), ldapAttributeName);
|
||||||
|
|
||||||
|
if (attributeName != null) {
|
||||||
|
// Find if it's java property or attribute
|
||||||
|
Property<Object> property = PropertyQueries
|
||||||
|
.createQuery(attributedType.getClass())
|
||||||
|
.addCriteria(new NamedPropertyCriteria(attributeName)).getFirstResult();
|
||||||
|
|
||||||
|
if (property != null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Populating property [%s] from ldap attribute [%s] with value [%s] from DN [%s].", property.getName(), ldapAttributeName, attributeValue, entryDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.getJavaClass().equals(Date.class)) {
|
||||||
|
property.setValue(attributedType, LDAPUtil.parseDate(attributeValue.toString()));
|
||||||
|
} else {
|
||||||
|
property.setValue(attributedType, attributeValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Populating attribute [%s] from ldap attribute [%s] with value [%s] from DN [%s].", attributeName, ldapAttributeName, attributeValue, entryDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributedType.setAttribute(new org.keycloak.federation.ldap.idm.model.Attribute(attributeName, (Serializable) attributeValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IdentityType.class.isInstance(attributedType)) {
|
||||||
|
IdentityType identityType = (IdentityType) attributedType;
|
||||||
|
|
||||||
|
String createdTimestamp = attributes.get(LDAPConstants.CREATE_TIMESTAMP).get().toString();
|
||||||
|
|
||||||
|
identityType.setCreatedDate(LDAPUtil.parseDate(createdTimestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
|
if (mappingConfig.getParentMembershipAttributeName() != null) {
|
||||||
|
StringBuilder filter = new StringBuilder("(&");
|
||||||
|
String entryBaseDN = entryDN.substring(entryDN.indexOf(LDAPConstants.COMMA) + 1);
|
||||||
|
|
||||||
|
filter
|
||||||
|
.append("(")
|
||||||
|
.append(getObjectClassesFilter(entryConfig))
|
||||||
|
.append(")")
|
||||||
|
.append("(")
|
||||||
|
.append(mappingConfig.getParentMembershipAttributeName())
|
||||||
|
.append(LDAPConstants.EQUAL).append("")
|
||||||
|
.append(getBindingDN(attributedType, false))
|
||||||
|
.append(LDAPConstants.COMMA)
|
||||||
|
.append(entryBaseDN)
|
||||||
|
.append(")");
|
||||||
|
|
||||||
|
filter.append(")");
|
||||||
|
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Searching parent entry for DN [%s] using filter [%s].", entryBaseDN, filter.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SearchResult> search = this.operationManager.search(getConfig().getBaseDN(), filter.toString(), entryConfig);
|
||||||
|
|
||||||
|
if (!search.isEmpty()) {
|
||||||
|
SearchResult next = search.get(0);
|
||||||
|
|
||||||
|
Property<AttributedType> parentProperty = PropertyQueries
|
||||||
|
.<AttributedType>createQuery(attributedType.getClass())
|
||||||
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
||||||
|
|
||||||
|
if (parentProperty != null) {
|
||||||
|
String parentDN = next.getNameInNamespace();
|
||||||
|
String parentBaseDN = parentDN.substring(parentDN.indexOf(",") + 1);
|
||||||
|
Class<? extends AttributedType> baseDNType = getConfig().getSupportedTypeByBaseDN(parentBaseDN, getEntryObjectClasses(attributes));
|
||||||
|
|
||||||
|
if (parentProperty.getJavaClass().isAssignableFrom(baseDNType)) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("Found parent [%s] for entry for DN [%s].", parentDN, entryDN);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hierarchyDepthCount1 = ++hierarchyDepthCount;
|
||||||
|
|
||||||
|
parentProperty.setValue(attributedType, populateAttributedType(next, null, hierarchyDepthCount1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.tracef("No parent entry found for DN [%s] using filter [%s].", entryDN, filter.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("Could not populate attribute type " + attributedType + ".", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findAttributeName(Map<String, String> attrMapping, String ldapAttributeName) {
|
||||||
|
for (Map.Entry<String,String> currentAttr : attrMapping.entrySet()) {
|
||||||
|
if (currentAttr.getValue().equalsIgnoreCase(ldapAttributeName)) {
|
||||||
|
return currentAttr.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getEntryObjectClasses(final Attributes attributes) throws NamingException {
|
||||||
|
Attribute objectClassesAttribute = attributes.get(LDAPConstants.OBJECT_CLASS);
|
||||||
|
List<String> objectClasses = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (objectClassesAttribute == null) {
|
||||||
|
return objectClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
NamingEnumeration<?> all = objectClassesAttribute.getAll();
|
||||||
|
|
||||||
|
while (all.hasMore()) {
|
||||||
|
objectClasses.add(all.next().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BasicAttributes extractAttributes(AttributedType attributedType, boolean isCreate) {
|
||||||
|
BasicAttributes entryAttributes = new BasicAttributes();
|
||||||
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
Map<String, String> mappedProperties = mappingConfig.getMappedProperties();
|
||||||
|
|
||||||
|
for (String propertyName : mappedProperties.keySet()) {
|
||||||
|
if (!mappingConfig.getReadOnlyAttributes().contains(propertyName) && (isCreate || !mappingConfig.getBindingProperty().getName().equals(propertyName))) {
|
||||||
|
Property<Object> property = PropertyQueries
|
||||||
|
.<Object>createQuery(attributedType.getClass())
|
||||||
|
.addCriteria(new NamedPropertyCriteria(propertyName)).getFirstResult();
|
||||||
|
|
||||||
|
Object propertyValue = null;
|
||||||
|
if (property != null) {
|
||||||
|
// Mapped Java property on the object
|
||||||
|
propertyValue = property.getValue(attributedType);
|
||||||
|
} else {
|
||||||
|
// Not mapped property. So fallback to attribute
|
||||||
|
org.keycloak.federation.ldap.idm.model.Attribute<?> attribute = attributedType.getAttribute(propertyName);
|
||||||
|
if (attribute != null) {
|
||||||
|
propertyValue = attribute.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AttributedType.class.isInstance(propertyValue)) {
|
||||||
|
AttributedType referencedType = (AttributedType) propertyValue;
|
||||||
|
propertyValue = getBindingDN(referencedType, true);
|
||||||
|
} else {
|
||||||
|
if (propertyValue == null || isNullOrEmpty(propertyValue.toString())) {
|
||||||
|
propertyValue = EMPTY_ATTRIBUTE_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entryAttributes.put(mappedProperties.get(propertyName), propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't extract object classes for update
|
||||||
|
if (isCreate) {
|
||||||
|
LDAPMappingConfiguration ldapEntryConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
|
BasicAttribute objectClassAttribute = new BasicAttribute(LDAPConstants.OBJECT_CLASS);
|
||||||
|
|
||||||
|
for (String objectClassValue : ldapEntryConfig.getObjectClasses()) {
|
||||||
|
objectClassAttribute.add(objectClassValue);
|
||||||
|
|
||||||
|
if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
|
||||||
|
|| objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
|
||||||
|
|| objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
|
||||||
|
entryAttributes.put(LDAPConstants.MEMBER, EMPTY_ATTRIBUTE_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entryAttributes.put(objectClassAttribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entryAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move class StringUtil from SAML module
|
||||||
|
public static boolean isNullOrEmpty(String str) {
|
||||||
|
return str == null || str.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
|
||||||
|
LDAPMappingConfiguration mappingConfig = getConfig().getMappingConfig(attributedType);
|
||||||
|
|
||||||
|
if (mappingConfig == null) {
|
||||||
|
throw new ModelException("Not mapped type [" + attributedType + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindingDN(AttributedType attributedType, boolean appendBaseDN) {
|
||||||
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
Property<String> idProperty = mappingConfig.getIdProperty();
|
||||||
|
|
||||||
|
String baseDN;
|
||||||
|
|
||||||
|
if (mappingConfig.getBaseDN() == null || !appendBaseDN) {
|
||||||
|
baseDN = "";
|
||||||
|
} else {
|
||||||
|
baseDN = LDAPConstants.COMMA + getBaseDN(attributedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Property<String> bindingProperty = mappingConfig.getBindingProperty();
|
||||||
|
String bindingAttribute;
|
||||||
|
String dn;
|
||||||
|
|
||||||
|
if (bindingProperty == null) {
|
||||||
|
bindingAttribute = mappingConfig.getMappedProperties().get(idProperty.getName());
|
||||||
|
dn = idProperty.getValue(attributedType);
|
||||||
|
} else {
|
||||||
|
bindingAttribute = mappingConfig.getMappedProperties().get(bindingProperty.getName());
|
||||||
|
dn = mappingConfig.getBindingProperty().getValue(attributedType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindingAttribute + LDAPConstants.EQUAL + dn + baseDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBaseDN(AttributedType attributedType) {
|
||||||
|
LDAPMappingConfiguration mappingConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
String baseDN = mappingConfig.getBaseDN();
|
||||||
|
String parentDN = mappingConfig.getParentMapping().get(mappingConfig.getIdProperty().getValue(attributedType));
|
||||||
|
|
||||||
|
if (parentDN != null) {
|
||||||
|
baseDN = parentDN;
|
||||||
|
} else {
|
||||||
|
Property<AttributedType> parentProperty = PropertyQueries
|
||||||
|
.<AttributedType>createQuery(attributedType.getClass())
|
||||||
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass())).getFirstResult();
|
||||||
|
|
||||||
|
if (parentProperty != null) {
|
||||||
|
AttributedType parentType = parentProperty.getValue(attributedType);
|
||||||
|
|
||||||
|
if (parentType != null) {
|
||||||
|
Property<String> parentIdProperty = getMappingConfig(parentType.getClass()).getIdProperty();
|
||||||
|
|
||||||
|
String parentId = parentIdProperty.getValue(parentType);
|
||||||
|
|
||||||
|
String parentBaseDN = mappingConfig.getParentMapping().get(parentId);
|
||||||
|
|
||||||
|
if (parentBaseDN != null) {
|
||||||
|
baseDN = parentBaseDN;
|
||||||
|
} else {
|
||||||
|
baseDN = getBaseDN(parentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseDN == null) {
|
||||||
|
baseDN = getConfig().getBaseDN();
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addToParentAsMember(final AttributedType attributedType) {
|
||||||
|
LDAPMappingConfiguration entryConfig = getMappingConfig(attributedType.getClass());
|
||||||
|
|
||||||
|
if (entryConfig.getParentMembershipAttributeName() != null) {
|
||||||
|
Property<AttributedType> parentProperty = PropertyQueries
|
||||||
|
.<AttributedType>createQuery(attributedType.getClass())
|
||||||
|
.addCriteria(new TypedPropertyCriteria(attributedType.getClass()))
|
||||||
|
.getFirstResult();
|
||||||
|
|
||||||
|
if (parentProperty != null) {
|
||||||
|
AttributedType parentType = parentProperty.getValue(attributedType);
|
||||||
|
|
||||||
|
if (parentType != null) {
|
||||||
|
Attributes attributes = this.operationManager.getAttributes(parentType.getId(), getBaseDN(parentType), entryConfig);
|
||||||
|
Attribute attribute = attributes.get(entryConfig.getParentMembershipAttributeName());
|
||||||
|
|
||||||
|
attribute.add(getBindingDN(attributedType, true));
|
||||||
|
|
||||||
|
this.operationManager.modifyAttribute(getBindingDN(parentType, true), attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getEntryIdentifier(final AttributedType attributedType) {
|
||||||
|
try {
|
||||||
|
// we need this to retrieve the entry's identifier from the ldap server
|
||||||
|
List<SearchResult> search = this.operationManager.search(getBaseDN(attributedType), "(" + getBindingDN(attributedType, false) + ")", getMappingConfig(attributedType.getClass()));
|
||||||
|
Attribute id = search.get(0).getAttributes().get(getConfig().getUniqueIdentifierAttributeName());
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
throw new ModelException("Could not retrieve identifier for entry [" + getBindingDN(attributedType, true) + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.operationManager.decodeEntryUUID(id.get());
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
throw new ModelException("Could not add type [" + attributedType + "].", ne);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,188 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A configuration for the LDAP store.
|
||||||
|
*
|
||||||
|
* @author anil saldhana
|
||||||
|
* @since Sep 6, 2012
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LDAPIdentityStoreConfiguration {
|
||||||
|
|
||||||
|
private String ldapURL;
|
||||||
|
private String factoryName = "com.sun.jndi.ldap.LdapCtxFactory";
|
||||||
|
private String authType = "simple";
|
||||||
|
private String protocol;
|
||||||
|
private String bindDN;
|
||||||
|
private String bindCredential;
|
||||||
|
private boolean activeDirectory;
|
||||||
|
private Properties connectionProperties;
|
||||||
|
private boolean pagination;
|
||||||
|
private String uniqueIdentifierAttributeName;
|
||||||
|
private boolean userAccountControlsAfterPasswordUpdate;
|
||||||
|
|
||||||
|
private String baseDN;
|
||||||
|
private Map<Class<? extends AttributedType>, LDAPMappingConfiguration> mappingConfig = new HashMap<Class<? extends AttributedType>, LDAPMappingConfiguration>();
|
||||||
|
|
||||||
|
public String getLdapURL() {
|
||||||
|
return this.ldapURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFactoryName() {
|
||||||
|
return this.factoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthType() {
|
||||||
|
return this.authType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseDN() {
|
||||||
|
return this.baseDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindDN() {
|
||||||
|
return this.bindDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindCredential() {
|
||||||
|
return this.bindCredential;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActiveDirectory() {
|
||||||
|
return this.activeDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties getConnectionProperties() {
|
||||||
|
return this.connectionProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration mappingConfig(Class<? extends AttributedType> clazz) {
|
||||||
|
LDAPMappingConfiguration mappingConfig = new LDAPMappingConfiguration(clazz);
|
||||||
|
this.mappingConfig.put(clazz, mappingConfig);
|
||||||
|
return mappingConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends AttributedType> getSupportedTypeByBaseDN(String entryDN, List<String> objectClasses) {
|
||||||
|
String entryBaseDN = entryDN.substring(entryDN.indexOf(LDAPConstants.COMMA) + 1);
|
||||||
|
|
||||||
|
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
|
||||||
|
if (mappingConfig.getBaseDN() != null) {
|
||||||
|
|
||||||
|
if (mappingConfig.getBaseDN().equalsIgnoreCase(entryDN)
|
||||||
|
|| mappingConfig.getParentMapping().values().contains(entryDN)) {
|
||||||
|
return mappingConfig.getMappedClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappingConfig.getBaseDN().equalsIgnoreCase(entryBaseDN)
|
||||||
|
|| mappingConfig.getParentMapping().values().contains(entryBaseDN)) {
|
||||||
|
return mappingConfig.getMappedClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
|
||||||
|
for (String objectClass : objectClasses) {
|
||||||
|
if (mappingConfig.getObjectClasses().contains(objectClass)) {
|
||||||
|
return mappingConfig.getMappedClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ModelException("No type found with Base DN [" + entryDN + "] or objectClasses [" + objectClasses + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration getMappingConfig(Class<? extends AttributedType> attributedType) {
|
||||||
|
for (LDAPMappingConfiguration mappingConfig : this.mappingConfig.values()) {
|
||||||
|
if (attributedType.equals(mappingConfig.getMappedClass())) {
|
||||||
|
return mappingConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProtocol() {
|
||||||
|
return protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUniqueIdentifierAttributeName() {
|
||||||
|
return uniqueIdentifierAttributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPagination() {
|
||||||
|
return pagination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserAccountControlsAfterPasswordUpdate() {
|
||||||
|
return userAccountControlsAfterPasswordUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setLdapURL(String ldapURL) {
|
||||||
|
this.ldapURL = ldapURL;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setFactoryName(String factoryName) {
|
||||||
|
this.factoryName = factoryName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setAuthType(String authType) {
|
||||||
|
this.authType = authType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setProtocol(String protocol) {
|
||||||
|
this.protocol = protocol;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setBindDN(String bindDN) {
|
||||||
|
this.bindDN = bindDN;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setBindCredential(String bindCredential) {
|
||||||
|
this.bindCredential = bindCredential;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setActiveDirectory(boolean activeDirectory) {
|
||||||
|
this.activeDirectory = activeDirectory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setPagination(boolean pagination) {
|
||||||
|
this.pagination = pagination;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setConnectionProperties(Properties connectionProperties) {
|
||||||
|
this.connectionProperties = connectionProperties;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setUniqueIdentifierAttributeName(String uniqueIdentifierAttributeName) {
|
||||||
|
this.uniqueIdentifierAttributeName = uniqueIdentifierAttributeName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setUserAccountControlsAfterPasswordUpdate(boolean userAccountControlsAfterPasswordUpdate) {
|
||||||
|
this.userAccountControlsAfterPasswordUpdate = userAccountControlsAfterPasswordUpdate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPIdentityStoreConfiguration setBaseDN(String baseDN) {
|
||||||
|
this.baseDN = baseDN;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.federation.ldap.idm.model.Attribute;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.AttributedType;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.utils.reflection.NamedPropertyCriteria;
|
||||||
|
import org.keycloak.models.utils.reflection.Property;
|
||||||
|
import org.keycloak.models.utils.reflection.PropertyQueries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author pedroigor
|
||||||
|
*/
|
||||||
|
public class LDAPMappingConfiguration {
|
||||||
|
|
||||||
|
private final Class<? extends AttributedType> mappedClass;
|
||||||
|
private Set<String> objectClasses;
|
||||||
|
private String baseDN;
|
||||||
|
private final Map<String, String> mappedProperties = new HashMap<String, String>();
|
||||||
|
private Property<String> idProperty;
|
||||||
|
private Class<? extends AttributedType> relatedAttributedType;
|
||||||
|
private String parentMembershipAttributeName;
|
||||||
|
private Map<String, String> parentMapping = new HashMap<String, String>();
|
||||||
|
private final Set<String> readOnlyAttributes = new HashSet<String>();
|
||||||
|
private int hierarchySearchDepth;
|
||||||
|
private Property<String> bindingProperty;
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration(Class<? extends AttributedType> mappedClass) {
|
||||||
|
this.mappedClass = mappedClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends AttributedType> getMappedClass() {
|
||||||
|
return this.mappedClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getObjectClasses() {
|
||||||
|
return this.objectClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseDN() {
|
||||||
|
return this.baseDN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMappedProperties() {
|
||||||
|
return this.mappedProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property<String> getIdProperty() {
|
||||||
|
return this.idProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property<String> getBindingProperty() {
|
||||||
|
return this.bindingProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends AttributedType> getRelatedAttributedType() {
|
||||||
|
return this.relatedAttributedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParentMembershipAttributeName() {
|
||||||
|
return this.parentMembershipAttributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getParentMapping() {
|
||||||
|
return this.parentMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getReadOnlyAttributes() {
|
||||||
|
return this.readOnlyAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHierarchySearchDepth() {
|
||||||
|
return this.hierarchySearchDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Property getBindingProperty(final String bindingPropertyName) {
|
||||||
|
Property bindingProperty = PropertyQueries
|
||||||
|
.<String>createQuery(getMappedClass())
|
||||||
|
.addCriteria(new NamedPropertyCriteria(bindingPropertyName)).getFirstResult();
|
||||||
|
|
||||||
|
// We don't have Java property, so actually delegate to setAttribute/getAttribute
|
||||||
|
if (bindingProperty == null) {
|
||||||
|
bindingProperty = new Property<String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return bindingPropertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getBaseType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<String> getJavaClass() {
|
||||||
|
return String.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotatedElement getAnnotatedElement() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Member getMember() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue(Object instance) {
|
||||||
|
if (!(instance instanceof AttributedType)) {
|
||||||
|
throw new IllegalStateException("Instance [ " + instance + " ] not an instance of AttributedType");
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributedType attributedType = (AttributedType) instance;
|
||||||
|
Attribute<String> attr = attributedType.getAttribute(bindingPropertyName);
|
||||||
|
return attr!=null ? attr.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(Object instance, String value) {
|
||||||
|
if (!(instance instanceof AttributedType)) {
|
||||||
|
throw new IllegalStateException("Instance [ " + instance + " ] not an instance of AttributedType");
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributedType attributedType = (AttributedType) instance;
|
||||||
|
attributedType.setAttribute(new Attribute(bindingPropertyName, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getDeclaringClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAccessible() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAnnotationPresent(Class annotation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindingProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setObjectClasses(Set<String> objectClasses) {
|
||||||
|
this.objectClasses = objectClasses;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setBaseDN(String baseDN) {
|
||||||
|
this.baseDN = baseDN;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration addAttributeMapping(String userAttributeName, String ldapAttributeName) {
|
||||||
|
this.mappedProperties.put(userAttributeName, ldapAttributeName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration addReadOnlyAttributeMapping(String userAttributeName, String ldapAttributeName) {
|
||||||
|
this.mappedProperties.put(userAttributeName, ldapAttributeName);
|
||||||
|
this.readOnlyAttributes.add(userAttributeName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setIdPropertyName(String idPropertyName) {
|
||||||
|
|
||||||
|
if (idPropertyName != null) {
|
||||||
|
this.idProperty = PropertyQueries
|
||||||
|
.<String>createQuery(getMappedClass())
|
||||||
|
.addCriteria(new NamedPropertyCriteria(idPropertyName)).getFirstResult();
|
||||||
|
} else {
|
||||||
|
this.idProperty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IdentityType.class.isAssignableFrom(mappedClass) && idProperty == null) {
|
||||||
|
throw new ModelException("Id attribute not mapped to any property of [" + mappedClass + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binding property is idProperty by default
|
||||||
|
if (this.bindingProperty == null) {
|
||||||
|
this.bindingProperty = this.idProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setRelatedAttributedType(Class<? extends AttributedType> relatedAttributedType) {
|
||||||
|
this.relatedAttributedType = relatedAttributedType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setParentMembershipAttributeName(String parentMembershipAttributeName) {
|
||||||
|
this.parentMembershipAttributeName = parentMembershipAttributeName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setParentMapping(Map<String, String> parentMapping) {
|
||||||
|
this.parentMapping = parentMapping;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setHierarchySearchDepth(int hierarchySearchDepth) {
|
||||||
|
this.hierarchySearchDepth = hierarchySearchDepth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPMappingConfiguration setBindingPropertyName(String bindingPropertyName) {
|
||||||
|
this.bindingProperty = getBindingProperty(bindingPropertyName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,606 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.naming.Binding;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.Attributes;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.ModificationItem;
|
||||||
|
import javax.naming.directory.SearchControls;
|
||||||
|
import javax.naming.directory.SearchResult;
|
||||||
|
import javax.naming.ldap.Control;
|
||||||
|
import javax.naming.ldap.InitialLdapContext;
|
||||||
|
import javax.naming.ldap.LdapContext;
|
||||||
|
import javax.naming.ldap.PagedResultsControl;
|
||||||
|
import javax.naming.ldap.PagedResultsResponseControl;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.IdentityType;
|
||||||
|
import org.keycloak.federation.ldap.idm.query.IdentityQuery;
|
||||||
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>This class provides a set of operations to manage LDAP trees.</p>
|
||||||
|
*
|
||||||
|
* @author Anil Saldhana
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
|
||||||
|
*/
|
||||||
|
public class LDAPOperationManager {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(LDAPOperationManager.class);
|
||||||
|
|
||||||
|
private final LDAPIdentityStoreConfiguration config;
|
||||||
|
private final Map<String, Object> connectionProperties;
|
||||||
|
|
||||||
|
public LDAPOperationManager(LDAPIdentityStoreConfiguration config) throws NamingException {
|
||||||
|
this.config = config;
|
||||||
|
this.connectionProperties = Collections.unmodifiableMap(createConnectionProperties());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Modifies the given {@link javax.naming.directory.Attribute} instance using the given DN. This method performs a REPLACE_ATTRIBUTE
|
||||||
|
* operation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
* @param attribute
|
||||||
|
*/
|
||||||
|
public void modifyAttribute(String dn, Attribute attribute) {
|
||||||
|
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)};
|
||||||
|
modifyAttributes(dn, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Modifies the given {@link Attribute} instances using the given DN. This method performs a REPLACE_ATTRIBUTE
|
||||||
|
* operation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
* @param attributes
|
||||||
|
*/
|
||||||
|
public void modifyAttributes(String dn, NamingEnumeration<Attribute> attributes) {
|
||||||
|
try {
|
||||||
|
List<ModificationItem> modItems = new ArrayList<ModificationItem>();
|
||||||
|
while (attributes.hasMore()) {
|
||||||
|
ModificationItem modItem = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attributes.next());
|
||||||
|
modItems.add(modItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}));
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
throw new ModelException("Could not modify attributes on entry from DN [" + dn + "]", ne);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Removes the given {@link Attribute} instance using the given DN. This method performs a REMOVE_ATTRIBUTE
|
||||||
|
* operation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
* @param attribute
|
||||||
|
*/
|
||||||
|
public void removeAttribute(String dn, Attribute attribute) {
|
||||||
|
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)};
|
||||||
|
modifyAttributes(dn, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Adds the given {@link Attribute} instance using the given DN. This method performs a ADD_ATTRIBUTE operation.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
* @param attribute
|
||||||
|
*/
|
||||||
|
public void addAttribute(String dn, Attribute attribute) {
|
||||||
|
ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)};
|
||||||
|
modifyAttributes(dn, mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Searches the LDAP tree.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param baseDN
|
||||||
|
* @param id
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public void removeEntryById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
|
||||||
|
final String filter = getFilterById(baseDN, id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final SearchControls cons = getSearchControls(mappingConfiguration);
|
||||||
|
|
||||||
|
execute(new LdapOperation<SearchResult>() {
|
||||||
|
@Override
|
||||||
|
public SearchResult execute(LdapContext context) throws NamingException {
|
||||||
|
NamingEnumeration<SearchResult> result = context.search(baseDN, filter, cons);
|
||||||
|
|
||||||
|
if (result.hasMore()) {
|
||||||
|
SearchResult sr = result.next();
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Removing entry [%s] with attributes: [", sr.getNameInNamespace());
|
||||||
|
|
||||||
|
NamingEnumeration<? extends Attribute> all = sr.getAttributes().getAll();
|
||||||
|
|
||||||
|
while (all.hasMore()) {
|
||||||
|
Attribute attribute = all.next();
|
||||||
|
|
||||||
|
logger.debugf(" %s = %s", attribute.getID(), attribute.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf("]");
|
||||||
|
}
|
||||||
|
destroySubcontext(context, sr.getNameInNamespace());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.close();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new ModelException("Could not remove entry from DN [" + baseDN + "] and id [" + id + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SearchResult> search(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration) throws NamingException {
|
||||||
|
final List<SearchResult> result = new ArrayList<SearchResult>();
|
||||||
|
final SearchControls cons = getSearchControls(mappingConfiguration);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return execute(new LdapOperation<List<SearchResult>>() {
|
||||||
|
@Override
|
||||||
|
public List<SearchResult> execute(LdapContext context) throws NamingException {
|
||||||
|
NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
|
||||||
|
|
||||||
|
while (search.hasMoreElements()) {
|
||||||
|
result.add(search.nextElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
search.close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
logger.errorf(e, "Could not query server using DN [%s] and filter [%s]", baseDN, filter);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <V extends IdentityType> List<SearchResult> searchPaginated(final String baseDN, final String filter, LDAPMappingConfiguration mappingConfiguration, final IdentityQuery<V> identityQuery) throws NamingException {
|
||||||
|
final List<SearchResult> result = new ArrayList<SearchResult>();
|
||||||
|
final SearchControls cons = getSearchControls(mappingConfiguration);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return execute(new LdapOperation<List<SearchResult>>() {
|
||||||
|
@Override
|
||||||
|
public List<SearchResult> execute(LdapContext context) throws NamingException {
|
||||||
|
try {
|
||||||
|
byte[] cookie = (byte[])identityQuery.getPaginationContext();
|
||||||
|
PagedResultsControl pagedControls = new PagedResultsControl(identityQuery.getLimit(), cookie, Control.CRITICAL);
|
||||||
|
context.setRequestControls(new Control[] { pagedControls });
|
||||||
|
|
||||||
|
NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
|
||||||
|
|
||||||
|
while (search.hasMoreElements()) {
|
||||||
|
result.add(search.nextElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
search.close();
|
||||||
|
|
||||||
|
Control[] responseControls = context.getResponseControls();
|
||||||
|
if (responseControls != null) {
|
||||||
|
for (Control respControl : responseControls) {
|
||||||
|
if (respControl instanceof PagedResultsResponseControl) {
|
||||||
|
PagedResultsResponseControl prrc = (PagedResultsResponseControl)respControl;
|
||||||
|
cookie = prrc.getCookie();
|
||||||
|
identityQuery.setPaginationContext(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
logger.errorf(ioe, "Could not query server with paginated query using DN [%s], filter [%s]", baseDN, filter);
|
||||||
|
throw new NamingException(ioe.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
logger.errorf(e, "Could not query server using DN [%s] and filter [%s]", baseDN, filter);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchControls getSearchControls(LDAPMappingConfiguration mappingConfiguration) {
|
||||||
|
final SearchControls cons = new SearchControls();
|
||||||
|
|
||||||
|
cons.setSearchScope(SUBTREE_SCOPE);
|
||||||
|
cons.setReturningObjFlag(false);
|
||||||
|
|
||||||
|
List<String> returningAttributes = getReturningAttributes(mappingConfiguration);
|
||||||
|
|
||||||
|
cons.setReturningAttributes(returningAttributes.toArray(new String[returningAttributes.size()]));
|
||||||
|
return cons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFilterById(String baseDN, String id) {
|
||||||
|
String filter = null;
|
||||||
|
|
||||||
|
if (this.config.isActiveDirectory()) {
|
||||||
|
final String strObjectGUID = "<GUID=" + id + ">";
|
||||||
|
|
||||||
|
try {
|
||||||
|
Attributes attributes = execute(new LdapOperation<Attributes>() {
|
||||||
|
@Override
|
||||||
|
public Attributes execute(LdapContext context) throws NamingException {
|
||||||
|
return context.getAttributes(strObjectGUID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
byte[] objectGUID = (byte[]) attributes.get(LDAPConstants.OBJECT_GUID).get();
|
||||||
|
|
||||||
|
filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIToByteString(objectGUID) + "))";
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter == null) {
|
||||||
|
filter = "(&(objectClass=*)(" + getUniqueIdentifierAttributeName() + LDAPConstants.EQUAL + id + "))";
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResult lookupById(final String baseDN, final String id, final LDAPMappingConfiguration mappingConfiguration) {
|
||||||
|
final String filter = getFilterById(baseDN, id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final SearchControls cons = getSearchControls(mappingConfiguration);
|
||||||
|
|
||||||
|
return execute(new LdapOperation<SearchResult>() {
|
||||||
|
@Override
|
||||||
|
public SearchResult execute(LdapContext context) throws NamingException {
|
||||||
|
NamingEnumeration<SearchResult> search = context.search(baseDN, filter, cons);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (search.hasMoreElements()) {
|
||||||
|
return search.next();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (search != null) {
|
||||||
|
search.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new ModelException("Could not query server using DN [" + baseDN + "] and filter [" + filter + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Destroys a subcontext with the given DN from the LDAP tree.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
*/
|
||||||
|
private void destroySubcontext(LdapContext context, final String dn) {
|
||||||
|
try {
|
||||||
|
NamingEnumeration<Binding> enumeration = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
enumeration = context.listBindings(dn);
|
||||||
|
|
||||||
|
while (enumeration.hasMore()) {
|
||||||
|
Binding binding = enumeration.next();
|
||||||
|
String name = binding.getNameInNamespace();
|
||||||
|
|
||||||
|
destroySubcontext(context, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.unbind(dn);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
enumeration.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("Could not unbind DN [" + dn + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Performs a simple authentication using the given DN and password to bind to the authentication context.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param dn
|
||||||
|
* @param password
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean authenticate(String dn, String password) {
|
||||||
|
InitialContext authCtx = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Hashtable<String, Object> env = new Hashtable<String, Object>(this.connectionProperties);
|
||||||
|
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, dn);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, password);
|
||||||
|
|
||||||
|
// Never use connection pool to prevent password caching
|
||||||
|
env.put("com.sun.jndi.ldap.connect.pool", "false");
|
||||||
|
|
||||||
|
authCtx = new InitialLdapContext(env, null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf(e, "Authentication failed for DN [%s]", dn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (authCtx != null) {
|
||||||
|
try {
|
||||||
|
authCtx.close();
|
||||||
|
} catch (NamingException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void modifyAttributes(final String dn, final ModificationItem[] mods) {
|
||||||
|
try {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Modifying attributes for entry [%s]: [", dn);
|
||||||
|
|
||||||
|
for (ModificationItem item : mods) {
|
||||||
|
Object values;
|
||||||
|
|
||||||
|
if (item.getAttribute().size() > 0) {
|
||||||
|
values = item.getAttribute().get();
|
||||||
|
} else {
|
||||||
|
values = "No values";
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf(" Op [%s]: %s = %s", item.getModificationOp(), item.getAttribute().getID(), values);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(new LdapOperation<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void execute(LdapContext context) throws NamingException {
|
||||||
|
context.modifyAttributes(dn, mods);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new ModelException("Could not modify attribute for DN [" + dn + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createSubContext(final String name, final Attributes attributes) {
|
||||||
|
try {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Creating entry [%s] with attributes: [", name);
|
||||||
|
|
||||||
|
NamingEnumeration<? extends Attribute> all = attributes.getAll();
|
||||||
|
|
||||||
|
while (all.hasMore()) {
|
||||||
|
Attribute attribute = all.next();
|
||||||
|
|
||||||
|
logger.debugf(" %s = %s", attribute.getID(), attribute.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(new LdapOperation<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void execute(LdapContext context) throws NamingException {
|
||||||
|
DirContext subcontext = context.createSubcontext(name, attributes);
|
||||||
|
|
||||||
|
subcontext.close();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new ModelException("Error creating subcontext [" + name + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUniqueIdentifierAttributeName() {
|
||||||
|
return this.config.getUniqueIdentifierAttributeName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NamingEnumeration<SearchResult> createEmptyEnumeration() {
|
||||||
|
return new NamingEnumeration<SearchResult>() {
|
||||||
|
@Override
|
||||||
|
public SearchResult next() throws NamingException {
|
||||||
|
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMore() throws NamingException {
|
||||||
|
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws NamingException {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMoreElements() {
|
||||||
|
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SearchResult nextElement() {
|
||||||
|
return null; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes getAttributes(final String entryUUID, final String baseDN, LDAPMappingConfiguration mappingConfiguration) {
|
||||||
|
SearchResult search = lookupById(baseDN, entryUUID, mappingConfiguration);
|
||||||
|
|
||||||
|
if (search == null) {
|
||||||
|
throw new ModelException("Couldn't find item with entryUUID [" + entryUUID + "] and baseDN [" + baseDN + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return search.getAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decodeEntryUUID(final Object entryUUID) {
|
||||||
|
String id;
|
||||||
|
|
||||||
|
if (this.config.isActiveDirectory()) {
|
||||||
|
id = LDAPUtil.decodeObjectGUID((byte[]) entryUUID);
|
||||||
|
} else {
|
||||||
|
id = entryUUID.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LdapContext createLdapContext() throws NamingException {
|
||||||
|
return new InitialLdapContext(new Hashtable<Object, Object>(this.connectionProperties), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createConnectionProperties() {
|
||||||
|
HashMap<String, Object> env = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, this.config.getFactoryName());
|
||||||
|
env.put(Context.SECURITY_AUTHENTICATION, this.config.getAuthType());
|
||||||
|
|
||||||
|
String protocol = this.config.getProtocol();
|
||||||
|
|
||||||
|
if (protocol != null) {
|
||||||
|
env.put(Context.SECURITY_PROTOCOL, protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
String bindDN = this.config.getBindDN();
|
||||||
|
|
||||||
|
char[] bindCredential = null;
|
||||||
|
|
||||||
|
if (this.config.getBindCredential() != null) {
|
||||||
|
bindCredential = this.config.getBindCredential().toCharArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindDN != null) {
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, bindDN);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = this.config.getLdapURL();
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
throw new RuntimeException("url");
|
||||||
|
}
|
||||||
|
|
||||||
|
env.put(Context.PROVIDER_URL, url);
|
||||||
|
|
||||||
|
// Just dump the additional properties
|
||||||
|
Properties additionalProperties = this.config.getConnectionProperties();
|
||||||
|
|
||||||
|
if (additionalProperties != null) {
|
||||||
|
for (Object key : additionalProperties.keySet()) {
|
||||||
|
env.put(key.toString(), additionalProperties.getProperty(key.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isActiveDirectory()) {
|
||||||
|
env.put("java.naming.ldap.attributes.binary", LDAPConstants.OBJECT_GUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debugf("Creating LdapContext using properties: [%s]", env);
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <R> R execute(LdapOperation<R> operation) throws NamingException {
|
||||||
|
LdapContext context = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
context = createLdapContext();
|
||||||
|
return operation.execute(context);
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
logger.error("Could not create Ldap context or operation execution error.", ne);
|
||||||
|
throw ne;
|
||||||
|
} finally {
|
||||||
|
if (context != null) {
|
||||||
|
try {
|
||||||
|
context.close();
|
||||||
|
} catch (NamingException ne) {
|
||||||
|
logger.error("Could not close Ldap context.", ne);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface LdapOperation<R> {
|
||||||
|
R execute(LdapContext context) throws NamingException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getReturningAttributes(final LDAPMappingConfiguration mappingConfiguration) {
|
||||||
|
List<String> returningAttributes = new ArrayList<String>();
|
||||||
|
|
||||||
|
if (mappingConfiguration != null) {
|
||||||
|
returningAttributes.addAll(mappingConfiguration.getMappedProperties().values());
|
||||||
|
|
||||||
|
returningAttributes.add(mappingConfiguration.getParentMembershipAttributeName());
|
||||||
|
|
||||||
|
// for (LDAPMappingConfiguration relationshipConfig : this.config.getRelationshipConfigs()) {
|
||||||
|
// if (relationshipConfig.getRelatedAttributedType().equals(mappingConfiguration.getMappedClass())) {
|
||||||
|
// returningAttributes.addAll(relationshipConfig.getMappedProperties().values());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
returningAttributes.add("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
returningAttributes.add(getUniqueIdentifierAttributeName());
|
||||||
|
returningAttributes.add(LDAPConstants.CREATE_TIMESTAMP);
|
||||||
|
returningAttributes.add(LDAPConstants.OBJECT_CLASS);
|
||||||
|
|
||||||
|
return returningAttributes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package org.keycloak.federation.ldap.idm.store.ldap;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.keycloak.models.ModelException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Utility class for working with LDAP.</p>
|
||||||
|
*
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class LDAPUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Formats the given date.</p>
|
||||||
|
*
|
||||||
|
* @param date The Date to format.
|
||||||
|
*
|
||||||
|
* @return A String representing the formatted date.
|
||||||
|
*/
|
||||||
|
public static final String formatDate(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
throw new IllegalArgumentException("You must provide a date.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss'.0Z'");
|
||||||
|
|
||||||
|
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
|
||||||
|
return dateFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Parses dates/time stamps stored in LDAP. Some possible values:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>20020228150820</li>
|
||||||
|
* <li>20030228150820Z</li>
|
||||||
|
* <li>20050228150820.12</li>
|
||||||
|
* <li>20060711011740.0Z</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param date The date string to parse from.
|
||||||
|
*
|
||||||
|
* @return the Date.
|
||||||
|
*/
|
||||||
|
public static final Date parseDate(String date) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (date.endsWith("Z")) {
|
||||||
|
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
} else {
|
||||||
|
dateFormat.setTimeZone(TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateFormat.parse(date);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ModelException("Error converting ldap date.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Creates a byte-based {@link String} representation of a raw byte array representing the value of the
|
||||||
|
* <code>objectGUID</code> attribute retrieved from Active Directory.</p>
|
||||||
|
*
|
||||||
|
* <p>The returned string is useful to perform queries on AD based on the <code>objectGUID</code> value. Eg.:</p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* String filter = "(&(objectClass=*)(objectGUID" + EQUAL + convertObjectGUIToByteString(objectGUID) + "))";
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param objectGUID A raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from
|
||||||
|
* Active Directory.
|
||||||
|
*
|
||||||
|
* @return A byte-based String representation in the form of \[0]\[1]\[2]\[3]\[4]\[5]\[6]\[7]\[8]\[9]\[10]\[11]\[12]\[13]\[14]\[15]
|
||||||
|
*/
|
||||||
|
public static String convertObjectGUIToByteString(byte[] objectGUID) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < objectGUID.length; i++) {
|
||||||
|
String transformed = prefixZeros((int) objectGUID[i] & 0xFF);
|
||||||
|
result.append("\\");
|
||||||
|
result.append(transformed);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Decode a raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from Active
|
||||||
|
* Directory.</p>
|
||||||
|
*
|
||||||
|
* <p>The returned string is useful to directly bind an entry. Eg.:</p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* String bindingString = decodeObjectGUID(objectGUID);
|
||||||
|
* <br/>
|
||||||
|
* Attributes attributes = ctx.getAttributes(bindingString);
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param objectGUID A raw byte array representing the value of the <code>objectGUID</code> attribute retrieved from
|
||||||
|
* Active Directory.
|
||||||
|
*
|
||||||
|
* @return A string representing the decoded value in the form of [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15].
|
||||||
|
*/
|
||||||
|
public static String decodeObjectGUID(byte[] objectGUID) {
|
||||||
|
StringBuilder displayStr = new StringBuilder();
|
||||||
|
|
||||||
|
displayStr.append(convertToDashedString(objectGUID));
|
||||||
|
|
||||||
|
return displayStr.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertToDashedString(byte[] objectGUID) {
|
||||||
|
StringBuilder displayStr = new StringBuilder();
|
||||||
|
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[3] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[2] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[1] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[0] & 0xFF));
|
||||||
|
displayStr.append("-");
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[5] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[4] & 0xFF));
|
||||||
|
displayStr.append("-");
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[7] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[6] & 0xFF));
|
||||||
|
displayStr.append("-");
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[8] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[9] & 0xFF));
|
||||||
|
displayStr.append("-");
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[10] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[11] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[12] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[13] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[14] & 0xFF));
|
||||||
|
displayStr.append(prefixZeros((int) objectGUID[15] & 0xFF));
|
||||||
|
|
||||||
|
return displayStr.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String prefixZeros(int value) {
|
||||||
|
if (value <= 0xF) {
|
||||||
|
StringBuilder sb = new StringBuilder("0");
|
||||||
|
sb.append(Integer.toHexString(value));
|
||||||
|
return sb.toString();
|
||||||
|
} else {
|
||||||
|
return Integer.toHexString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -29,5 +29,40 @@ public class LDAPConstants {
|
||||||
public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
|
public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
|
||||||
public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
|
public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
|
||||||
|
|
||||||
|
// Config option to specify if registrations will be synced or not
|
||||||
|
public static final String SYNC_REGISTRATIONS = "syncRegistrations";
|
||||||
|
|
||||||
|
// Applicable just for active directory
|
||||||
public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
|
public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
|
||||||
|
|
||||||
|
// Custom attributes on UserModel, which is mapped to LDAP
|
||||||
|
public static final String LDAP_ID = "LDAP_ID";
|
||||||
|
public static final String LDAP_ENTRY_DN = "LDAP_ENTRY_DN";
|
||||||
|
|
||||||
|
|
||||||
|
// Those are forked from Picketlink
|
||||||
|
public static final String GIVENNAME = "givenname";
|
||||||
|
public static final String CN = "cn";
|
||||||
|
public static final String SN = "sn";
|
||||||
|
public static final String EMAIL = "mail";
|
||||||
|
public static final String MEMBER = "member";
|
||||||
|
public static final String MEMBER_OF = "memberOf";
|
||||||
|
public static final String OBJECT_CLASS = "objectclass";
|
||||||
|
public static final String UID = "uid";
|
||||||
|
public static final String USER_PASSWORD_ATTRIBUTE = "userpassword";
|
||||||
|
public static final String GROUP_OF_NAMES = "groupOfNames";
|
||||||
|
public static final String GROUP_OF_ENTRIES = "groupOfEntries";
|
||||||
|
public static final String GROUP_OF_UNIQUE_NAMES = "groupOfUniqueNames";
|
||||||
|
|
||||||
|
public static final String COMMA = ",";
|
||||||
|
public static final String EQUAL = "=";
|
||||||
|
public static final String SPACE_STRING = " ";
|
||||||
|
|
||||||
|
public static final String CUSTOM_ATTRIBUTE_ENABLED = "enabled";
|
||||||
|
public static final String CUSTOM_ATTRIBUTE_CREATE_DATE = "createDate";
|
||||||
|
public static final String CUSTOM_ATTRIBUTE_EXPIRY_DATE = "expiryDate";
|
||||||
|
public static final String ENTRY_UUID = "entryUUID";
|
||||||
|
public static final String OBJECT_GUID = "objectGUID";
|
||||||
|
public static final String CREATE_TIMESTAMP = "createTimeStamp";
|
||||||
|
public static final String MODIFY_TIMESTAMP = "modifyTimeStamp";
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.keycloak.models.utils.reflection;
|
||||||
|
|
||||||
|
import java.beans.Introspector;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A criteria that matches a property based on name
|
||||||
|
*
|
||||||
|
* @see PropertyCriteria
|
||||||
|
*/
|
||||||
|
public class NamedPropertyCriteria implements PropertyCriteria {
|
||||||
|
private final String[] propertyNames;
|
||||||
|
|
||||||
|
public NamedPropertyCriteria(String... propertyNames) {
|
||||||
|
this.propertyNames = propertyNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean fieldMatches(Field f) {
|
||||||
|
for (String propertyName : propertyNames) {
|
||||||
|
if (propertyName.equals(f.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean methodMatches(Method m) {
|
||||||
|
String[] validPrefix = {"get", "is"};
|
||||||
|
for (String propertyName : propertyNames) {
|
||||||
|
for (String prefix : validPrefix) {
|
||||||
|
if (m.getName().startsWith(prefix) &&
|
||||||
|
Introspector.decapitalize(m.getName().substring(prefix.length())).equals(propertyName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.keycloak.models.utils.reflection;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A criteria that matches a property based on its type
|
||||||
|
*
|
||||||
|
* @see PropertyCriteria
|
||||||
|
*/
|
||||||
|
public class TypedPropertyCriteria implements PropertyCriteria {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p> Different options can be used to match a specific property based on its type. Regardless of the option
|
||||||
|
* chosen, if the property type equals the <code>propertyClass</code> it will be selected. <p/> <ul> <li>SUB_TYPE:
|
||||||
|
* Also consider properties where its type is a subtype of <code>propertyClass</code>. .</li> <li>SUPER_TYPE: Also
|
||||||
|
* consider properties where its type is a superclass or superinterface of <code>propertyClass</code>. .</li> </ul>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public static enum MatchOption {
|
||||||
|
SUB_TYPE, SUPER_TYPE, ALL
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Class<?> propertyClass;
|
||||||
|
private final MatchOption matchOption;
|
||||||
|
|
||||||
|
public TypedPropertyCriteria(Class<?> propertyClass) {
|
||||||
|
this(propertyClass, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypedPropertyCriteria(Class<?> propertyClass, MatchOption matchOption) {
|
||||||
|
if (propertyClass == null) {
|
||||||
|
throw new IllegalArgumentException("Property class can not be null.");
|
||||||
|
}
|
||||||
|
this.propertyClass = propertyClass;
|
||||||
|
this.matchOption = matchOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean fieldMatches(Field f) {
|
||||||
|
return match(f.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean methodMatches(Method m) {
|
||||||
|
return match(m.getReturnType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean match(Class<?> type) {
|
||||||
|
if (propertyClass.equals(type)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
boolean matchSubType = propertyClass.isAssignableFrom(type);
|
||||||
|
|
||||||
|
if (MatchOption.SUB_TYPE == this.matchOption) {
|
||||||
|
return matchSubType;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean matchSuperType = type.isAssignableFrom(propertyClass);
|
||||||
|
|
||||||
|
if (MatchOption.SUPER_TYPE == this.matchOption) {
|
||||||
|
return matchSuperType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MatchOption.ALL == this.matchOption) {
|
||||||
|
return matchSubType || matchSuperType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
<?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-picketlink-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-picketlink-api</artifactId>
|
|
||||||
<name>Keycloak Picketlink API</name>
|
|
||||||
<description />
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</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>
|
|
|
@ -1,14 +0,0 @@
|
||||||
package org.keycloak.picketlink;
|
|
||||||
|
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
|
||||||
import org.keycloak.provider.Provider;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface PartitionManagerProvider extends Provider {
|
|
||||||
|
|
||||||
PartitionManager getPartitionManager(UserFederationProviderModel model);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package org.keycloak.picketlink;
|
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface PartitionManagerProviderFactory extends ProviderFactory<PartitionManagerProvider> {
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package org.keycloak.picketlink;
|
|
||||||
|
|
||||||
import org.keycloak.provider.Provider;
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
import org.keycloak.provider.Spi;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class PartitionManagerSpi implements Spi {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "picketlink-idm";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends Provider> getProviderClass() {
|
|
||||||
return PartitionManagerProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return PartitionManagerProviderFactory.class;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.picketlink.PartitionManagerSpi
|
|
|
@ -1,66 +0,0 @@
|
||||||
<?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-picketlink-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-picketlink-ldap</artifactId>
|
|
||||||
<name>Keycloak Picketlink LDAP</name>
|
|
||||||
<description />
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-picketlink-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketlink</groupId>
|
|
||||||
<artifactId>picketlink-idm-impl</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</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>
|
|
|
@ -1,63 +0,0 @@
|
||||||
package org.keycloak.picketlink.idm;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.event.CredentialUpdatedEvent;
|
|
||||||
import org.picketlink.idm.event.EventBridge;
|
|
||||||
import org.picketlink.idm.internal.ContextualIdentityManager;
|
|
||||||
import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
|
|
||||||
import org.picketlink.idm.ldap.internal.LDAPOperationManager;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
import org.picketlink.idm.spi.CredentialStore;
|
|
||||||
import org.picketlink.idm.spi.IdentityContext;
|
|
||||||
import org.picketlink.idm.spi.StoreSelector;
|
|
||||||
|
|
||||||
import javax.naming.directory.BasicAttribute;
|
|
||||||
import javax.naming.directory.DirContext;
|
|
||||||
import javax.naming.directory.ModificationItem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class KeycloakEventBridge implements EventBridge {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(KeycloakEventBridge.class);
|
|
||||||
|
|
||||||
private final boolean updateUserAccountAfterPasswordUpdate;
|
|
||||||
|
|
||||||
public KeycloakEventBridge(boolean updateUserAccountAfterPasswordUpdate) {
|
|
||||||
this.updateUserAccountAfterPasswordUpdate = updateUserAccountAfterPasswordUpdate;
|
|
||||||
if (updateUserAccountAfterPasswordUpdate) {
|
|
||||||
logger.info("userAccountControl attribute will be updated in Active Directory after user registration");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void raiseEvent(Object event) {
|
|
||||||
// Used in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
|
|
||||||
if (updateUserAccountAfterPasswordUpdate && event instanceof CredentialUpdatedEvent) {
|
|
||||||
CredentialUpdatedEvent credEvent = ((CredentialUpdatedEvent) event);
|
|
||||||
PartitionManager partitionManager = credEvent.getPartitionMananger();
|
|
||||||
ContextualIdentityManager identityManager = (ContextualIdentityManager) partitionManager.createIdentityManager();
|
|
||||||
IdentityContext identityCtx = identityManager.getIdentityContext();
|
|
||||||
|
|
||||||
CredentialStore store = identityManager.getStoreSelector().getStoreForCredentialOperation(identityCtx, credEvent.getCredential().getClass());
|
|
||||||
if (store instanceof LDAPIdentityStore) {
|
|
||||||
LDAPIdentityStore ldapStore = (LDAPIdentityStore)store;
|
|
||||||
LDAPOperationManager operationManager = ldapStore.getOperationManager();
|
|
||||||
User picketlinkUser = (User) credEvent.getAccount();
|
|
||||||
String userDN = ldapStore.getBindingDN(picketlinkUser, true);
|
|
||||||
|
|
||||||
ModificationItem[] mods = new ModificationItem[1];
|
|
||||||
BasicAttribute mod0 = new BasicAttribute("userAccountControl", "512");
|
|
||||||
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
|
|
||||||
operationManager.modifyAttribute(userDN, mod0);
|
|
||||||
logger.debug("Attribute userAccountControls switched to 512 after password update of user " + picketlinkUser.getLoginName());
|
|
||||||
} else {
|
|
||||||
logger.debug("Store for credential updates is not LDAPIdentityStore. Ignored");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package org.keycloak.picketlink.idm;
|
|
||||||
|
|
||||||
import org.picketlink.idm.IdentityManager;
|
|
||||||
import org.picketlink.idm.config.LDAPMappingConfiguration;
|
|
||||||
import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
|
||||||
import org.picketlink.idm.credential.storage.CredentialStorage;
|
|
||||||
import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
|
|
||||||
import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
|
|
||||||
import org.picketlink.idm.model.Account;
|
|
||||||
import org.picketlink.idm.model.basic.BasicModel;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
import org.picketlink.idm.spi.IdentityContext;
|
|
||||||
|
|
||||||
import javax.naming.directory.SearchResult;
|
|
||||||
|
|
||||||
import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
|
|
||||||
|
|
||||||
// Overridden as in Keycloak, we don't have Agents
|
|
||||||
@Override
|
|
||||||
protected User getAccount(IdentityContext context, String loginName) {
|
|
||||||
IdentityManager identityManager = getIdentityManager(context);
|
|
||||||
|
|
||||||
if (CREDENTIAL_LOGGER.isDebugEnabled()) {
|
|
||||||
CREDENTIAL_LOGGER.debugf("Trying to find account [%s] using default account type [%s]", loginName, User.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BasicModel.getUser(identityManager, loginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean validateCredential(IdentityContext context, CredentialStorage credentialStorage, UsernamePasswordCredentials credentials, LDAPIdentityStore ldapIdentityStore) {
|
|
||||||
Account account = getAccount(context, credentials.getUsername());
|
|
||||||
char[] password = credentials.getPassword().getValue();
|
|
||||||
String userDN = (String) account.getAttribute(LDAPIdentityStore.ENTRY_DN_ATTRIBUTE_NAME).getValue();
|
|
||||||
if (CREDENTIAL_LOGGER.isDebugEnabled()) {
|
|
||||||
CREDENTIAL_LOGGER.debugf("Using DN [%s] for authentication of user [%s]", userDN, credentials.getUsername());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ldapIdentityStore.getOperationManager().authenticate(userDN, new String(password))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package org.keycloak.picketlink.ldap;
|
|
||||||
|
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
|
||||||
import org.keycloak.picketlink.PartitionManagerProvider;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class LDAPPartitionManagerProvider implements PartitionManagerProvider {
|
|
||||||
|
|
||||||
private final PartitionManagerRegistry partitionManagerRegistry;
|
|
||||||
|
|
||||||
public LDAPPartitionManagerProvider(PartitionManagerRegistry partitionManagerRegistry) {
|
|
||||||
this.partitionManagerRegistry = partitionManagerRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartitionManager getPartitionManager(UserFederationProviderModel model) {
|
|
||||||
return partitionManagerRegistry.getPartitionManager(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package org.keycloak.picketlink.ldap;
|
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.picketlink.PartitionManagerProvider;
|
|
||||||
import org.keycloak.picketlink.PartitionManagerProviderFactory;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains {@link PartitionManager} instances from shared {@link PartitionManagerRegistry} and uses UserFederationModel configuration for it
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class LDAPPartitionManagerProviderFactory implements PartitionManagerProviderFactory {
|
|
||||||
|
|
||||||
private PartitionManagerRegistry partitionManagerRegistry;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PartitionManagerProvider create(KeycloakSession session) {
|
|
||||||
return new LDAPPartitionManagerProvider(partitionManagerRegistry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
partitionManagerRegistry = new PartitionManagerRegistry();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "ldap";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
package org.keycloak.picketlink.ldap;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.models.LDAPConstants;
|
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
|
||||||
import org.keycloak.picketlink.idm.KeycloakEventBridge;
|
|
||||||
import org.keycloak.picketlink.idm.LDAPKeycloakCredentialHandler;
|
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.config.IdentityConfigurationBuilder;
|
|
||||||
import org.picketlink.idm.config.LDAPMappingConfigurationBuilder;
|
|
||||||
import org.picketlink.idm.config.LDAPStoreConfigurationBuilder;
|
|
||||||
import org.picketlink.idm.internal.DefaultPartitionManager;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static org.picketlink.common.constants.LDAPConstants.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class PartitionManagerRegistry {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PartitionManagerRegistry.class);
|
|
||||||
|
|
||||||
private Map<String, PartitionManagerContext> partitionManagers = new ConcurrentHashMap<String, PartitionManagerContext>();
|
|
||||||
|
|
||||||
public PartitionManager getPartitionManager(UserFederationProviderModel model) {
|
|
||||||
PartitionManagerContext context = partitionManagers.get(model.getId());
|
|
||||||
|
|
||||||
// Ldap config might have changed for the realm. In this case, we must re-initialize
|
|
||||||
Map<String, String> config = model.getConfig();
|
|
||||||
if (context == null || !config.equals(context.config)) {
|
|
||||||
logLDAPConfig(model.getId(), config);
|
|
||||||
|
|
||||||
PartitionManager manager = createPartitionManager(config);
|
|
||||||
context = new PartitionManagerContext(config, manager);
|
|
||||||
partitionManagers.put(model.getId(), context);
|
|
||||||
}
|
|
||||||
return context.partitionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't log LDAP password
|
|
||||||
private void logLDAPConfig(String fedProviderId, Map<String, String> ldapConfig) {
|
|
||||||
Map<String, String> copy = new HashMap<String, String>(ldapConfig);
|
|
||||||
copy.remove(LDAPConstants.BIND_CREDENTIAL);
|
|
||||||
logger.infof("Creating new LDAP based partition manager for the Federation provider: " + fedProviderId + ", LDAP Configuration: " + copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ldapConfig from realm
|
|
||||||
* @return PartitionManager instance based on LDAP store
|
|
||||||
*/
|
|
||||||
public static PartitionManager createPartitionManager(Map<String,String> ldapConfig) {
|
|
||||||
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
|
|
||||||
|
|
||||||
Properties connectionProps = new Properties();
|
|
||||||
if (ldapConfig.containsKey(LDAPConstants.CONNECTION_POOLING)) {
|
|
||||||
connectionProps.put("com.sun.jndi.ldap.connect.pool", ldapConfig.get(LDAPConstants.CONNECTION_POOLING));
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1000");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
|
|
||||||
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
|
|
||||||
|
|
||||||
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
|
|
||||||
|
|
||||||
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
|
|
||||||
|
|
||||||
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
|
|
||||||
if (ldapLoginNameMapping == null) {
|
|
||||||
ldapLoginNameMapping = activeDirectory ? CN : UID;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ldapFirstNameMapping = activeDirectory ? "givenName" : CN;
|
|
||||||
String createTimestampMapping = activeDirectory ? "whenCreated" : CREATE_TIMESTAMP;
|
|
||||||
String modifyTimestampMapping = activeDirectory ? "whenChanged" : MODIFY_TIMESTAMP;
|
|
||||||
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
|
|
||||||
|
|
||||||
boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
|
|
||||||
|
|
||||||
// Use same mapping for User and Agent for now
|
|
||||||
LDAPStoreConfigurationBuilder ldapStoreBuilder =
|
|
||||||
builder
|
|
||||||
.named("SIMPLE_LDAP_STORE_CONFIG")
|
|
||||||
.stores()
|
|
||||||
.ldap()
|
|
||||||
.connectionProperties(connectionProps)
|
|
||||||
.addCredentialHandler(LDAPKeycloakCredentialHandler.class)
|
|
||||||
.baseDN(ldapConfig.get(LDAPConstants.BASE_DN))
|
|
||||||
.bindDN(ldapConfig.get(LDAPConstants.BIND_DN))
|
|
||||||
.bindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
|
|
||||||
.url(ldapConfig.get(LDAPConstants.CONNECTION_URL))
|
|
||||||
.activeDirectory(activeDirectory)
|
|
||||||
.supportAllFeatures()
|
|
||||||
.pagination(pagination);
|
|
||||||
|
|
||||||
// RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
|
|
||||||
if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
|
|
||||||
ldapStoreBuilder.uniqueIdentifierAttributeName("nsuniqueid");
|
|
||||||
} else if (LDAPConstants.VENDOR_TIVOLI.equals(vendor)) {
|
|
||||||
ldapStoreBuilder.uniqueIdentifierAttributeName("uniqueidentifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
LDAPMappingConfigurationBuilder ldapUserMappingBuilder = ldapStoreBuilder
|
|
||||||
.mapping(User.class)
|
|
||||||
.baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
|
|
||||||
.objectClasses(userObjectClasses)
|
|
||||||
.attribute("loginName", ldapLoginNameMapping, true)
|
|
||||||
.attribute("firstName", ldapFirstNameMapping)
|
|
||||||
.attribute("lastName", SN)
|
|
||||||
.attribute("email", EMAIL)
|
|
||||||
.readOnlyAttribute("createdDate", createTimestampMapping)
|
|
||||||
.readOnlyAttribute("modifyDate", modifyTimestampMapping);
|
|
||||||
|
|
||||||
if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
|
|
||||||
ldapUserMappingBuilder.bindingAttribute("fullName", CN);
|
|
||||||
logger.infof("Using 'cn' attribute for DN of user and 'sAMAccountName' for username");
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakEventBridge eventBridge = new KeycloakEventBridge(activeDirectory && "true".equals(ldapConfig.get(LDAPConstants.USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE)));
|
|
||||||
return new DefaultPartitionManager(builder.buildAll(), eventBridge, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkSystemProperty(String name, String defaultValue) {
|
|
||||||
if (System.getProperty(name) == null) {
|
|
||||||
System.setProperty(name, defaultValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
|
|
||||||
private static String[] getUserObjectClasses(Map<String,String> ldapConfig) {
|
|
||||||
String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
|
|
||||||
String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
|
|
||||||
|
|
||||||
String[] objectClasses = objClassesStr.split(",");
|
|
||||||
|
|
||||||
// Trim them
|
|
||||||
String[] userObjectClasses = new String[objectClasses.length];
|
|
||||||
for (int i=0 ; i<objectClasses.length ; i++) {
|
|
||||||
userObjectClasses[i] = objectClasses[i].trim();
|
|
||||||
}
|
|
||||||
return userObjectClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PartitionManagerContext {
|
|
||||||
|
|
||||||
private PartitionManagerContext(Map<String,String> config, PartitionManager manager) {
|
|
||||||
this.config = config;
|
|
||||||
this.partitionManager = manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String,String> config;
|
|
||||||
private PartitionManager partitionManager;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.picketlink.ldap.LDAPPartitionManagerProviderFactory
|
|
|
@ -1,24 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>keycloak-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<artifactId>keycloak-picketlink-parent</artifactId>
|
|
||||||
<name>Keycloak Picketlink</name>
|
|
||||||
<description />
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>keycloak-picketlink-api</module>
|
|
||||||
<module>keycloak-picketlink-ldap</module>
|
|
||||||
</modules>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
1
pom.xml
1
pom.xml
|
@ -114,7 +114,6 @@
|
||||||
<module>model</module>
|
<module>model</module>
|
||||||
<module>integration</module>
|
<module>integration</module>
|
||||||
<module>proxy</module>
|
<module>proxy</module>
|
||||||
<module>picketlink</module>
|
|
||||||
<module>federation</module>
|
<module>federation</module>
|
||||||
<module>services</module>
|
<module>services</module>
|
||||||
<module>saml</module>
|
<module>saml</module>
|
||||||
|
|
|
@ -102,12 +102,6 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-picketlink-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelReadOnlyException;
|
import org.keycloak.models.ModelReadOnlyException;
|
||||||
|
@ -21,7 +23,6 @@ import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserFederationProvider;
|
import org.keycloak.models.UserFederationProvider;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.picketlink.PartitionManagerProvider;
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
@ -35,8 +36,6 @@ import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -57,19 +56,19 @@ public class FederationProvidersIntegrationTest {
|
||||||
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
|
addUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
|
||||||
|
|
||||||
Map<String,String> ldapConfig = ldapRule.getConfig();
|
Map<String,String> ldapConfig = ldapRule.getConfig();
|
||||||
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
|
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
|
||||||
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
|
||||||
|
|
||||||
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
|
||||||
|
|
||||||
// Delete all LDAP users and add some new for testing
|
// Delete all LDAP users and add some new for testing
|
||||||
PartitionManager partitionManager = getPartitionManager(manager.getSession(), ldapModel);
|
LDAPIdentityStore ldapStore = getLdapIdentityStore(manager.getSession(), ldapModel);
|
||||||
LDAPUtils.removeAllUsers(partitionManager);
|
LDAPUtils.removeAllUsers(ldapStore);
|
||||||
|
|
||||||
User john = LDAPUtils.addUser(partitionManager, "johnkeycloak", "John", "Doe", "john@email.org");
|
LDAPUser john = LDAPUtils.addUser(ldapStore, "johnkeycloak", "John", "Doe", "john@email.org");
|
||||||
LDAPUtils.updatePassword(partitionManager, john, "Password1");
|
LDAPUtils.updatePassword(ldapStore, john, "Password1");
|
||||||
|
|
||||||
User existing = LDAPUtils.addUser(partitionManager, "existing", "Existing", "Foo", "existing@email.org");
|
LDAPUser existing = LDAPUtils.addUser(ldapStore, "existing", "Existing", "Foo", "existing@email.org");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -339,13 +338,13 @@ public class FederationProvidersIntegrationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSearch() {
|
public void testSearch() {
|
||||||
KeycloakSession session = keycloakRule.startSession();
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
PartitionManager partitionManager = getPartitionManager(session, ldapModel);
|
LDAPIdentityStore ldapStore = getLdapIdentityStore(session, ldapModel);
|
||||||
try {
|
try {
|
||||||
RealmModel appRealm = session.realms().getRealmByName("test");
|
RealmModel appRealm = session.realms().getRealmByName("test");
|
||||||
LDAPUtils.addUser(partitionManager, "username1", "John1", "Doel1", "user1@email.org");
|
LDAPUtils.addUser(ldapStore, "username1", "John1", "Doel1", "user1@email.org");
|
||||||
LDAPUtils.addUser(partitionManager, "username2", "John2", "Doel2", "user2@email.org");
|
LDAPUtils.addUser(ldapStore, "username2", "John2", "Doel2", "user2@email.org");
|
||||||
LDAPUtils.addUser(partitionManager, "username3", "John3", "Doel3", "user3@email.org");
|
LDAPUtils.addUser(ldapStore, "username3", "John3", "Doel3", "user3@email.org");
|
||||||
LDAPUtils.addUser(partitionManager, "username4", "John4", "Doel4", "user4@email.org");
|
LDAPUtils.addUser(ldapStore, "username4", "John4", "Doel4", "user4@email.org");
|
||||||
|
|
||||||
// Users are not at local store at this moment
|
// Users are not at local store at this moment
|
||||||
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
|
Assert.assertNull(session.userStorage().getUserByUsername("username1", appRealm));
|
||||||
|
@ -395,7 +394,7 @@ public class FederationProvidersIntegrationTest {
|
||||||
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
|
Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
|
||||||
|
|
||||||
// LDAP password is still unchanged
|
// LDAP password is still unchanged
|
||||||
Assert.assertTrue(LDAPUtils.validatePassword(getPartitionManager(session, model), "johnkeycloak", "Password1"));
|
Assert.assertTrue(LDAPUtils.validatePassword(getLdapIdentityStore(session, model), user, "Password1"));
|
||||||
|
|
||||||
// ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
|
// ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
|
||||||
Assert.assertFalse(session.users().removeUser(appRealm, user));
|
Assert.assertFalse(session.users().removeUser(appRealm, user));
|
||||||
|
@ -412,9 +411,10 @@ public class FederationProvidersIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
|
static LDAPIdentityStore getLdapIdentityStore(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
|
||||||
PartitionManagerProvider partitionManagerProvider = keycloakSession.getProvider(PartitionManagerProvider.class);
|
LDAPFederationProviderFactory ldapProviderFactory = (LDAPFederationProviderFactory) keycloakSession.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, ldapFedModel.getProviderName());
|
||||||
return partitionManagerProvider.getPartitionManager(ldapFedModel);
|
LDAPFederationProvider ldapProvider = ldapProviderFactory.getInstance(keycloakSession, ldapFedModel);
|
||||||
|
return ldapProvider.getLdapIdentityStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,10 @@ import org.junit.Test;
|
||||||
import org.junit.rules.RuleChain;
|
import org.junit.rules.RuleChain;
|
||||||
import org.junit.rules.TestRule;
|
import org.junit.rules.TestRule;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
|
||||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||||
import org.keycloak.federation.ldap.LDAPUtils;
|
import org.keycloak.federation.ldap.LDAPUtils;
|
||||||
|
import org.keycloak.federation.ldap.idm.model.LDAPUser;
|
||||||
|
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
|
@ -25,8 +26,6 @@ import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
import org.keycloak.testutils.DummyUserFederationProviderFactory;
|
import org.keycloak.testutils.DummyUserFederationProviderFactory;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
import org.picketlink.idm.PartitionManager;
|
|
||||||
import org.picketlink.idm.model.basic.User;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -50,26 +49,20 @@ public class SyncProvidersTest {
|
||||||
Time.setOffset(0);
|
Time.setOffset(0);
|
||||||
|
|
||||||
Map<String,String> ldapConfig = ldapRule.getConfig();
|
Map<String,String> ldapConfig = ldapRule.getConfig();
|
||||||
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
|
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "false");
|
||||||
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
|
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
|
||||||
|
|
||||||
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
|
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
|
||||||
-1, -1, 0);
|
-1, -1, 0);
|
||||||
|
|
||||||
// Delete all LDAP users and add 5 new users for testing
|
// Delete all LDAP users and add 5 new users for testing
|
||||||
PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(manager.getSession(), ldapModel);
|
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(manager.getSession(), ldapModel);
|
||||||
LDAPUtils.removeAllUsers(partitionManager);
|
LDAPUtils.removeAllUsers(ldapStore);
|
||||||
|
|
||||||
User user1 = LDAPUtils.addUser(partitionManager, "user1", "User1FN", "User1LN", "user1@email.org");
|
for (int i=1 ; i<6 ; i++) {
|
||||||
LDAPUtils.updatePassword(partitionManager, user1, "Password1");
|
LDAPUser user = LDAPUtils.addUser(ldapStore, "user" + i, "User" + i + "FN", "User" + i + "LN", "user" + i + "@email.org");
|
||||||
User user2 = LDAPUtils.addUser(partitionManager, "user2", "User2FN", "User2LN", "user2@email.org");
|
LDAPUtils.updatePassword(ldapStore, user, "Password1");
|
||||||
LDAPUtils.updatePassword(partitionManager, user2, "Password2");
|
}
|
||||||
User user3 = LDAPUtils.addUser(partitionManager, "user3", "User3FN", "User3LN", "user3@email.org");
|
|
||||||
LDAPUtils.updatePassword(partitionManager, user3, "Password3");
|
|
||||||
User user4 = LDAPUtils.addUser(partitionManager, "user4", "User4FN", "User4LN", "user4@email.org");
|
|
||||||
LDAPUtils.updatePassword(partitionManager, user4, "Password4");
|
|
||||||
User user5 = LDAPUtils.addUser(partitionManager, "user5", "User5FN", "User5LN", "user5@email.org");
|
|
||||||
LDAPUtils.updatePassword(partitionManager, user5, "Password5");
|
|
||||||
|
|
||||||
// Add dummy provider
|
// Add dummy provider
|
||||||
dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
|
dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
|
||||||
|
@ -122,9 +115,9 @@ public class SyncProvidersTest {
|
||||||
sleep(1000);
|
sleep(1000);
|
||||||
|
|
||||||
// Add user to LDAP and update 'user5' in LDAP
|
// Add user to LDAP and update 'user5' in LDAP
|
||||||
PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel);
|
LDAPIdentityStore ldapStore = FederationProvidersIntegrationTest.getLdapIdentityStore(session, ldapModel);
|
||||||
LDAPUtils.addUser(partitionManager, "user6", "User6FN", "User6LN", "user6@email.org");
|
LDAPUtils.addUser(ldapStore, "user6", "User6FN", "User6LN", "user6@email.org");
|
||||||
LDAPUtils.updateUser(partitionManager, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
|
LDAPUtils.updateUser(ldapStore, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
|
||||||
|
|
||||||
// Assert still old users in local provider
|
// Assert still old users in local provider
|
||||||
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
|
assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
|
||||||
|
|
Loading…
Reference in a new issue