KEYCLOAK-1359 LDAP & Active directory fixes and improvements

This commit is contained in:
mposolda committed 2015-06-04 20:15:44 +02:00
1 parent d875b9885d
commit c3eb6df220
18 files changed
+240 -143

No files matched your search

@@ -45,16 +45,6 @@ public class LDAPUtils {
return ldapUser;
}
public static void removeAllUsers(LDAPFederationProvider ldapProvider, RealmModel realm) {
LDAPIdentityStore ldapStore = ldapProvider.getLdapIdentityStore();
LDAPIdentityQuery ldapQuery = LDAPUtils.createQueryForUserSearch(ldapProvider, realm);
List<LDAPObject> allUsers = ldapQuery.getResultList();
for (LDAPObject ldapUser : allUsers) {
ldapStore.remove(ldapUser);
}
}
public static LDAPIdentityQuery createQueryForUserSearch(LDAPFederationProvider ldapProvider, RealmModel realm) {
LDAPIdentityQuery ldapQuery = new LDAPIdentityQuery(ldapProvider);
LDAPConfig config = ldapProvider.getLdapIdentityStore().getConfig();
@@ -71,7 +71,7 @@ public class LDAPIdentityStore implements IdentityStore {
public void add(LDAPObject ldapObject) {
// id will be assigned by the ldap server
if (ldapObject.getUuid() != null) {
throw new IllegalStateException("Can't add object with already assigned uuid");
throw new ModelException("Can't add object with already assigned uuid");
}
String entryDN = ldapObject.getDn().toString();
@@ -108,20 +108,20 @@ public class LDAPIdentityStore implements IdentityStore {
@Override
public List<LDAPObject> fetchQueryResults(LDAPIdentityQuery identityQuery) {
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
}
List<LDAPObject> results = new ArrayList<>();
try {
if (identityQuery.getSorting() != null && !identityQuery.getSorting().isEmpty()) {
throw new ModelException("LDAP Identity Store does not yet support sorted queries.");
}
String baseDN = identityQuery.getSearchDn();
for (Condition condition : identityQuery.getConditions()) {
// Check if we are searching by ID
String uuidAttrName = getConfig().getUuidLDAPAttributeName();
if (condition.getParameter() != null && condition.getParameter().getName().equals(uuidAttrName)) {
if (condition.getParameter() != null && condition.getParameter().getName().equalsIgnoreCase(uuidAttrName)) {
if (EqualCondition.class.isInstance(condition)) {
EqualCondition equalCondition = (EqualCondition) condition;
SearchResult search = this.operationManager
@@ -147,7 +147,7 @@ public class LDAPIdentityStore implements IdentityStore {
}
for (SearchResult result : search) {
if (!result.getNameInNamespace().equals(baseDN)) {
if (!result.getNameInNamespace().equalsIgnoreCase(baseDN)) {
results.add(populateAttributedType(result, identityQuery.getReturningReadOnlyLdapAttributes()));
}
}
@@ -278,7 +278,7 @@ public class LDAPIdentityStore implements IdentityStore {
QueryParameter queryParameter = condition.getParameter();
if (!getConfig().getUuidLDAPAttributeName().equals(queryParameter.getName())) {
if (!getConfig().getUuidLDAPAttributeName().equalsIgnoreCase(queryParameter.getName())) {
String attributeName = queryParameter.getName();
if (attributeName != null) {
@@ -400,7 +400,7 @@ public class LDAPIdentityStore implements IdentityStore {
String ldapAttributeName = ldapAttribute.getID();
if (ldapAttributeName.toLowerCase().equals(getConfig().getUuidLDAPAttributeName().toLowerCase())) {
if (ldapAttributeName.equalsIgnoreCase(getConfig().getUuidLDAPAttributeName())) {
Object uuidValue = ldapAttribute.get();
ldapObject.setUuid(this.operationManager.decodeEntryUUID(uuidValue));
} else {
@@ -411,7 +411,7 @@ public class LDAPIdentityStore implements IdentityStore {
attrValues.add(attrVal);
}
if (ldapAttributeName.toLowerCase().equals(LDAPConstants.OBJECT_CLASS)) {
if (ldapAttributeName.equalsIgnoreCase(LDAPConstants.OBJECT_CLASS)) {
ldapObject.setObjectClasses(attrValues);
} else {
if (logger.isTraceEnabled()) {
@@ -444,7 +444,7 @@ public class LDAPIdentityStore implements IdentityStore {
for (Map.Entry<String, Object> attrEntry : ldapObject.getAttributes().entrySet()) {
String attrName = attrEntry.getKey();
Object attrValue = attrEntry.getValue();
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equals(attrName))) {
if (!ldapObject.getReadOnlyAttributeNames().contains(attrName) && (isCreate || !ldapObject.getRdnAttributeName().equalsIgnoreCase(attrName))) {
if (String.class.isInstance(attrValue)) {
if (attrValue.toString().trim().length() == 0) {
@@ -461,7 +461,7 @@ public class LDAPIdentityStore implements IdentityStore {
} else if (attrValue == null || attrValue.toString().trim().length() == 0) {
entryAttributes.put(attrName, LDAPConstants.EMPTY_ATTRIBUTE_VALUE);
} else {
throw new IllegalArgumentException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
throw new ModelException("Unexpected type of value of argument " + attrName + ". Value is " + attrValue);
}
}
}
@@ -473,9 +473,9 @@ public class LDAPIdentityStore implements IdentityStore {
for (String objectClassValue : ldapObject.getObjectClasses()) {
objectClassAttribute.add(objectClassValue);
if (objectClassValue.equals(LDAPConstants.GROUP_OF_NAMES)
|| objectClassValue.equals(LDAPConstants.GROUP_OF_ENTRIES)
|| objectClassValue.equals(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
if (objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_NAMES)
|| objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_ENTRIES)
|| objectClassValue.equalsIgnoreCase(LDAPConstants.GROUP_OF_UNIQUE_NAMES)) {
entryAttributes.put(LDAPConstants.MEMBER, LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
}
}
@@ -513,7 +513,6 @@ public class LDAPOperationManager {
context = createLdapContext();
return operation.execute(context);
} catch (NamingException ne) {
logger.error("Could not create Ldap context or operation execution error.", ne);
throw ne;
} finally {
if (context != null) {
@@ -1,24 +1,19 @@
package org.keycloak.federation.ldap.mappers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.EqualCondition;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
* Mapper useful for the LDAP deployments when some attribute (usually CN) is mapped to full name of user
@@ -130,7 +125,7 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
fullName = firstNameCondition.getValue() + " " + lastNameCondition.getValue();
} else if (firstNameCondition != null) {
fullName = (String) firstNameCondition.getValue();
} else if (firstNameCondition != null) {
} else if (lastNameCondition != null) {
fullName = (String) lastNameCondition.getValue();
} else {
return;
@@ -1,14 +1,11 @@
package org.keycloak.federation.ldap.mappers;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.naming.directory.SearchControls;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
@@ -126,7 +123,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
String rolesDn = getRolesDn(mapperModel);
ldapQuery.setSearchDn(rolesDn);
Collection<String> roleObjectClasses = getRoleObjectClasses(mapperModel);
Collection<String> roleObjectClasses = getRoleObjectClasses(mapperModel, ldapProvider);
ldapQuery.addObjectClasses(roleObjectClasses);
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
@@ -144,11 +141,11 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
} else {
String clientId = mapperModel.getConfig().get(CLIENT_ID);
if (clientId == null) {
throw new IllegalStateException("Using client roles mapping is requested, but parameter client.id not found!");
throw new ModelException("Using client roles mapping is requested, but parameter client.id not found!");
}
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
throw new IllegalStateException("Can't found requested client with clientId: " + clientId);
throw new ModelException("Can't found requested client with clientId: " + clientId);
}
return client;
}
@@ -157,7 +154,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
protected String getRolesDn(UserFederationMapperModel mapperModel) {
String rolesDn = mapperModel.getConfig().get(ROLES_DN);
if (rolesDn == null) {
throw new IllegalStateException("Roles DN is null! Check your configuration");
throw new ModelException("Roles DN is null! Check your configuration");
}
return rolesDn;
}
@@ -172,10 +169,11 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
return membershipAttrName!=null ? membershipAttrName : LDAPConstants.MEMBER;
}
protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapperModel) {
protected Collection<String> getRoleObjectClasses(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
String objectClasses = mapperModel.getConfig().get(ROLE_OBJECT_CLASSES);
if (objectClasses == null) {
objectClasses = "groupOfNames";
// For Active directory, the default is 'group' . For other servers 'groupOfNames'
objectClasses = ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES;
}
String[] objClasses = objectClasses.split(",");
@@ -191,18 +189,18 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
private Mode getMode(UserFederationMapperModel mapperModel) {
String modeString = mapperModel.getConfig().get(MODE);
if (modeString == null || modeString.trim().length() == 0) {
return Mode.LDAP_ONLY;
if (modeString == null || modeString.isEmpty()) {
throw new ModelException("Mode is missing! Check your configuration");
}
return Enum.valueOf(Mode.class, modeString.toUpperCase());
}
protected LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
public LDAPObject createLDAPRole(UserFederationMapperModel mapperModel, String roleName, LDAPFederationProvider ldapProvider) {
LDAPObject ldapObject = new LDAPObject();
String roleNameAttribute = getRoleNameLdapAttribute(mapperModel);
ldapObject.setRdnAttributeName(roleNameAttribute);
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel));
ldapObject.setObjectClasses(getRoleObjectClasses(mapperModel, ldapProvider));
ldapObject.setAttribute(roleNameAttribute, roleName);
LDAPDn roleDn = LDAPDn.fromString(getRolesDn(mapperModel));
@@ -231,8 +229,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
Set<String> memberships = getExistingMemberships(mapperModel, ldapRole);
memberships.remove(ldapUser.getDn().toString());
// Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers
if (memberships.size() == 0) {
// Some membership placeholder needs to be always here as "member" is mandatory attribute on some LDAP servers. But on active directory! (Empty membership is not allowed here)
if (memberships.size() == 0 && !ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
memberships.add(LDAPConstants.EMPTY_MEMBER_ATTRIBUTE_VALUE);
}
@@ -278,23 +276,6 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
return ldapQuery.getResultList();
}
protected Set<RoleModel> getLDAPRoleMappingsConverted(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, RoleContainerModel roleContainer) {
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
Set<RoleModel> roles = new HashSet<RoleModel>();
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
for (LDAPObject role : ldapRoles) {
String roleName = role.getAttributeAsString(roleNameLdapAttr);
RoleModel modelRole = roleContainer.getRole(roleName);
if (modelRole == null) {
// Add role to local DB
modelRole = roleContainer.addRole(roleName);
}
roles.add(modelRole);
}
return roles;
}
@Override
public UserModel proxy(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel delegate, RealmModel realm) {
final Mode mode = getMode(mapperModel);
@@ -321,6 +302,9 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
private final RealmModel realm;
private final Mode mode;
// Avoid loading role mappings from LDAP more times per-request
private Set<RoleModel> cachedLDAPRoleMappings;
public LDAPRoleMappingsUserDelegate(UserModel user, UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser,
RealmModel realm, Mode mode) {
super(user);
@@ -385,6 +369,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
if (role.getContainer().equals(roleContainer)) {
// We need to create new role mappings in LDAP
cachedLDAPRoleMappings = null;
addRoleMappingInLDAP(mapperModel, role.getName(), ldapProvider, ldapUser);
} else {
super.grantRole(role);
@@ -415,6 +400,30 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
return modelRoleMappings;
}
protected Set<RoleModel> getLDAPRoleMappingsConverted(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, RoleContainerModel roleContainer) {
if (cachedLDAPRoleMappings != null) {
return new HashSet<>(cachedLDAPRoleMappings);
}
List<LDAPObject> ldapRoles = getLDAPRoleMappings(mapperModel, ldapProvider, ldapUser);
Set<RoleModel> roles = new HashSet<RoleModel>();
String roleNameLdapAttr = getRoleNameLdapAttribute(mapperModel);
for (LDAPObject role : ldapRoles) {
String roleName = role.getAttributeAsString(roleNameLdapAttr);
RoleModel modelRole = roleContainer.getRole(roleName);
if (modelRole == null) {
// Add role to local DB
modelRole = roleContainer.addRole(roleName);
}
roles.add(modelRole);
}
cachedLDAPRoleMappings = new HashSet<>(roles);
return roles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
@@ -438,6 +447,7 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
throw new ModelException("Not possible to delete LDAP role mappings as mapper mode is READ_ONLY");
} else {
// Delete ldap role mappings
cachedLDAPRoleMappings = null;
deleteRoleMappingInLDAP(mapperModel, ldapProvider, ldapUser, ldapRole);
}
}
@@ -1,25 +1,33 @@
package org.keycloak.federation.ldap.mappers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
private static final Logger logger = Logger.getLogger(RoleLDAPFederationMapperFactory.class);
public static final String PROVIDER_ID = "role-ldap-mapper";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@@ -40,8 +48,8 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
configProperties.add(membershipLDAPAttribute);
ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleLDAPFederationMapper.ROLE_OBJECT_CLASSES, "Role Object Classes",
"Object classes of the role object divided by comma (if more values needed). In typical LDAP deployment it could be 'groupOfNames' or 'groupOfEntries' ",
ProviderConfigProperty.STRING_TYPE, LDAPConstants.GROUP_OF_NAMES);
"Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ",
ProviderConfigProperty.STRING_TYPE, null);
configProperties.add(roleObjectClasses);
List<String> modes = new LinkedList<String>();
@@ -90,9 +98,46 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
return PROVIDER_ID;
}
// Sync roles from LDAP to Keycloak DB during creation or update of mapperModel
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(new ProviderEventListener() {
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RealmModel.UserFederationMapperEvent) {
RealmModel.UserFederationMapperEvent mapperEvent = (RealmModel.UserFederationMapperEvent)event;
UserFederationMapperModel mapperModel = mapperEvent.getFederationMapper();
RealmModel realm = mapperEvent.getRealm();
KeycloakSession session = mapperEvent.getSession();
if (mapperModel.getFederationMapperType().equals(PROVIDER_ID)) {
try {
String federationProviderId = mapperModel.getFederationProviderId();
UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderById(federationProviderId, realm);
if (providerModel == null) {
throw new IllegalStateException("Can't find federation provider with ID [" + federationProviderId + "] in realm " + realm.getName());
}
UserFederationProviderFactory ldapFactory = (UserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, providerModel.getProviderName());
LDAPFederationProvider ldapProvider = (LDAPFederationProvider) ldapFactory.getInstance(session, providerModel);
// Sync roles
new RoleLDAPFederationMapper().syncRolesFromLDAP(mapperModel, ldapProvider, realm);
} catch (Exception e) {
logger.warn("Exception during initial sync of roles from LDAP.", e);
}
}
}
}
});
}
@Override
public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
checkMandatoryConfigAttribute(RoleLDAPFederationMapper.ROLES_DN, "LDAP Roles DN", mapperModel);
checkMandatoryConfigAttribute(RoleLDAPFederationMapper.MODE, "Mode", mapperModel);
String realmMappings = mapperModel.getConfig().get(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING);
boolean useRealmMappings = Boolean.parseBoolean(realmMappings);
@@ -1,18 +1,13 @@
package org.keycloak.federation.ldap.mappers;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPIdentityQuery;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
@@ -20,7 +15,6 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.reflection.Property;
import org.keycloak.models.utils.reflection.PropertyCriteria;
import org.keycloak.models.utils.reflection.PropertyQueries;
import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -124,7 +118,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
}
protected void setLDAPAttribute(String modelAttrName, String value) {
if (modelAttrName.equals(userModelAttrName)) {
if (modelAttrName.equalsIgnoreCase(userModelAttrName)) {
if (logger.isTraceEnabled()) {
logger.tracef("Pushing user attribute to LDAP. Model attribute name: %s, LDAP attribute name: %s, Attribute value: %s", modelAttrName, ldapAttrName, value);
}
@@ -157,7 +151,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap
// Change conditions and use ldapAttribute instead of userModel
for (Condition condition : query.getConditions()) {
QueryParameter param = condition.getParameter();
if (param != null && param.getName().equals(userModelAttrName)) {
if (param != null && param.getName().equalsIgnoreCase(userModelAttrName)) {
param.setName(ldapAttrName);
}
}