diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index d36543dd63..92f6df07b8 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -112,6 +112,11 @@
keycloak-picketlink-api
${project.version}
+
+ org.keycloak
+ keycloak-picketlink-ldap
+ ${project.version}
+
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index 0ce4c2e9c5..a156a54088 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -25,6 +25,12 @@
${project.version}
provided
+
+ org.keycloak
+ keycloak-picketlink-api
+ ${project.version}
+ provided
+
org.jboss.resteasy
resteasy-jaxrs
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
deleted file mode 100755
index 1ff6c119a1..0000000000
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.keycloak.federation.ldap;
-
-import org.keycloak.models.utils.reflection.Reflections;
-import org.picketlink.idm.config.LDAPMappingConfiguration;
-import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
-import org.picketlink.idm.ldap.internal.LDAPOperationManager;
-import org.picketlink.idm.model.AttributedType;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.IdentityQuery;
-import org.picketlink.idm.spi.IdentityContext;
-
-import javax.naming.directory.BasicAttributes;
-import java.lang.reflect.Method;
-
-import static org.picketlink.common.constants.LDAPConstants.*;
-
-/**
- * @author Marek Posolda
- */
-public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
-
- public static Method GET_OPERATION_MANAGER_METHOD;
- public static Method CREATE_SEARCH_FILTER_METHOD;
- public static Method EXTRACT_ATTRIBUTES_METHOD;
- public static Method GET_ENTRY_IDENTIFIER_METHOD;
-
- public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
-
- static {
- GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
- CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
- EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
- GET_ENTRY_IDENTIFIER_METHOD = getMethodOnLDAPStore("getEntryIdentifier", AttributedType.class);
- }
-
- @Override
- public void addAttributedType(IdentityContext context, AttributedType attributedType) {
- LDAPMappingConfiguration userMappingConfig = getConfig().getMappingConfig(attributedType.getClass());
- String ldapUsernameAttrName = userMappingConfig.getMappedProperties().get(userMappingConfig.getIdProperty().getName());
-
- if (getConfig().isActiveDirectory() && SAM_ACCOUNT_NAME.equals(ldapUsernameAttrName)) {
- // TODO: pain, but everything in picketlink is private... Improve if possible...
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, this);
-
- String cn = getCommonName(attributedType);
- String bindingDN = CN + EQUAL + cn + COMMA + userMappingConfig.getBaseDN();
-
- BasicAttributes ldapAttributes = Reflections.invokeMethod(false, EXTRACT_ATTRIBUTES_METHOD, BasicAttributes.class, this, attributedType, true);
- ldapAttributes.put(CN, cn);
-
- operationManager.createSubContext(bindingDN, ldapAttributes);
-
- String ldapId = Reflections.invokeMethod(false, GET_ENTRY_IDENTIFIER_METHOD, String.class, this, attributedType);
- attributedType.setId(ldapId);
- } else {
- super.addAttributedType(context, attributedType);
- }
- }
-
- // Hack as methods are protected on LDAPIdentityStore :/
- public static Method getMethodOnLDAPStore(String methodName, Class... classes) {
- try {
- Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
- m.setAccessible(true);
- return m;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- protected String getCommonName(AttributedType aType) {
- User user = (User)aType;
- String fullName;
- if (user.getFirstName() != null && user.getLastName() != null) {
- fullName = user.getFirstName() + " " + user.getLastName();
- } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
- fullName = user.getFirstName();
- } else {
- fullName = user.getLastName();
- }
-
- // Fallback to loginName
- if (fullName == null || fullName.trim().length() == 0) {
- fullName = user.getLoginName();
- }
-
- return fullName;
- }
-}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 27a6dd2c82..562e247715 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -16,6 +16,7 @@ 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.IdentityQuery;
@@ -123,6 +124,7 @@ public class LDAPFederationProvider implements UserFederationProvider {
picketlinkUser.setFirstName(user.getFirstName());
picketlinkUser.setLastName(user.getLastName());
picketlinkUser.setEmail(user.getEmail());
+ picketlinkUser.setAttribute(new Attribute("fullName", getFullName(user)));
identityManager.add(picketlinkUser);
user.setAttribute(LDAP_ID, picketlinkUser.getId());
return proxy(user);
@@ -321,4 +323,23 @@ public class LDAPFederationProvider implements UserFederationProvider {
public void close() {
//To change body of implemented methods use File | Settings | File Templates.
}
+
+ // Needed for ActiveDirectory updates
+ protected String getFullName(UserModel user) {
+ String fullName;
+ if (user.getFirstName() != null && user.getLastName() != null) {
+ fullName = user.getFirstName() + " " + user.getLastName();
+ } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
+ fullName = user.getFirstName();
+ } else {
+ fullName = user.getLastName();
+ }
+
+ // Fallback to loginName
+ if (fullName == null || fullName.trim().length() == 0) {
+ fullName = user.getUsername();
+ }
+
+ return fullName;
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index 8da274e916..ad6d18508e 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -5,10 +5,10 @@ import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.picketlink.PartitionManagerProvider;
import org.picketlink.idm.PartitionManager;
import java.util.Collections;
-import java.util.List;
import java.util.Set;
/**
@@ -18,7 +18,6 @@ import java.util.Set;
*/
public class LDAPFederationProviderFactory implements UserFederationProviderFactory {
public static final String PROVIDER_NAME = "ldap";
- PartitionManagerRegistry registry;
@Override
public UserFederationProvider create(KeycloakSession session) {
@@ -27,13 +26,13 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
@Override
public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
- PartitionManager partition = registry.getPartitionManager(model);
+ PartitionManagerProvider idmProvider = session.getProvider(PartitionManagerProvider.class);
+ PartitionManager partition = idmProvider.getPartitionManager(model);
return new LDAPFederationProvider(session, model, partition);
}
@Override
public void init(Config.Scope config) {
- registry = new PartitionManagerRegistry();
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
deleted file mode 100755
index 7078d87494..0000000000
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.keycloak.federation.ldap;
-
-import org.keycloak.models.utils.reflection.Reflections;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.config.LDAPMappingConfiguration;
-import org.picketlink.idm.credential.Credentials;
-import org.picketlink.idm.credential.Password;
-import org.picketlink.idm.credential.UsernamePasswordCredentials;
-import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
-import org.picketlink.idm.ldap.internal.LDAPOperationManager;
-import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
-import org.picketlink.idm.model.Account;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.IdentityQuery;
-import org.picketlink.idm.spi.IdentityContext;
-
-import javax.naming.NamingException;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-import javax.naming.directory.SearchResult;
-import java.io.UnsupportedEncodingException;
-import java.util.Date;
-import java.util.List;
-
-import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
-import static org.picketlink.idm.model.basic.BasicModel.getUser;
-
-/**
- * @author Marek Posolda
- */
-public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
-
- // Used just 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
- private String userAccountControlAfterPasswordUpdate;
-
- @Override
- public void setup(LDAPIdentityStore store) {
- // TODO: Don't setup it here once PLINK-508 is fixed
- if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) {
- String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate");
- this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512";
- CREDENTIAL_LOGGER.info("Will use userAccountControl=" + userAccountControlAfterPasswordUpdate + " after password update of user in Active Directory");
- }
- }
-
- // 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 getUser(identityManager, loginName);
- }
-
- @Override
- public void update(IdentityContext context, Account account, Password password, LDAPIdentityStore store, Date effectiveDate, Date expiryDate) {
- if (!store.getConfig().isActiveDirectory()) {
- super.update(context, account, password, store, effectiveDate, expiryDate);
- } else {
- User user = (User)account;
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
- IdentityManager identityManager = getIdentityManager(context);
- String userDN = getDNOfUser(user, identityManager, store, operationManager);
-
- updateADPassword(userDN, new String(password.getValue()), operationManager);
-
- if (userAccountControlAfterPasswordUpdate != null) {
- ModificationItem[] mods = new ModificationItem[1];
- BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
- mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
- operationManager.modifyAttribute(userDN, mod0);
- }
- }
- }
-
- protected void updateADPassword(String userDN, String password, LDAPOperationManager operationManager) {
- 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);
- operationManager.modifyAttribute(userDN, unicodePwd);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void validate(IdentityContext context, UsernamePasswordCredentials credentials,
- LDAPIdentityStore store) {
- credentials.setStatus(Credentials.Status.INVALID);
- credentials.setValidatedAccount(null);
-
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Validating credentials [%s][%s] using identity store [%s] and credential handler [%s].", credentials.getClass(), credentials, store, this);
- }
-
- User account = getAccount(context, credentials.getUsername());
-
- // If the user for the provided username cannot be found we fail validation
- if (account != null) {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Found account [%s] from credentials [%s].", account, credentials);
- }
-
- if (account.isEnabled()) {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account [%s] is ENABLED.", account, credentials);
- }
-
- char[] password = credentials.getPassword().getValue();
-
- // String bindingDN = store.getBindingDN(account);
-
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
- String bindingDN = getDNOfUser(account, getIdentityManager(context), store, operationManager);
-
- if (operationManager.authenticate(bindingDN, new String(password))) {
- credentials.setValidatedAccount(account);
- credentials.setStatus(Credentials.Status.VALID);
- }
- } else {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account [%s] is DISABLED.", account, credentials);
- }
- credentials.setStatus(Credentials.Status.ACCOUNT_DISABLED);
- }
- } else {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account NOT FOUND for credentials [%s][%s].", credentials.getClass(), credentials);
- }
- }
-
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Credential [%s][%s] validated using identity store [%s] and credential handler [%s]. Status [%s]. Validated Account [%s]",
- credentials.getClass(), credentials, store, this, credentials.getStatus(), credentials.getValidatedAccount());
- }
- }
-
- // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingDN, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
- // but DN like: cn=John Doe,OU=foo,DC=bar
- protected String getDNOfUser(User user, IdentityManager identityManager, LDAPIdentityStore ldapStore, LDAPOperationManager operationManager) {
-
- LDAPMappingConfiguration ldapEntryConfig = ldapStore.getConfig().getMappingConfig(User.class);
- IdentityQuery identityQuery = identityManager.createIdentityQuery(User.class)
- .setParameter(User.LOGIN_NAME, user.getLoginName());
- StringBuilder filter = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.CREATE_SEARCH_FILTER_METHOD, StringBuilder.class, ldapStore, identityQuery, ldapEntryConfig);
-
- List search = null;
- try {
- search = operationManager.search(ldapEntryConfig.getBaseDN(), filter.toString(), ldapEntryConfig);
- } catch (NamingException ne) {
- throw new RuntimeException(ne);
- }
-
- if (search.size() > 0) {
- SearchResult sr1 = search.get(0);
- return sr1.getNameInNamespace();
- } else {
- return null;
- }
- }
-}
diff --git a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 3b6ac1c092..ba29870729 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -18,4 +18,6 @@ public class LDAPConstants {
public static final String USER_DN_SUFFIX = "userDnSuffix";
public static final String BIND_DN = "bindDn";
public static final String BIND_CREDENTIAL = "bindCredential";
+
+ public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
}
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java
deleted file mode 100644
index 3ee589e343..0000000000
--- a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/AbstractIdentityManagerProvider.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.keycloak.picketlink;
-
-import org.keycloak.models.RealmModel;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-
-/**
- * Per-request IdentityManager caching . Not thread-safe
- *
- * @author Marek Posolda
- */
-public abstract class AbstractIdentityManagerProvider implements IdentityManagerProvider {
-
- private IdentityManager identityManager;
-
- @Override
- public IdentityManager getIdentityManager(RealmModel realm) {
- if (identityManager == null) {
- PartitionManager partitionManager = getPartitionManager(realm);
- identityManager = partitionManager.createIdentityManager();
- }
-
- return identityManager;
- }
-
- protected abstract PartitionManager getPartitionManager(RealmModel realm);
-
- @Override
- public void close() {
- identityManager = null;
- }
-}
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProvider.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProvider.java
deleted file mode 100644
index 4b9ec6e3dd..0000000000
--- a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProvider.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.keycloak.picketlink;
-
-import org.keycloak.models.RealmModel;
-import org.keycloak.provider.Provider;
-import org.picketlink.idm.IdentityManager;
-
-/**
- * @author Marek Posolda
- */
-public interface IdentityManagerProvider extends Provider {
-
- IdentityManager getIdentityManager(RealmModel realm);
-}
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProvider.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProvider.java
new file mode 100644
index 0000000000..0b22305364
--- /dev/null
+++ b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProvider.java
@@ -0,0 +1,14 @@
+package org.keycloak.picketlink;
+
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.provider.Provider;
+import org.picketlink.idm.PartitionManager;
+
+/**
+ *
+ * @author Marek Posolda
+ */
+public interface PartitionManagerProvider extends Provider {
+
+ PartitionManager getPartitionManager(UserFederationProviderModel model);
+}
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProviderFactory.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProviderFactory.java
similarity index 60%
rename from picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProviderFactory.java
rename to picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProviderFactory.java
index 1456e98e09..203c7f9c23 100644
--- a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerProviderFactory.java
+++ b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerProviderFactory.java
@@ -5,5 +5,5 @@ import org.keycloak.provider.ProviderFactory;
/**
* @author Marek Posolda
*/
-public interface IdentityManagerProviderFactory extends ProviderFactory {
+public interface PartitionManagerProviderFactory extends ProviderFactory {
}
diff --git a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerSpi.java b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerSpi.java
similarity index 70%
rename from picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerSpi.java
rename to picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerSpi.java
index 42111da0d3..721cf12d68 100644
--- a/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/IdentityManagerSpi.java
+++ b/picketlink/keycloak-picketlink-api/src/main/java/org/keycloak/picketlink/PartitionManagerSpi.java
@@ -7,19 +7,19 @@ import org.keycloak.provider.Spi;
/**
* @author Stian Thorgersen
*/
-public class IdentityManagerSpi implements Spi {
+public class PartitionManagerSpi implements Spi {
@Override
public String getName() {
- return "picketlink-identity-manager";
+ return "picketlink-idm";
}
@Override
public Class extends Provider> getProviderClass() {
- return IdentityManagerProvider.class;
+ return PartitionManagerProvider.class;
}
@Override
public Class extends ProviderFactory> getProviderFactoryClass() {
- return IdentityManagerProviderFactory.class;
+ return PartitionManagerProviderFactory.class;
}
}
diff --git a/picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index ffcb6e5424..88b1bcc076 100644
--- a/picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/picketlink/keycloak-picketlink-api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1 +1 @@
-org.keycloak.picketlink.IdentityManagerSpi
\ No newline at end of file
+org.keycloak.picketlink.PartitionManagerSpi
\ No newline at end of file
diff --git a/picketlink/keycloak-picketlink-realm/pom.xml b/picketlink/keycloak-picketlink-ldap/pom.xml
similarity index 95%
rename from picketlink/keycloak-picketlink-realm/pom.xml
rename to picketlink/keycloak-picketlink-ldap/pom.xml
index d95eaaabc5..9360aa81d8 100755
--- a/picketlink/keycloak-picketlink-realm/pom.xml
+++ b/picketlink/keycloak-picketlink-ldap/pom.xml
@@ -10,8 +10,8 @@
4.0.0
- keycloak-picketlink-realm
- Keycloak Picketlink Realm
+ keycloak-picketlink-ldap
+ Keycloak Picketlink LDAP
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/KeycloakEventBridge.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/KeycloakEventBridge.java
new file mode 100644
index 0000000000..90a221eefb
--- /dev/null
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/KeycloakEventBridge.java
@@ -0,0 +1,57 @@
+package org.keycloak.picketlink.idm;
+
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+
+import org.jboss.logging.Logger;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.event.CredentialUpdatedEvent;
+import org.picketlink.idm.event.EventBridge;
+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;
+
+/**
+ * @author Marek Posolda
+ */
+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;
+ }
+
+ @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();
+ IdentityContext identityCtx = (IdentityContext)partitionManager.createIdentityManager();
+
+ CredentialStore store = ((StoreSelector)partitionManager).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");
+ }
+
+ }
+ }
+}
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
new file mode 100644
index 0000000000..7844e8e19f
--- /dev/null
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
@@ -0,0 +1,27 @@
+package org.keycloak.picketlink.idm;
+
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.spi.IdentityContext;
+
+import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
+
+/**
+ * @author Marek Posolda
+ */
+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);
+ }
+}
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProvider.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProvider.java
new file mode 100644
index 0000000000..bbb7201e69
--- /dev/null
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProvider.java
@@ -0,0 +1,26 @@
+package org.keycloak.picketlink.ldap;
+
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.picketlink.PartitionManagerProvider;
+import org.picketlink.idm.PartitionManager;
+
+/**
+ * @author Marek Posolda
+ */
+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() {
+ }
+}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProviderFactory.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProviderFactory.java
similarity index 54%
rename from picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProviderFactory.java
rename to picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProviderFactory.java
index 616e96d43e..b647057e46 100644
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProviderFactory.java
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/LDAPPartitionManagerProviderFactory.java
@@ -1,23 +1,23 @@
-package org.keycloak.picketlink.realm;
+package org.keycloak.picketlink.ldap;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.picketlink.IdentityManagerProvider;
-import org.keycloak.picketlink.IdentityManagerProviderFactory;
+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 realm configuration for it
+ * Obtains {@link PartitionManager} instances from shared {@link PartitionManagerRegistry} and uses UserFederationModel configuration for it
*
* @author Marek Posolda
*/
-public class RealmIdentityManagerProviderFactory implements IdentityManagerProviderFactory {
+public class LDAPPartitionManagerProviderFactory implements PartitionManagerProviderFactory {
private PartitionManagerRegistry partitionManagerRegistry;
@Override
- public IdentityManagerProvider create(KeycloakSession session) {
- return new RealmIdentityManagerProvider(partitionManagerRegistry);
+ public PartitionManagerProvider create(KeycloakSession session) {
+ return new LDAPPartitionManagerProvider(partitionManagerRegistry);
}
@Override
@@ -31,7 +31,7 @@ public class RealmIdentityManagerProviderFactory implements IdentityManagerProvi
@Override
public String getId() {
- return "realm";
+ return "ldap";
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
old mode 100755
new mode 100644
similarity index 61%
rename from federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
rename to picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
index 9992b877de..53e4cbe68b
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
@@ -1,23 +1,26 @@
-package org.keycloak.federation.ldap;
+package org.keycloak.picketlink.ldap;
-import org.jboss.logging.Logger;
-import org.keycloak.models.UserFederationProviderModel;
-import org.keycloak.models.LDAPConstants;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.config.AbstractIdentityStoreConfiguration;
-import org.picketlink.idm.config.IdentityConfiguration;
-import org.picketlink.idm.config.IdentityConfigurationBuilder;
-import org.picketlink.idm.config.IdentityStoreConfiguration;
-import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
-import org.picketlink.idm.internal.DefaultPartitionManager;
-import org.picketlink.idm.model.basic.User;
-
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
-import static org.picketlink.common.constants.LDAPConstants.*;
+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 static org.picketlink.common.constants.LDAPConstants.CN;
+import static org.picketlink.common.constants.LDAPConstants.EMAIL;
+import static org.picketlink.common.constants.LDAPConstants.SN;
+import static org.picketlink.common.constants.LDAPConstants.UID;
/**
* @author Marek Posolda
@@ -34,8 +37,8 @@ public class PartitionManagerRegistry {
// Ldap config might have changed for the realm. In this case, we must re-initialize
Map config = model.getConfig();
if (context == null || !config.equals(context.config)) {
- logger.infof("Creating new partition manager for the federation provider: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", model.getId(),
- config.get(LDAPConstants.CONNECTION_URL), config.get(LDAPConstants.BASE_DN), config.get(LDAPConstants.VENDOR));
+ logLDAPConfig(model.getId(), config);
+
PartitionManager manager = createPartitionManager(config);
context = new PartitionManagerContext(config, manager);
partitionManagers.put(model.getId(), context);
@@ -43,6 +46,13 @@ public class PartitionManagerRegistry {
return context.partitionManager;
}
+ // Don't log LDAP password
+ private void logLDAPConfig(String fedProviderId, Map ldapConfig) {
+ Map copy = new HashMap(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
@@ -55,7 +65,7 @@ public class PartitionManagerRegistry {
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", "10");
+ 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");
@@ -63,11 +73,6 @@ public class PartitionManagerRegistry {
String vendor = ldapConfig.get(LDAPConstants.VENDOR);
- // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
- if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
- checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
- }
-
boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
@@ -75,17 +80,11 @@ public class PartitionManagerRegistry {
ldapLoginNameMapping = activeDirectory ? CN : UID;
}
- // Try to compute properties based on LDAP server type, but still allow to override them through System properties TODO: Should allow better way than overriding from System properties. Perhaps init from XML?
- ldapLoginNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", ldapLoginNameMapping, ldapLoginNameMapping, activeDirectory);
- String ldapFirstNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
- String ldapLastNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
- String ldapEmailMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
-
+ String ldapFirstNameMapping = activeDirectory ? "givenName" : CN;
String[] userObjectClasses = getUserObjectClasses(ldapConfig);
- logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginNameMapping, ldapFirstNameMapping, ldapLastNameMapping, ldapEmailMapping);
-
// Use same mapping for User and Agent for now
+ LDAPStoreConfigurationBuilder ldapStoreBuilder =
builder
.named("SIMPLE_LDAP_STORE_CONFIG")
.stores()
@@ -97,21 +96,29 @@ public class PartitionManagerRegistry {
.bindCredential(ldapConfig.get(LDAPConstants.BIND_CREDENTIAL))
.url(ldapConfig.get(LDAPConstants.CONNECTION_URL))
.activeDirectory(activeDirectory)
- .supportAllFeatures()
- .mapping(User.class)
- .baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
- .objectClasses(userObjectClasses)
- .attribute("loginName", ldapLoginNameMapping, true)
- .attribute("firstName", ldapFirstNameMapping)
- .attribute("lastName", ldapLastNameMapping)
- .attribute("email", ldapEmailMapping);
+ .supportAllFeatures();
- // Workaround to override the LDAPIdentityStore with our own :/
- List identityConfigs = builder.buildAll();
- IdentityStoreConfiguration identityStoreConfig = identityConfigs.get(0).getStoreConfiguration().get(0);
- ((AbstractIdentityStoreConfiguration)identityStoreConfig).setIdentityStoreType(KeycloakLDAPIdentityStore.class);
+ // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
+ if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
+ ldapStoreBuilder.uniqueIdentifierAttributeName("nsuniqueid");
+ }
- return new DefaultPartitionManager(identityConfigs);
+ 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);
+
+ 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) {
@@ -120,16 +127,6 @@ public class PartitionManagerRegistry {
}
}
- private static String getNameOfLDAPAttribute(String systemPropertyName, String defaultAttrName, String defaultAttrNameInActiveDirectory, boolean activeDirectory) {
- // System property has biggest priority if available
- String sysProperty = System.getProperty(systemPropertyName);
- if (sysProperty != null) {
- return sysProperty;
- }
-
- return activeDirectory ? defaultAttrNameInActiveDirectory : defaultAttrName;
- }
-
// Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
private static String[] getUserObjectClasses(Map ldapConfig) {
String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
diff --git a/picketlink/keycloak-picketlink-ldap/src/main/resources/META-INF/services/org.keycloak.picketlink.PartitionManagerProviderFactory b/picketlink/keycloak-picketlink-ldap/src/main/resources/META-INF/services/org.keycloak.picketlink.PartitionManagerProviderFactory
new file mode 100644
index 0000000000..5bfaf9d173
--- /dev/null
+++ b/picketlink/keycloak-picketlink-ldap/src/main/resources/META-INF/services/org.keycloak.picketlink.PartitionManagerProviderFactory
@@ -0,0 +1 @@
+org.keycloak.picketlink.ldap.LDAPPartitionManagerProviderFactory
\ No newline at end of file
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java
deleted file mode 100644
index 124bb8294e..0000000000
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/KeycloakLDAPIdentityStore.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.keycloak.picketlink.idm;
-
-import java.lang.reflect.Method;
-
-import javax.naming.directory.BasicAttributes;
-
-import org.keycloak.models.utils.reflection.Reflections;
-import org.picketlink.idm.config.LDAPMappingConfiguration;
-import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
-import org.picketlink.idm.ldap.internal.LDAPOperationManager;
-import org.picketlink.idm.model.AttributedType;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.IdentityQuery;
-import org.picketlink.idm.spi.IdentityContext;
-
-import static org.picketlink.common.constants.LDAPConstants.CN;
-import static org.picketlink.common.constants.LDAPConstants.COMMA;
-import static org.picketlink.common.constants.LDAPConstants.EQUAL;
-
-/**
- * @author Marek Posolda
- */
-public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
-
- public static Method GET_OPERATION_MANAGER_METHOD;
- public static Method CREATE_SEARCH_FILTER_METHOD;
- public static Method EXTRACT_ATTRIBUTES_METHOD;
- public static Method GET_ENTRY_IDENTIFIER_METHOD;
-
- public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
-
- static {
- GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
- CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
- EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
- GET_ENTRY_IDENTIFIER_METHOD = getMethodOnLDAPStore("getEntryIdentifier", AttributedType.class);
- }
-
- @Override
- public void addAttributedType(IdentityContext context, AttributedType attributedType) {
- LDAPMappingConfiguration userMappingConfig = getConfig().getMappingConfig(attributedType.getClass());
- String ldapUsernameAttrName = userMappingConfig.getMappedProperties().get(userMappingConfig.getIdProperty().getName());
-
- if (getConfig().isActiveDirectory() && SAM_ACCOUNT_NAME.equals(ldapUsernameAttrName)) {
- // TODO: pain, but everything in picketlink is private... Improve if possible...
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, this);
-
- String cn = getCommonName(attributedType);
- String bindingDN = CN + EQUAL + cn + COMMA + userMappingConfig.getBaseDN();
-
- BasicAttributes ldapAttributes = Reflections.invokeMethod(false, EXTRACT_ATTRIBUTES_METHOD, BasicAttributes.class, this, attributedType, true);
- ldapAttributes.put(CN, cn);
-
- operationManager.createSubContext(bindingDN, ldapAttributes);
-
- String ldapId = Reflections.invokeMethod(false, GET_ENTRY_IDENTIFIER_METHOD, String.class, this, attributedType);
- attributedType.setId(ldapId);
- } else {
- super.addAttributedType(context, attributedType);
- }
- }
-
- // Hack as methods are protected on LDAPIdentityStore :/
- public static Method getMethodOnLDAPStore(String methodName, Class... classes) {
- try {
- Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
- m.setAccessible(true);
- return m;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- protected String getCommonName(AttributedType aType) {
- User user = (User)aType;
- String fullName;
- if (user.getFirstName() != null && user.getLastName() != null) {
- fullName = user.getFirstName() + " " + user.getLastName();
- } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
- fullName = user.getFirstName();
- } else {
- fullName = user.getLastName();
- }
-
- // Fallback to loginName
- if (fullName == null || fullName.trim().length() == 0) {
- fullName = user.getLoginName();
- }
-
- return fullName;
- }
-}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
deleted file mode 100644
index cf4d70c81b..0000000000
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/idm/LDAPKeycloakCredentialHandler.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.keycloak.picketlink.idm;
-
-import java.io.UnsupportedEncodingException;
-import java.util.Date;
-import java.util.List;
-
-import javax.naming.NamingException;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-import javax.naming.directory.SearchResult;
-
-import org.keycloak.models.utils.reflection.Reflections;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.config.LDAPMappingConfiguration;
-import org.picketlink.idm.credential.Credentials;
-import org.picketlink.idm.credential.Password;
-import org.picketlink.idm.credential.UsernamePasswordCredentials;
-import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
-import org.picketlink.idm.ldap.internal.LDAPOperationManager;
-import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
-import org.picketlink.idm.model.Account;
-import org.picketlink.idm.model.basic.User;
-import org.picketlink.idm.query.IdentityQuery;
-import org.picketlink.idm.spi.IdentityContext;
-
-import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
-import static org.picketlink.idm.model.basic.BasicModel.getUser;
-
-/**
- * @author Marek Posolda
- */
-public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
-
- // Used just 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
- private String userAccountControlAfterPasswordUpdate;
-
- @Override
- public void setup(LDAPIdentityStore store) {
- // TODO: Don't setup it here once PLINK-508 is fixed
- if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) {
- String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate");
- this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512";
- CREDENTIAL_LOGGER.info("Will use userAccountControl=" + userAccountControlAfterPasswordUpdate + " after password update of user in Active Directory");
- }
- }
-
- // 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 getUser(identityManager, loginName);
- }
-
- @Override
- public void update(IdentityContext context, Account account, Password password, LDAPIdentityStore store, Date effectiveDate, Date expiryDate) {
- if (!store.getConfig().isActiveDirectory()) {
- super.update(context, account, password, store, effectiveDate, expiryDate);
- } else {
- User user = (User)account;
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
- IdentityManager identityManager = getIdentityManager(context);
- String userDN = getDNOfUser(user, identityManager, store, operationManager);
-
- updateADPassword(userDN, new String(password.getValue()), operationManager);
-
- if (userAccountControlAfterPasswordUpdate != null) {
- ModificationItem[] mods = new ModificationItem[1];
- BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
- mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
- operationManager.modifyAttribute(userDN, mod0);
- }
- }
- }
-
- protected void updateADPassword(String userDN, String password, LDAPOperationManager operationManager) {
- 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);
- operationManager.modifyAttribute(userDN, unicodePwd);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void validate(IdentityContext context, UsernamePasswordCredentials credentials,
- LDAPIdentityStore store) {
- credentials.setStatus(Credentials.Status.INVALID);
- credentials.setValidatedAccount(null);
-
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Validating credentials [%s][%s] using identity store [%s] and credential handler [%s].", credentials.getClass(), credentials, store, this);
- }
-
- User account = getAccount(context, credentials.getUsername());
-
- // If the user for the provided username cannot be found we fail validation
- if (account != null) {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Found account [%s] from credentials [%s].", account, credentials);
- }
-
- if (account.isEnabled()) {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account [%s] is ENABLED.", account, credentials);
- }
-
- char[] password = credentials.getPassword().getValue();
-
- // String bindingDN = store.getBindingDN(account);
-
- LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
- String bindingDN = getDNOfUser(account, getIdentityManager(context), store, operationManager);
-
- if (operationManager.authenticate(bindingDN, new String(password))) {
- credentials.setValidatedAccount(account);
- credentials.setStatus(Credentials.Status.VALID);
- }
- } else {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account [%s] is DISABLED.", account, credentials);
- }
- credentials.setStatus(Credentials.Status.ACCOUNT_DISABLED);
- }
- } else {
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Account NOT FOUND for credentials [%s][%s].", credentials.getClass(), credentials);
- }
- }
-
- if (CREDENTIAL_LOGGER.isDebugEnabled()) {
- CREDENTIAL_LOGGER.debugf("Credential [%s][%s] validated using identity store [%s] and credential handler [%s]. Status [%s]. Validated Account [%s]",
- credentials.getClass(), credentials, store, this, credentials.getStatus(), credentials.getValidatedAccount());
- }
- }
-
- // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingDN, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
- // but DN like: cn=John Doe,OU=foo,DC=bar
- protected String getDNOfUser(User user, IdentityManager identityManager, LDAPIdentityStore ldapStore, LDAPOperationManager operationManager) {
-
- LDAPMappingConfiguration ldapEntryConfig = ldapStore.getConfig().getMappingConfig(User.class);
- IdentityQuery identityQuery = identityManager.createIdentityQuery(User.class)
- .setParameter(User.LOGIN_NAME, user.getLoginName());
- StringBuilder filter = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.CREATE_SEARCH_FILTER_METHOD, StringBuilder.class, ldapStore, identityQuery, ldapEntryConfig);
-
- List search = null;
- try {
- search = operationManager.search(ldapEntryConfig.getBaseDN(), filter.toString(), ldapEntryConfig);
- } catch (NamingException ne) {
- throw new RuntimeException(ne);
- }
-
- if (search.size() > 0) {
- SearchResult sr1 = search.get(0);
- return sr1.getNameInNamespace();
- } else {
- return null;
- }
- }
-}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
deleted file mode 100644
index a084eb4e12..0000000000
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/PartitionManagerRegistry.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.keycloak.picketlink.realm;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.jboss.logging.Logger;
-import org.keycloak.models.LDAPConstants;
-import org.keycloak.models.RealmModel;
-import org.keycloak.picketlink.idm.KeycloakLDAPIdentityStore;
-import org.keycloak.picketlink.idm.LDAPKeycloakCredentialHandler;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.config.AbstractIdentityStoreConfiguration;
-import org.picketlink.idm.config.IdentityConfiguration;
-import org.picketlink.idm.config.IdentityConfigurationBuilder;
-import org.picketlink.idm.config.IdentityStoreConfiguration;
-import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
-import org.picketlink.idm.internal.DefaultPartitionManager;
-import org.picketlink.idm.model.basic.User;
-
-import static org.picketlink.common.constants.LDAPConstants.CN;
-import static org.picketlink.common.constants.LDAPConstants.EMAIL;
-import static org.picketlink.common.constants.LDAPConstants.SN;
-import static org.picketlink.common.constants.LDAPConstants.UID;
-
-/**
- * @author Marek Posolda
- */
-public class PartitionManagerRegistry {
-
- private static final Logger logger = Logger.getLogger(PartitionManagerRegistry.class);
-
- private Map partitionManagers = new ConcurrentHashMap();
-
- public PartitionManager getPartitionManager(RealmModel realm) {
- Map ldapConfig = realm.getLdapServerConfig();
- if (ldapConfig == null || ldapConfig.isEmpty()) {
- logger.warnf("Ldap configuration is missing for realm '%s'", realm.getName());
- return null;
- }
-
- PartitionManagerContext context = partitionManagers.get(realm.getId());
-
- // Ldap config might have changed for the realm. In this case, we must re-initialize
- if (context == null || !ldapConfig.equals(context.config)) {
- logger.infof("Creating new partition manager for the realm: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", realm.getId(),
- ldapConfig.get(LDAPConstants.CONNECTION_URL), ldapConfig.get(LDAPConstants.BASE_DN), ldapConfig.get(LDAPConstants.VENDOR));
- PartitionManager manager = createPartitionManager(ldapConfig);
- context = new PartitionManagerContext(ldapConfig, manager);
- partitionManagers.put(realm.getId(), context);
- }
- return context.partitionManager;
- }
-
- /**
- * @param ldapConfig from realm
- * @return PartitionManager instance based on LDAP store
- */
- protected PartitionManager createPartitionManager(Map ldapConfig) {
- IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
-
- Properties connectionProps = new Properties();
- connectionProps.put("com.sun.jndi.ldap.connect.pool", "true");
-
- 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", "10");
- 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);
-
- // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
- if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
- checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
- }
-
- 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;
- }
-
- // Try to compute properties based on LDAP server type, but still allow to override them through System properties TODO: Should allow better way than overriding from System properties. Perhaps init from XML?
- ldapLoginNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", ldapLoginNameMapping, ldapLoginNameMapping, activeDirectory);
- String ldapFirstNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
- String ldapLastNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
- String ldapEmailMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
-
- String[] userObjectClasses = getUserObjectClasses(ldapConfig);
-
- logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginNameMapping, ldapFirstNameMapping, ldapLastNameMapping, ldapEmailMapping);
-
- // Use same mapping for User and Agent for now
- 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()
- .mapping(User.class)
- .baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
- .objectClasses(userObjectClasses)
- .attribute("loginName", ldapLoginNameMapping, true)
- .attribute("firstName", ldapFirstNameMapping)
- .attribute("lastName", ldapLastNameMapping)
- .attribute("email", ldapEmailMapping);
-
- // Workaround to override the LDAPIdentityStore with our own :/
- List identityConfigs = builder.buildAll();
- IdentityStoreConfiguration identityStoreConfig = identityConfigs.get(0).getStoreConfiguration().get(0);
- ((AbstractIdentityStoreConfiguration)identityStoreConfig).setIdentityStoreType(KeycloakLDAPIdentityStore.class);
-
- return new DefaultPartitionManager(identityConfigs);
- }
-
- private void checkSystemProperty(String name, String defaultValue) {
- if (System.getProperty(name) == null) {
- System.setProperty(name, defaultValue);
- }
- }
-
- private String getNameOfLDAPAttribute(String systemPropertyName, String defaultAttrName, String defaultAttrNameInActiveDirectory, boolean activeDirectory) {
- // System property has biggest priority if available
- String sysProperty = System.getProperty(systemPropertyName);
- if (sysProperty != null) {
- return sysProperty;
- }
-
- return activeDirectory ? defaultAttrNameInActiveDirectory : defaultAttrName;
- }
-
- // Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
- private String[] getUserObjectClasses(Map 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 config, PartitionManager manager) {
- this.config = config;
- this.partitionManager = manager;
- }
-
- private Map config;
- private PartitionManager partitionManager;
- }
-}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java b/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java
deleted file mode 100644
index 849aa98e3b..0000000000
--- a/picketlink/keycloak-picketlink-realm/src/main/java/org/keycloak/picketlink/realm/RealmIdentityManagerProvider.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.keycloak.picketlink.realm;
-
-import org.keycloak.models.RealmModel;
-import org.keycloak.picketlink.AbstractIdentityManagerProvider;
-import org.keycloak.picketlink.IdentityManagerProvider;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-
-/**
- * @author Marek Posolda
- */
-public class RealmIdentityManagerProvider extends AbstractIdentityManagerProvider {
-
- private final PartitionManagerRegistry partitionManagerRegistry;
-
- public RealmIdentityManagerProvider(PartitionManagerRegistry partitionManagerRegistry) {
- this.partitionManagerRegistry = partitionManagerRegistry;
- }
-
- @Override
- protected PartitionManager getPartitionManager(RealmModel realm) {
- return partitionManagerRegistry.getPartitionManager(realm);
- }
-}
diff --git a/picketlink/keycloak-picketlink-realm/src/main/resources/META-INF/services/org.keycloak.picketlink.IdentityManagerProviderFactory b/picketlink/keycloak-picketlink-realm/src/main/resources/META-INF/services/org.keycloak.picketlink.IdentityManagerProviderFactory
deleted file mode 100644
index 87130cbf8b..0000000000
--- a/picketlink/keycloak-picketlink-realm/src/main/resources/META-INF/services/org.keycloak.picketlink.IdentityManagerProviderFactory
+++ /dev/null
@@ -1 +0,0 @@
-org.keycloak.picketlink.realm.RealmIdentityManagerProviderFactory
\ No newline at end of file
diff --git a/picketlink/pom.xml b/picketlink/pom.xml
index 30d9b06e73..4cefc46f5e 100755
--- a/picketlink/pom.xml
+++ b/picketlink/pom.xml
@@ -17,6 +17,7 @@
keycloak-picketlink-api
+ keycloak-picketlink-ldap
diff --git a/pom.xml b/pom.xml
index 6e8c88ddd2..dcecacf3c4 100755
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
2.3.7.Final
3.0.8.Final
1.0.15.Final
- 2.6.0.CR5
+ 2.7.0.Beta1-20140731
1.0.2.Final
2.11.3
3.1.4.GA
diff --git a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
index 6f7b21e420..7101a8f8fb 100755
--- a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
@@ -78,7 +78,7 @@
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
"port": "${keycloak.connectionsMongo.port:27017}",
"db": "${keycloak.connectionsMongo.db:keycloak}",
- "clearOnStartup": "${keycloak.connectionsMongo.clearOnStartup:true}"
+ "clearOnStartup": "${keycloak.connectionsMongo.clearOnStartup:false}"
}
}
}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/LDAPTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/LDAPTestUtils.java
index fa9f703fe8..cf1ab6126a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/LDAPTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/LDAPTestUtils.java
@@ -1,9 +1,6 @@
package org.keycloak.testsuite;
-import org.keycloak.federation.ldap.PartitionManagerRegistry;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.picketlink.IdentityManagerProvider;
+import org.keycloak.picketlink.ldap.PartitionManagerRegistry;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.credential.Password;