/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2013 OpenPlans
* 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.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import javax.naming.directory.DirContext;
import org.geoserver.security.GeoServerRoleService;
import org.geoserver.security.GeoServerRoleStore;
import org.geoserver.security.config.SecurityNamedServiceConfig;
import org.geoserver.security.event.RoleLoadedListener;
import org.geoserver.security.impl.GeoServerRole;
import org.geotools.util.logging.Logging;
import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapEntryIdentification;
/**
* LDAP implementation of {@link GeoServerRoleService}
*
* @author "Mauro Bartolomeoli - mauro.bartolomeoli@geo-solutions.it"
*
*/
public class LDAPRoleService extends LDAPBaseSecurityService implements GeoServerRoleService {
private static final SortedSet<String> emptyStringSet = Collections
.unmodifiableSortedSet(new TreeSet<String>());
private static final Map<String, String> emptyMap = Collections.emptyMap();
static Logger LOGGER = Logging.getLogger("org.geoserver.security.ldap");
protected Set<RoleLoadedListener> listeners =
Collections.synchronizedSet(new HashSet<RoleLoadedListener>());
private String rolePrefix = "ROLE_";
private boolean convertToUpperCase = true;
private String adminGroup;
private String groupAdminGroup;
@Override
public void initializeFromConfig(SecurityNamedServiceConfig config)
throws IOException {
super.initializeFromConfig(config);
LDAPRoleServiceConfig ldapConfig = (LDAPRoleServiceConfig) config;
if (!isEmpty(ldapConfig.getAdminGroup())) {
this.adminGroup = ldapConfig.getAdminGroup();
}
if (!isEmpty(ldapConfig.getGroupAdminGroup())) {
this.groupAdminGroup = ldapConfig.getGroupAdminGroup();
}
}
/**
* Read only store.
*/
@Override
public boolean canCreateStore() {
return false;
}
/**
* Read only store.
*/
@Override
public GeoServerRoleStore createStore() throws IOException {
return null;
}
/**
* @see org.geoserver.security.GeoServerRoleService#registerRoleLoadedListener(RoleLoadedListener)
*/
public void registerRoleLoadedListener(RoleLoadedListener listener) {
listeners.add(listener);
}
/**
* @see org.geoserver.security.GeoServerRoleService#unregisterRoleLoadedListener(RoleLoadedListener)
*/
public void unregisterRoleLoadedListener(RoleLoadedListener listener) {
listeners.remove(listener);
}
/**
* Roles to group association is not supported
*/
@Override
public SortedSet<String> getGroupNamesForRole(GeoServerRole role)
throws IOException {
return emptyStringSet;
}
@Override
public SortedSet<String> getUserNamesForRole(final GeoServerRole role)
throws IOException {
final SortedSet<String> users = new TreeSet<String>();
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {
@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
fillUsersForRole(ctx, users, role);
}
});
return Collections.unmodifiableSortedSet(users);
}
@Override
public SortedSet<GeoServerRole> getRolesForUser(final String username)
throws IOException {
final SortedSet<GeoServerRole> roles = new TreeSet<GeoServerRole>();
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {
@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
fillRolesForUser(ctx, username, lookupDn(username), roles);
}
});
return Collections.unmodifiableSortedSet(roles);
}
/**
* Assume role name = group name
*/
@Override
public SortedSet<GeoServerRole> getRolesForGroup(String groupname)
throws IOException {
SortedSet<GeoServerRole> set = new TreeSet<GeoServerRole>();
GeoServerRole role = getRoleByName(groupname);
if (role != null) {
set.add(role);
}
return Collections.unmodifiableSortedSet(set);
}
@Override
public SortedSet<GeoServerRole> getRoles() throws IOException {
final SortedSet<GeoServerRole> roles = new TreeSet<GeoServerRole>();
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {
@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
fillAllRoles(ctx, roles);
}
});
return Collections.unmodifiableSortedSet(roles);
}
private void fillAllRoles(DirContext ctx, SortedSet<GeoServerRole> roles) {
Set<String> roleNames = LDAPUtils.getLdapTemplateInContext(ctx, template)
.searchForSingleAttributeValues(groupSearchBase,
allGroupsSearchFilter, new String[] {}, groupNameAttribute);
addRolesToSet(roles, roleNames);
}
private void fillUsersForRole(DirContext ctx, SortedSet<String> users,
GeoServerRole role) {
String roleStr = role.toString();
if (roleStr.startsWith("ROLE_")) {
// remove standard role prefix
roleStr = roleStr.substring(5);
}
DirContextOperations roleObj = LDAPUtils.getLdapTemplateInContext(ctx,
template).searchForSingleEntry(groupSearchBase, groupNameFilter,
new String[] { roleStr });
if (roleObj != null) {
Object[] usernames = roleObj
.getObjectAttributes(groupMembershipAttribute);
if (usernames != null) {
for (Object username : usernames) {
String user = username.toString();
Matcher m = userMembershipPattern.matcher(user);
if (m.matches()) {
user = m.group(1);
}
users.add(getUserNameFromMembership(user));
}
}
}
}
private void addRolesToSet(SortedSet<GeoServerRole> roles,
Set<String> roleNames) {
for (String roleName : roleNames) {
try {
roles.add(createRoleObject(roleName));
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error adding a new role from LDAP", e);
}
}
}
private void fillRolesForUser(DirContext ctx, String username, String userDn,
SortedSet<GeoServerRole> roles) {
Set<String> roleNames = LDAPUtils.getLdapTemplateInContext(ctx, template)
.searchForSingleAttributeValues(groupSearchBase, groupMembershipFilter,
new String[] { username, userDn }, groupNameAttribute);
addRolesToSet(roles, roleNames);
}
@Override
public Map<String, String> getParentMappings() throws IOException {
return emptyMap;
}
@Override
public GeoServerRole createRoleObject(String role) throws IOException {
return new GeoServerRole(rolePrefix
+ (convertToUpperCase ? role.toUpperCase() : role));
}
@Override
public GeoServerRole getParentRole(GeoServerRole role) throws IOException {
return null;
}
@Override
public GeoServerRole getRoleByName(String role) throws IOException {
if (role.startsWith("ROLE_")) {
// remove standard role prefix
role = role.substring(5);
}
final String roleName = role;
final SortedSet<String> roles = new TreeSet<String>();
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {
@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
roles.addAll(LDAPUtils.getLdapTemplateInContext(ctx, template)
.searchForSingleAttributeValues(groupSearchBase,
groupNameFilter, new String[] { roleName },
groupNameAttribute));
}
});
if (roles.size() == 1) {
return createRoleObject(role);
}
return null;
}
@Override
public void load() throws IOException {
}
@Override
public Properties personalizeRoleParams(String roleName,
Properties roleParams, String userName, Properties userProps)
throws IOException {
return null;
}
@Override
public GeoServerRole getAdminRole() {
if(adminGroup == null) {
return null;
}
try {
return getRoleByName(adminGroup);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public GeoServerRole getGroupAdminRole() {
if(groupAdminGroup == null) {
return null;
}
try {
return getRoleByName(groupAdminGroup);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public int getRoleCount() throws IOException {
AtomicInteger count = new AtomicInteger(0);
authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() {
@Override
public void executeWithContext(DirContext ctx,
LdapEntryIdentification ldapEntryIdentification) {
LDAPUtils.getLdapTemplateInContext(ctx, template).search(
groupSearchBase, allGroupsSearchFilter, counter(count));
}
});
return count.get();
}
}