Merge pull request #458 from mposolda/master

Support for Active Directory and RHDS
This commit is contained in:
Bill Burke 2014-06-09 15:15:15 -04:00
commit 9a3abf6a6a
16 changed files with 226 additions and 70 deletions

View file

@ -67,6 +67,10 @@ public class PicketlinkAuthenticationProvider implements AuthenticationProvider
try { try {
User picketlinkUser = new User(username); User picketlinkUser = new User(username);
identityManager.add(picketlinkUser); identityManager.add(picketlinkUser);
// Hack needed due to ActiveDirectory bug in Picketlink TODO: Remove once https://issues.jboss.org/browse/PLINK-485 fixed and updated in keycloak master
picketlinkUser = BasicModel.getUser(identityManager, picketlinkUser.getLoginName());
return picketlinkUser.getId(); return picketlinkUser.getId();
} catch (IdentityManagementException ie) { } catch (IdentityManagementException ie) {
throw convertIDMException(ie); throw convertIDMException(ie);

View file

@ -902,6 +902,12 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
module.controller('RealmLdapSettingsCtrl', function($scope, $location, Notifications, Realm, realm) { module.controller('RealmLdapSettingsCtrl', function($scope, $location, Notifications, Realm, realm) {
console.log('RealmLdapSettingsCtrl'); console.log('RealmLdapSettingsCtrl');
$scope.ldapVendors = [
{ "id": "ad", "name": "Active Directory" },
{ "id": "rhds", "name": "Red Hat Directory Server" },
{ "id": "other", "name": "Other" }
];
$scope.realm = realm; $scope.realm = realm;
var oldCopy = angular.copy($scope.realm); var oldCopy = angular.copy($scope.realm);

View file

@ -13,6 +13,17 @@
<fieldset> <fieldset>
<legend><span class="text">Required Settings</span></legend> <legend><span class="text">Required Settings</span></legend>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="vendor">Vendor <span class="required">*</span></label>
<div class="col-sm-4">
<div class="select-kc">
<select id="vendor"
ng-model="realm.ldapServer.vendor"
ng-options="vendor.id as vendor.name for vendor in ldapVendors">
</select>
</div>
</div>
</div>
<div class="form-group clearfix"> <div class="form-group clearfix">
<label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL <span class="required">*</span></label> <label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL <span class="required">*</span></label>
<div class="col-sm-4"> <div class="col-sm-4">

View file

@ -49,6 +49,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
protected boolean startEmbeddedLdapLerver = true; protected boolean startEmbeddedLdapLerver = true;
protected String bindDn = "uid=admin,ou=system"; protected String bindDn = "uid=admin,ou=system";
protected String bindCredential = "secret"; protected String bindCredential = "secret";
protected String vendor;
public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url"; public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn"; public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
@ -59,6 +60,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server"; public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server";
public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn"; public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn";
public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential"; public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential";
public static String IDM_TEST_LDAP_VENDOR = "idm.test.ldap.vendor";
public LDAPEmbeddedServer() { public LDAPEmbeddedServer() {
@ -85,6 +87,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true")); startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true"));
bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn); bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn);
bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential); bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential);
vendor = p.getProperty(IDM_TEST_LDAP_VENDOR);
} }
@Override @Override
@ -132,6 +135,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
ldapConfig.put(LdapConstants.BIND_DN, getBindDn()); ldapConfig.put(LdapConstants.BIND_DN, getBindDn());
ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential()); ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential());
ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix()); ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix());
ldapConfig.put(LdapConstants.VENDOR, getVendor());
realm.setLdapServerConfig(ldapConfig); realm.setLdapServerConfig(ldapConfig);
} }
@ -202,6 +206,10 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
return bindCredential; return bindCredential;
} }
public String getVendor() {
return vendor;
}
@Override @Override
public void importLDIF(String fileName) throws Exception { public void importLDIF(String fileName) throws Exception {
// import LDIF only in case we are running against embedded LDAP server // import LDIF only in case we are running against embedded LDAP server

View file

@ -75,23 +75,23 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
@Test @Test
public void testLdapAuthentication() { public void testLdapAuthentication() {
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password"); MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
// Set password of user in LDAP // Set password of user in LDAP
LdapTestUtils.setLdapPassword(providerSession, realm, "john", "password"); LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
// Verify that user doesn't exists in realm2 and can't authenticate here // Verify that user doesn't exists in realm2 and can't authenticate here
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData));
Assert.assertNull(realm.getUser("john")); Assert.assertNull(realm.getUser("johnkeycloak"));
// Add ldap authenticationProvider // Add ldap authenticationProvider
setupAuthenticationProviders(); setupAuthenticationProviders();
// Authenticate john and verify that now he exists in realm // Authenticate john and verify that now he exists in realm
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
UserModel john = realm.getUser("john"); UserModel john = realm.getUser("johnkeycloak");
Assert.assertNotNull(john); Assert.assertNotNull(john);
Assert.assertEquals("john", john.getLoginName()); Assert.assertEquals("johnkeycloak", john.getLoginName());
Assert.assertEquals("John", john.getFirstName()); Assert.assertEquals("John", john.getFirstName());
Assert.assertEquals("Doe", john.getLastName()); Assert.assertEquals("Doe", john.getLastName());
Assert.assertEquals("john@email.org", john.getEmail()); Assert.assertEquals("john@email.org", john.getEmail());
@ -119,7 +119,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_USER, am.authenticateForm(null, realm, formData));
// User exists in ldap // User exists in ldap
formData = AuthProvidersExternalModelTest.createFormData("john", "invalid"); formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "invalid");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData));
// User exists in realm // User exists in realm
@ -142,23 +142,23 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
// Add ldap // Add ldap
setupAuthenticationProviders(); setupAuthenticationProviders();
LdapTestUtils.setLdapPassword(providerSession, realm, "john", "password"); LdapTestUtils.setLdapPassword(providerSession, realm, "johnkeycloak", "password");
// First authenticate successfully to sync john into realm // First authenticate successfully to sync john into realm
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password"); MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
// Change credential and validate that user can authenticate // Change credential and validate that user can authenticate
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm, providerSession); AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm, providerSession);
UserModel john = realm.getUser("john"); UserModel john = realm.getUser("johnkeycloak");
try { try {
Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated")); Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
} catch (AuthenticationProviderException ape) { } catch (AuthenticationProviderException ape) {
ape.printStackTrace(); ape.printStackTrace();
Assert.fail("Error not expected"); Assert.fail("Error not expected");
} }
formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated"); formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password-updated");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
// Password updated just in LDAP, so validating directly in realm should fail // Password updated just in LDAP, so validating directly in realm should fail
@ -174,7 +174,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
ape.printStackTrace(); ape.printStackTrace();
Assert.fail("Error not expected"); Assert.fail("Error not expected");
} }
formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated2"); formData = AuthProvidersExternalModelTest.createFormData("johnkeycloak", "password-updated2");
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData)); Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm, formData));
} }

