/* (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; };
}
}