Skip to content

Commit

Permalink
LDAP: UserGroupService + refactoring to share code with RoleService
Browse files Browse the repository at this point in the history
  • Loading branch information
NielsCharlier committed May 6, 2016
1 parent 78545ef commit b635332
Show file tree
Hide file tree
Showing 18 changed files with 1,619 additions and 224 deletions.
45 changes: 45 additions & 0 deletions doc/en/user/source/security/usergrouprole/usergroupservices.rst
Expand Up @@ -176,3 +176,48 @@ The default GeoServer security configuration is:

For further information, please refer to :ref:`configuring a user/group service <security_webadmin_usergroupservices>` in the :ref:`web_admin`.

.. _security_rolesystem_usergroupldap:

LDAP user/group service
------------------------

The LDAP user/group service is a read only user/group service that maps users and groups from an LDAP repository to GeoServer users and groups.

Users are extracted from a specific LDAP node, configured as the ``Users search base``. Groups are extracted from a specific LDAP node, configured as the ``Groups search base``. A user is mapped for every matching user and a group is mapped for every matching group.

It is possible to specify the attributes which contain the name of the group (such as ``cn``), the user (such as ``uid``) as well as the membership relationship between the two (such as ``member``). However, it is also possible to specify specific filters to search for all users/groups (for example ``cn=*``), find a user/group by name (for example ``cn={0}``) and map users to groups (such as ``member={0}``). These filters can also be automatically derived from the attribute names. Alternatively, the attribute names may be left empty if the filters are provided.

For users, additional properties (key/value pairs, see :ref:`security_rolesystem_usergroups`) may be populated from the LDAP Server by providing a comma separated list of property names.

Retrieving the user/group information can be done anonymously or using a given username/password if the LDAP repository requires it.

An example of configuration file (config.xml) for this type of role service is the following:

.. code-block:: xml
<org.geoserver.security.ldap.LDAPUserGroupServiceConfig>
<id>2c3e0e8d:154853796a3:-8000</id>
<name>myldapservice</name>
<className>org.geoserver.security.ldap.LDAPUserGroupService</className>
<serverURL>ldap://127.0.0.1:10389/dc=acme,dc=org</serverURL>
<groupSearchBase>ou=groups</groupSearchBase>
<groupFilter>cn={0}</groupFilter>
<groupNameAttribute>cn</groupNameAttribute>
<allGroupsSearchFilter>cn=*</allGroupsSearchFilter>
<groupSearchFilter>member={0}</groupSearchFilter>
<groupMembershipAttribute>member</groupMembershipAttribute>
<userSearchBase>ou=people</userSearchBase>
<userFilter>uid</userFilter>
<userNameAttribute>uid={0}</userNameAttribute>
<allUsersSearchFilter>uid=*</allUsersSearchFilter>
<useTLS>false</useTLS>
<bindBeforeGroupSearch>true</bindBeforeGroupSearch>
<user>admin</user>
<password>admin</password>
<passwordEncoderName>emptyPasswordEncoder</passwordEncoderName>
<passwordPolicyName>default</passwordPolicyName>
<populatedAttributes>email, telephone</populatedAttributes>
</org.geoserver.security.ldap.LDAPUserGroupServiceConfig>

For further information, please refer to :ref:`configuring a user/group service <security_webadmin_usergroupservices>` in the :ref:`web_admin`.

@@ -0,0 +1,286 @@
/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.ldap;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.directory.DirContext;

import org.geoserver.security.config.SecurityNamedServiceConfig;
import org.geoserver.security.impl.AbstractGeoServerSecurityService;
import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.ldap.core.LdapEntryIdentification;