View file

@ -201,10 +201,11 @@ public class ImportTest extends AbstractModelTest {
// Test ldap config // Test ldap config
Map<String, String> ldapConfig = realm.getLdapServerConfig(); Map<String, String> ldapConfig = realm.getLdapServerConfig();
Assert.assertTrue(ldapConfig.size() == 5); Assert.assertTrue(ldapConfig.size() == 6);
Assert.assertEquals("ldap://localhost:10389", ldapConfig.get("connectionUrl")); Assert.assertEquals("ldap://localhost:10389", ldapConfig.get("connectionUrl"));
Assert.assertEquals("dc=keycloak,dc=org", ldapConfig.get("baseDn")); Assert.assertEquals("dc=keycloak,dc=org", ldapConfig.get("baseDn"));
Assert.assertEquals("ou=People,dc=keycloak,dc=org", ldapConfig.get("userDnSuffix")); Assert.assertEquals("ou=People,dc=keycloak,dc=org", ldapConfig.get("userDnSuffix"));
Assert.assertEquals("other", ldapConfig.get("vendor"));
// Test authentication providers // Test authentication providers
List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders(); List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();

View file

@ -9,12 +9,12 @@ objectclass: top
objectclass: organizationalUnit objectclass: organizationalUnit
ou: People ou: People
dn: uid=john,ou=People,dc=keycloak,dc=org dn: uid=johnkeycloak,ou=People,dc=keycloak,dc=org
objectclass: top objectclass: top
objectclass: uidObject objectclass: uidObject
objectclass: person objectclass: person
objectclass: inetOrgPerson objectclass: inetOrgPerson
uid: john uid: johnkeycloak
cn: John cn: John
sn: Doe sn: Doe
mail: john@email.org mail: john@email.org

View file

@ -17,7 +17,8 @@
"baseDn": "dc=keycloak,dc=org", "baseDn": "dc=keycloak,dc=org",
"userDnSuffix": "ou=People,dc=keycloak,dc=org", "userDnSuffix": "ou=People,dc=keycloak,dc=org",
"bindDn": "uid=admin,ou=system", "bindDn": "uid=admin,ou=system",
"bindCredential": "secret" "bindCredential": "secret",
"vendor": "other"
}, },
"socialProviders": { "socialProviders": {
"google.key": "abc", "google.key": "abc",

View file

@ -0,0 +1,32 @@
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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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;
}
}

