/* (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.Collections; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.DirContext; import org.geoserver.security.GeoServerUserGroupService; import org.geoserver.security.GeoServerUserGroupStore; import org.geoserver.security.config.SecurityNamedServiceConfig; import org.geoserver.security.event.UserGroupLoadedListener; import org.geoserver.security.impl.GeoServerUser; import org.geoserver.security.impl.GeoServerUserGroup; import org.geoserver.security.impl.RoleCalculator; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.ldap.core.AuthenticatedLdapEntryContextCallback; import org.springframework.ldap.core.ContextMapper; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.LdapEntryIdentification; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * LDAP implementation of {@link GeoServerUserGroupService} * * @author Niels Charlier * */ public class LDAPUserGroupService extends LDAPBaseSecurityService implements GeoServerUserGroupService { private static final Logger LOGGER = org.geotools.util.logging.Logging .getLogger("org.geoserver.security.ldap"); private String passwordEncoderName; private String passwordValidatorName; private String[] populatedAttributes = new String[] {}; public LDAPUserGroupService(SecurityNamedServiceConfig config) throws IOException { initializeFromConfig(config); } @Override public void initializeFromConfig(SecurityNamedServiceConfig config) throws IOException { super.initializeFromConfig(config); LDAPUserGroupServiceConfig ldapConfig = ((LDAPUserGroupServiceConfig) config); passwordEncoderName = ldapConfig.getPasswordEncoderName(); passwordValidatorName = ldapConfig.getPasswordPolicyName(); if (!isEmpty(ldapConfig.getPopulatedAttributes())) { populatedAttributes = ldapConfig.getPopulatedAttributes().trim().split("[\\s]*,[\\s]*"); } } @Override public GeoServerUserGroupStore createStore() throws IOException { return null; //read-only! } @Override public void load() throws IOException { //do nothing } @Override public void registerUserGroupLoadedListener(UserGroupLoadedListener listener) { //ignore, there are no events } @Override public void unregisterUserGroupLoadedListener(UserGroupLoadedListener listener) { //ignore, there are no events } @Override public String getPasswordEncoderName() { return passwordEncoderName; } @Override public String getPasswordValidatorName() { return passwordValidatorName; } //---------------------------------------------------------------------------------- @Override public GeoServerUser createUserObject(String username, String password, boolean isEnabled) throws IOException { GeoServerUser user = new GeoServerUser(username); user.setEnabled(isEnabled); user.setPassword(password); return user; } @Override public GeoServerUserGroup createGroupObject(String groupname, boolean isEnabled) throws IOException { GeoServerUserGroup group = new GeoServerUserGroup(groupname); group.setEnabled(isEnabled); return group; } @Override public SortedSet<GeoServerUserGroup> getUserGroups() { final SortedSet<GeoServerUserGroup> groups = new TreeSet<GeoServerUserGroup>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { Set<String> groupNames = LDAPUtils.getLdapTemplateInContext(ctx, template) .searchForSingleAttributeValues(groupSearchBase, allGroupsSearchFilter, new String[] {}, groupNameAttribute); for (String groupName : groupNames) { groups.add(new GeoServerUserGroup(groupName)); } } }); return Collections.unmodifiableSortedSet(groups); } protected GeoServerUser createUser(DirContextOperations dco) { GeoServerUser gsUser = new GeoServerUser(dco.getStringAttribute(userNameAttribute)); for (String attName : populatedAttributes) { try { Attribute att = dco.getAttributes().get(attName.toLowerCase()); if (att != null) { Object value = att.get(); if (value instanceof String) { gsUser.getProperties().put(attName, value); } } } catch (NamingException e) { LOGGER.log(Level.WARNING, "Could not populate value for user attribute " + attName, e); } } return gsUser; } protected ContextMapper addToUsers(SortedSet<GeoServerUser> users) { return ctx -> { users.add(createUser((DirContextAdapter) ctx)); return null; }; } @Override public SortedSet<GeoServerUser> getUsers() { final SortedSet<GeoServerUser> users = new TreeSet<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, allUsersSearchFilter, addToUsers(users)); } }); return Collections.unmodifiableSortedSet(users); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { GeoServerUser user=null; try { user = getUserByUsername(username); if (user == null) { throw new UsernameNotFoundException(userNotFoundMessage(username)); } RoleCalculator calculator = new RoleCalculator(this, getSecurityManager().getActiveRoleService()); user.setAuthorities(calculator.calculateRoles(user)); } catch (IOException e) { throw new UsernameNotFoundException(userNotFoundMessage(username), e); } return user; } protected String userNotFoundMessage(String username) { return "User " + username + " not found in usergroupservice: " + getName(); } @Override public GeoServerUserGroup getGroupByGroupname(String groupname) { final AtomicReference<GeoServerUserGroup> group = new AtomicReference<GeoServerUserGroup>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { try { DirContextOperations dco = LDAPUtils.getLdapTemplateInContext(ctx, template).searchForSingleEntry( groupSearchBase, groupNameFilter, new String[] { groupname }); if (dco != null) { group.set(new GeoServerUserGroup(dco.getStringAttribute(groupNameAttribute))); } } catch (IncorrectResultSizeDataAccessException e) {} } }); return group.get(); } @Override public GeoServerUser getUserByUsername(String username) { final AtomicReference<GeoServerUser> user = new AtomicReference<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { try{ DirContextOperations dco = LDAPUtils.getLdapTemplateInContext(ctx, template).searchForSingleEntry( userSearchBase, userNameFilter, new String[] { username }); if (dco != null) { user.set(createUser(dco)); } } catch (IncorrectResultSizeDataAccessException e) {} } }); return user.get(); } @Override public SortedSet<GeoServerUser> getUsersForGroup(final GeoServerUserGroup group) { final SortedSet<GeoServerUser> users = new TreeSet<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { try { DirContextOperations roleObj = LDAPUtils.getLdapTemplateInContext(ctx, template).searchForSingleEntry(groupSearchBase, groupNameFilter, new String[] { group.getGroupname() }); 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(getUserByUsername(getUserNameFromMembership(user))); } } } } catch (IncorrectResultSizeDataAccessException e) {} } }); return Collections.unmodifiableSortedSet(users); } @Override public SortedSet<GeoServerUserGroup> getGroupsForUser(final GeoServerUser user) { final SortedSet<GeoServerUserGroup> groups = new TreeSet<GeoServerUserGroup>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { Set<String> groupNames = LDAPUtils.getLdapTemplateInContext(ctx, template) .searchForSingleAttributeValues(groupSearchBase, groupMembershipFilter, new String[] { user.getUsername(), lookupDn(user.getUsername()) }, groupNameAttribute); for (String groupName : groupNames) { groups.add(new GeoServerUserGroup(groupName)); } } }); return Collections.unmodifiableSortedSet(groups); } @Override public int getUserCount() { AtomicInteger size = new AtomicInteger(0); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, allUsersSearchFilter, counter(size)); } }); return size.get(); } @Override public int getGroupCount() { AtomicInteger size = new AtomicInteger(0); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( groupSearchBase, allGroupsSearchFilter, counter(size)); } }); return size.get(); } @Override public SortedSet<GeoServerUser> getUsersHavingProperty(String propname) { final SortedSet<GeoServerUser> users = new TreeSet<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, propname + "=*", addToUsers(users)); } }); return users; } @Override public int getUserCountHavingProperty(String propname) { AtomicInteger size = new AtomicInteger(0); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, propname + "=*", counter(size)); } }); return size.get(); } @Override public SortedSet<GeoServerUser> getUsersNotHavingProperty(String propname) { final SortedSet<GeoServerUser> users = new TreeSet<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, "(&(!(" + propname + "=*))(" + allUsersSearchFilter + "))", addToUsers(users)); } }); return users; } @Override public int getUserCountNotHavingProperty(String propname) { AtomicInteger size = new AtomicInteger(0); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, "(&(!(" + propname + "=*))(" + allUsersSearchFilter + "))", counter(size)); } }); return size.get(); } @Override public SortedSet<GeoServerUser> getUsersHavingPropertyValue(String propname, String propvalue) throws IOException { final SortedSet<GeoServerUser> users = new TreeSet<GeoServerUser>(); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, propname + "=" + propvalue, addToUsers(users)); } }); return users; } @Override public int getUserCountHavingPropertyValue(String propname, String propvalue) throws IOException { AtomicInteger size = new AtomicInteger(0); authenticateIfNeeded(new AuthenticatedLdapEntryContextCallback() { @Override public void executeWithContext(DirContext ctx, LdapEntryIdentification ldapEntryIdentification) { LDAPUtils.getLdapTemplateInContext(ctx, template).search( userSearchBase, propname + "=" + propvalue, counter(size)); } }); return size.get(); } }