/**
*
* @author Niels Charlier
*
*/
public abstract class LDAPBaseSecurityService extends AbstractGeoServerSecurityService {

/**
* regex to find membership attribute in expression
*/
protected static final Pattern lookForMembershipAttribute = Pattern.compile(
"^\\(*([a-z]+)=(.*?)\\{([01])\\}(.*?)\\)*$", Pattern.CASE_INSENSITIVE);

/**
* regex to extract the username from the user info
*/
protected Pattern userNamePattern = Pattern.compile("^(.*)$");

/**
* regex to extract username from membership info
*/
protected Pattern userMembershipPattern = Pattern.compile("^(.*)$");

/**
* LDAP context
*/
protected LdapContextSource ldapContext;

/**
* LDAP template
*/
protected SpringSecurityLdapTemplate template;

/**
* User (if authenticating)
*/
protected String user;

/**
* Pasdword (if authenticating)
*/
protected String password;

/**
* Search base for ldap groups that are to be mapped to GeoServer groups/roles
*/
protected String groupSearchBase = "ou=groups";

/**
* Standard filter for getting all roles bounded to a user
*/
protected String groupNameFilter = "cn={0}";

/**
* Standard filter for getting all roles
*/
protected String allGroupsSearchFilter = "cn=*";

/**
* The ID of the attribute which contains the role name for a group
*/
protected String groupNameAttribute = "cn";

/**
* Standard filter for getting all roles bounded to a user
*/
protected String groupMembershipFilter = "member={0}";

/**
* attribute of a group containing the membership info
*/
protected String groupMembershipAttribute = "member";

/**
* Search base for ldap users that are to be mapped to GeoServer roles
*/
protected String userSearchBase = "ou=people";

/**
* Standard filter for getting all groups bounded to a user
*/
protected String userNameFilter = "uid={0}";

/**
* Standard filter for getting all groups bounded to a user
*/
protected String allUsersSearchFilter = "uid=*";

/**
* attribute of a user containing the username (used if userFilter is defined)
*/
protected String userNameAttribute = "uid";


/**
* lookup user for dn
*/
protected boolean lookupUserForDn = false;



@Override
public void initializeFromConfig(SecurityNamedServiceConfig config)
throws IOException {
super.initializeFromConfig(config);
LDAPBaseSecurityServiceConfig ldapConfig = (LDAPBaseSecurityServiceConfig) config;

ldapContext = LDAPUtils.createLdapContext(ldapConfig);

if (ldapConfig.isBindBeforeGroupSearch()) {
// authenticate before LDAP searches
user = ldapConfig.getUser();
password = ldapConfig.getPassword();
template = new BindingLdapTemplate(ldapContext);
} else {
template = new SpringSecurityLdapTemplate(ldapContext);
}

if (!isEmpty(ldapConfig.getGroupSearchBase())) {
groupSearchBase = ldapConfig.getGroupSearchBase();
}
if (!isEmpty(ldapConfig.getUserSearchBase())) {
userSearchBase = ldapConfig.getUserSearchBase();
}

if (!isEmpty(ldapConfig.getGroupSearchFilter())) {
groupMembershipFilter = ldapConfig.getGroupSearchFilter();
Matcher m = lookForMembershipAttribute.matcher(groupMembershipFilter);
if (m.matches()) {
if (isEmpty(ldapConfig.getGroupMembershipAttribute())) {
groupMembershipAttribute = m.group(1);
}
lookupUserForDn = m.group(3).equals("1");
userMembershipPattern = Pattern.compile("^"
+ Pattern.quote(m.group(2)) + "(.*)"
+ Pattern.quote(m.group(4)) + "$");
}
}
if (!isEmpty(ldapConfig.getGroupMembershipAttribute())) {
groupMembershipAttribute = ldapConfig.getGroupMembershipAttribute();
if (isEmpty(ldapConfig.getGroupSearchFilter())) {
groupMembershipFilter = groupMembershipAttribute + "={0}";
}
}

if (!isEmpty(ldapConfig.getGroupFilter())) {
groupNameFilter = ldapConfig.getGroupFilter();
if (isEmpty(ldapConfig.getGroupNameAttribute())) {
Matcher m = lookForMembershipAttribute.matcher(groupNameFilter);
if (m.matches()) {
groupNameAttribute = m.group(1);
}
}
}
if (!isEmpty(ldapConfig.getGroupNameAttribute())) {
groupNameAttribute = ldapConfig.getGroupNameAttribute();
if (isEmpty(ldapConfig.getGroupFilter())) {
groupNameFilter = groupNameAttribute + "={0}";
}
}
if (!isEmpty(ldapConfig.getAllGroupsSearchFilter())) {
allGroupsSearchFilter = ldapConfig.getAllGroupsSearchFilter();
} else {
allGroupsSearchFilter = groupNameAttribute + "=*";
}

if (!isEmpty(ldapConfig.getUserFilter())) {
this.userNameFilter = ldapConfig.getUserFilter();
Matcher m = lookForMembershipAttribute.matcher(userNameFilter);
if (m.matches()) {
if (isEmpty(ldapConfig.getUserNameAttribute())) {
userNameAttribute = m.group(1);
}
userNamePattern = Pattern.compile("^"
+ Pattern.quote(m.group(2)) + "(.*)"
+ Pattern.quote(m.group(4)) + "$");
}
}
if (!isEmpty(ldapConfig.getUserNameAttribute())) {
userNameAttribute = ldapConfig.getUserNameAttribute();
if (isEmpty(ldapConfig.getUserFilter())) {
userNameFilter = userNameAttribute + "={0}";
}
}
if (!isEmpty(ldapConfig.getAllUsersSearchFilter())) {
allUsersSearchFilter = ldapConfig.getAllUsersSearchFilter();
} else {
allUsersSearchFilter = userNameAttribute + "=*";
}
}

/**
* Execute authentication, if configured to do so, and then
* call the given callback on authenticated context, or simply
* call the given callback if no authentication is needed.
*
* @param callback
*/
protected void authenticateIfNeeded(AuthenticatedLdapEntryContextCallback callback) {
if (user != null && password != null) {
template.authenticate(DistinguishedName.EMPTY_PATH, user, password,
callback);
} else {
callback.executeWithContext(null, null);
}

}

protected static boolean isEmpty(String property) {
return property == null || property.isEmpty();
}

protected String getUserNameFromMembership(final String user) {
final AtomicReference<String> userName = new AtomicReference<String>(user);

if (lookupUserForDn) {
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {

@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
DirContextOperations obj = (DirContextOperations)LDAPUtils
.getLdapTemplateInContext(ctx, template)
.lookup(user);
String name = obj.getObjectAttribute(userNameAttribute).toString();
Matcher m = userNamePattern.matcher(name);
if(m.matches()) {
name = m.group(1);
}
userName.set(name);
}
});
}
return userName.get();
}

protected String lookupDn(String username) {
final AtomicReference<String> dn = new AtomicReference<String>(username);
if (lookupUserForDn) {
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {

@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
try {
dn.set(LDAPUtils.getLdapTemplateInContext(ctx, template)
.searchForSingleEntry("", userNameFilter, new String[] { username }).getDn().toString());
} catch (Exception e) {
// not found, let's use username instead
}
}
});
}

return dn.get();
}

protected ContextMapper counter(AtomicInteger count) {
return ctx -> { count.set(count.get() + 1); return null; };
}

}

0 comments on commit b635332

Please sign in to comment.