View file

@ -1,27 +0,0 @@
package org.keycloak.picketlink.idm;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
import org.picketlink.idm.model.Account;
import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.spi.IdentityContext;
import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
import static org.picketlink.idm.model.basic.BasicModel.getUser;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPAgentIgnoreCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
@Override
protected Account 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);
}
}

View file

@ -0,0 +1,93 @@
package org.keycloak.picketlink.idm;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.credential.Password;
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.AttributedType;
import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.spi.IdentityContext;
import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
import static org.picketlink.idm.model.basic.BasicModel.getUser;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
private static Method GET_BINDING_DN_METHOD;
private static Method GET_OPERATION_MANAGER_METHOD;
static {
GET_BINDING_DN_METHOD = getMethodOnLDAPStore("getBindingDN", AttributedType.class);
GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
}
// 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) {
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 Account 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) {
super.update(context, account, password, store, effectiveDate, expiryDate);
if (userAccountControlAfterPasswordUpdate != null) {
ModificationItem[] mods = new ModificationItem[1];
BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
try {
String bindingDN = (String) GET_BINDING_DN_METHOD.invoke(store, account);
LDAPOperationManager operationManager = (LDAPOperationManager) GET_OPERATION_MANAGER_METHOD.invoke(store);
operationManager.modifyAttribute(bindingDN, mod0);
} catch (IllegalAccessException iae) {
throw new RuntimeException(iae);
} catch (InvocationTargetException ite) {
Throwable cause = ite.getTargetException() != null ? ite.getTargetException() : ite;
throw new RuntimeException(cause);
}
}
}
// Hack as methods are protected on LDAPIdentityStore :/
private 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);
}
}
}

View file

@ -5,6 +5,11 @@ package org.keycloak.picketlink.idm;
*/ */
public class LdapConstants { public class LdapConstants {
public static final String VENDOR = "vendor";
public static final String VENDOR_RHDS = "rhds";
public static final String VENDOR_ACTIVE_DIRECTORY = "ad";
public static final String VENDOR_OTHER = "other";
public static final String CONNECTION_URL = "connectionUrl"; public static final String CONNECTION_URL = "connectionUrl";
public static final String BASE_DN = "baseDn"; public static final String BASE_DN = "baseDn";
public static final String USER_DN_SUFFIX = "userDnSuffix"; public static final String USER_DN_SUFFIX = "userDnSuffix";

View file

@ -6,15 +6,15 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.picketlink.idm.LDAPAgentIgnoreCredentialHandler; import org.keycloak.picketlink.idm.LDAPKeycloakCredentialHandler;
import org.keycloak.picketlink.idm.LdapConstants; import org.keycloak.picketlink.idm.LdapConstants;
import org.picketlink.idm.PartitionManager; import org.picketlink.idm.PartitionManager;
import org.picketlink.idm.config.IdentityConfigurationBuilder; import org.picketlink.idm.config.IdentityConfigurationBuilder;
import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
import org.picketlink.idm.internal.DefaultPartitionManager; import org.picketlink.idm.internal.DefaultPartitionManager;
import org.picketlink.idm.model.basic.User; import org.picketlink.idm.model.basic.User;
import static org.picketlink.common.constants.LDAPConstants.CN; import static org.picketlink.common.constants.LDAPConstants.CN;
import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
import static org.picketlink.common.constants.LDAPConstants.EMAIL; import static org.picketlink.common.constants.LDAPConstants.EMAIL;
import static org.picketlink.common.constants.LDAPConstants.SN; import static org.picketlink.common.constants.LDAPConstants.SN;
import static org.picketlink.common.constants.LDAPConstants.UID; import static org.picketlink.common.constants.LDAPConstants.UID;
@ -39,8 +39,8 @@ public class PartitionManagerRegistry {
// Ldap config might have changed for the realm. In this case, we must re-initialize // Ldap config might have changed for the realm. In this case, we must re-initialize
if (context == null || !ldapConfig.equals(context.config)) { 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", realm.getId(), 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.CONNECTION_URL), ldapConfig.get(LdapConstants.BASE_DN), ldapConfig.get(LdapConstants.VENDOR));
PartitionManager manager = createPartitionManager(ldapConfig); PartitionManager manager = createPartitionManager(ldapConfig);
context = new PartitionManagerContext(ldapConfig, manager); context = new PartitionManagerContext(ldapConfig, manager);
partitionManagers.put(realm.getId(), context); partitionManagers.put(realm.getId(), context);
@ -66,26 +66,43 @@ public class PartitionManagerRegistry {
checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain"); checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off"); 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);
// 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?
String ldapLoginName = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", UID, CN, activeDirectory);
String ldapFirstName = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
String ldapLastName = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
String ldapEmail = getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginName, ldapFirstName, ldapLastName, ldapEmail);
// Use same mapping for User and Agent for now // Use same mapping for User and Agent for now
builder builder
.named("SIMPLE_LDAP_STORE_CONFIG") .named("SIMPLE_LDAP_STORE_CONFIG")
.stores() .stores()
.ldap() .ldap()
.connectionProperties(connectionProps) .connectionProperties(connectionProps)
.addCredentialHandler(LDAPAgentIgnoreCredentialHandler.class) .addCredentialHandler(LDAPKeycloakCredentialHandler.class)
.baseDN(ldapConfig.get(LdapConstants.BASE_DN)) .baseDN(ldapConfig.get(LdapConstants.BASE_DN))
.bindDN(ldapConfig.get(LdapConstants.BIND_DN)) .bindDN(ldapConfig.get(LdapConstants.BIND_DN))
.bindCredential(ldapConfig.get(LdapConstants.BIND_CREDENTIAL)) .bindCredential(ldapConfig.get(LdapConstants.BIND_CREDENTIAL))
.url(ldapConfig.get(LdapConstants.CONNECTION_URL)) .url(ldapConfig.get(LdapConstants.CONNECTION_URL))
.activeDirectory(activeDirectory)
.supportAllFeatures() .supportAllFeatures()
.mapping(User.class) .mapping(User.class)
.baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX)) .baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX))
.objectClasses("inetOrgPerson", "organizationalPerson") .objectClasses("inetOrgPerson", "organizationalPerson")
.attribute("loginName", UID, true) .attribute("loginName", ldapLoginName, true)
.attribute("firstName", CN) .attribute("firstName", ldapFirstName)
.attribute("lastName", SN) .attribute("lastName", ldapLastName)
.attribute("email", EMAIL) .attribute("email", ldapEmail);
.readOnlyAttribute("createdDate", CREATE_TIMESTAMP);
return new DefaultPartitionManager(builder.buildAll()); return new DefaultPartitionManager(builder.buildAll());
} }
@ -96,6 +113,16 @@ public class PartitionManagerRegistry {
} }
} }
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;
}
private class PartitionManagerContext { private class PartitionManagerContext {
private PartitionManagerContext(Map<String,String> config, PartitionManager manager) { private PartitionManagerContext(Map<String,String> config, PartitionManager manager) {

View file

@ -1,6 +1,7 @@
package org.keycloak.picketlink.realm; package org.keycloak.picketlink.realm;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.picketlink.AbstractIdentityManagerProvider;
import org.keycloak.picketlink.IdentityManagerProvider; import org.keycloak.picketlink.IdentityManagerProvider;
import org.picketlink.idm.IdentityManager; import org.picketlink.idm.IdentityManager;
import org.picketlink.idm.PartitionManager; import org.picketlink.idm.PartitionManager;
@ -8,7 +9,7 @@ import org.picketlink.idm.PartitionManager;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class RealmIdentityManagerProvider implements IdentityManagerProvider { public class RealmIdentityManagerProvider extends AbstractIdentityManagerProvider {
private final PartitionManagerRegistry partitionManagerRegistry; private final PartitionManagerRegistry partitionManagerRegistry;
@ -17,12 +18,7 @@ public class RealmIdentityManagerProvider implements IdentityManagerProvider {
} }
@Override @Override
public IdentityManager getIdentityManager(RealmModel realm) { protected PartitionManager getPartitionManager(RealmModel realm) {
PartitionManager partitionManager = partitionManagerRegistry.getPartitionManager(realm); return partitionManagerRegistry.getPartitionManager(realm);
return partitionManager.createIdentityManager();
}
@Override
public void close() {
} }
} }

View file

@ -62,7 +62,7 @@ public class AuthProvidersIntegrationTest {
// Configure LDAP // Configure LDAP
ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm); ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
LdapTestUtils.setLdapPassword(providerSession, appRealm, "john", "password"); LdapTestUtils.setLdapPassword(providerSession, appRealm, "johnkeycloak", "password");
} }
}); });
@ -135,7 +135,7 @@ public class AuthProvidersIntegrationTest {
@Test @Test
public void loginLdap() { public void loginLdap() {
loginPage.open(); loginPage.open();
loginPage.login("john", "password"); loginPage.login("johnkeycloak", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@ -186,20 +186,19 @@ public class AuthProvidersIntegrationTest {
@Test @Test
public void passwordChangeLdap() throws Exception { public void passwordChangeLdap() throws Exception {
changePasswordPage.open(); changePasswordPage.open();
loginPage.login("john", "password"); loginPage.login("johnkeycloak", "password");
changePasswordPage.changePassword("password", "new-password", "new-password"); changePasswordPage.changePassword("password", "new-password", "new-password");
Assert.assertEquals("Your password has been updated", profilePage.getSuccess()); Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
changePasswordPage.logout(); changePasswordPage.logout();
// TODO: Disabled until https://issues.jboss.org/browse/PLINK-384 is released and updated loginPage.open();
// loginPage.open(); loginPage.login("johnkeycloak", "password");
// loginPage.login("john", "password"); Assert.assertEquals("Invalid username or password.", loginPage.getError());
// Assert.assertEquals("Invalid username or password.", loginPage.getError());
loginPage.open(); loginPage.open();
loginPage.login("john", "new-password"); loginPage.login("johnkeycloak", "new-password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
} }

View file

@ -19,12 +19,12 @@ objectclass: top
objectclass: organizationalUnit objectclass: organizationalUnit
ou: Groups ou: Groups
dn: uid=john,ou=People,dc=keycloak,dc=org dn: uid=johnkeycloak,ou=People,dc=keycloak,dc=org
objectclass: top objectclass: top
objectclass: uidObject objectclass: uidObject
objectclass: person objectclass: person
objectclass: inetOrgPerson objectclass: inetOrgPerson
uid: john uid: johnkeycloak
cn: John cn: John
sn: Doe sn: Doe
mail: john@email.org mail: john@email.org