Repository: guacamole-client Updated Branches: refs/heads/master ecca7bc50 -> 756ec2fcc
GUACAMOLE-220: Retrieve user groups from LDAP. Take immediate group membership into account. Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/aa0c6542 Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/aa0c6542 Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/aa0c6542 Branch: refs/heads/master Commit: aa0c65423146929a46ceeb1beb7573815c0e4513 Parents: bdc7926 Author: Michael Jumper <mjum...@apache.org> Authored: Sat Nov 3 12:34:04 2018 -0700 Committer: Michael Jumper <mjum...@apache.org> Committed: Sat Nov 3 12:41:54 2018 -0700 ---------------------------------------------------------------------- .../ldap/AuthenticationProviderService.java | 15 +- .../ldap/LDAPAuthenticationProviderModule.java | 2 + .../auth/ldap/connection/ConnectionService.java | 52 ++--- .../auth/ldap/group/UserGroupService.java | 224 +++++++++++++++++++ .../auth/ldap/user/AuthenticatedUser.java | 22 +- .../guacamole/auth/ldap/user/UserContext.java | 29 ++- 6 files changed, 300 insertions(+), 44 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index a25c697..4a746f1 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -23,9 +23,11 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.novell.ldap.LDAPConnection; import java.util.List; +import java.util.Set; import org.apache.guacamole.auth.ldap.user.AuthenticatedUser; import org.apache.guacamole.auth.ldap.user.UserContext; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.auth.ldap.user.UserService; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; @@ -63,6 +65,12 @@ public class AuthenticationProviderService { private UserService userService; /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + + /** * Provider for AuthenticatedUser objects. */ @Inject @@ -222,9 +230,14 @@ public class AuthenticationProviderService { try { + // Retrieve group membership of the user that just authenticated + Set<String> effectiveGroups = + userGroupService.getParentUserGroupIdentifiers(ldapConnection, + ldapConnection.getAuthenticationDN()); + // Return AuthenticatedUser if bind succeeds AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(credentials); + authenticatedUser.init(credentials, effectiveGroups); return authenticatedUser; } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java index 5478080..23decec 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java @@ -23,6 +23,7 @@ import com.google.inject.AbstractModule; import org.apache.guacamole.auth.ldap.connection.ConnectionService; import org.apache.guacamole.auth.ldap.user.UserService; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -78,6 +79,7 @@ public class LDAPAuthenticationProviderModule extends AbstractModule { bind(EscapingService.class); bind(LDAPConnectionService.class); bind(ObjectQueryService.class); + bind(UserGroupService.class); bind(UserService.class); } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java index 78100a0..bae1da8 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java @@ -24,8 +24,6 @@ import com.novell.ldap.LDAPAttribute; import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPEntry; import com.novell.ldap.LDAPException; -import com.novell.ldap.LDAPReferralException; -import com.novell.ldap.LDAPSearchResults; import java.util.Collections; import java.util.Enumeration; import java.util.List; @@ -36,6 +34,7 @@ import org.apache.guacamole.auth.ldap.EscapingService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.ldap.ObjectQueryService; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.simple.SimpleConnection; @@ -75,6 +74,12 @@ public class ConnectionService { private ObjectQueryService queryService; /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + + /** * Returns all Guacamole connections accessible to the user currently bound * under the given LDAP connection. * @@ -226,43 +231,12 @@ public class ConnectionService { connectionSearchFilter.append(escapingService.escapeLDAPSearchFilter(userDN)); connectionSearchFilter.append(")"); - // If group base DN is specified search for user groups - String groupBaseDN = confService.getGroupBaseDN(); - if (groupBaseDN != null) { - - // Get all groups the user is a member of starting at the groupBaseDN, excluding guacConfigGroups - LDAPSearchResults userRoleGroupResults = ldapConnection.search( - groupBaseDN, - LDAPConnection.SCOPE_SUB, - "(&(!(objectClass=guacConfigGroup))(member=" + escapingService.escapeLDAPSearchFilter(userDN) + "))", - null, - false, - confService.getLDAPSearchConstraints() - ); - - // Append the additional user groups to the LDAP filter - // Now the filter will also look for guacConfigGroups that refer - // to groups the user is a member of - // The guacConfig group uses the seeAlso attribute to refer - // to these other groups - while (userRoleGroupResults.hasMore()) { - try { - LDAPEntry entry = userRoleGroupResults.next(); - connectionSearchFilter.append("(seeAlso=").append(escapingService.escapeLDAPSearchFilter(entry.getDN())).append(")"); - } - - catch (LDAPReferralException e) { - if (confService.getFollowReferrals()) { - logger.error("Could not follow referral: {}", e.getFailedReferral()); - logger.debug("Error encountered trying to follow referral.", e); - throw new GuacamoleServerException("Could not follow LDAP referral.", e); - } - else { - logger.warn("Given a referral, but referrals are disabled. Error was: {}", e.getMessage()); - logger.debug("Got a referral, but configured to not follow them.", e); - } - } - } + // Additionally filter by group membership if the current user is a + // member of any user groups + List<LDAPEntry> userGroups = userGroupService.getParentUserGroupEntries(ldapConnection, userDN); + if (!userGroups.isEmpty()) { + for (LDAPEntry entry : userGroups) + connectionSearchFilter.append("(seeAlso=").append(escapingService.escapeLDAPSearchFilter(entry.getDN())).append(")"); } // Complete the search filter. http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java new file mode 100644 index 0000000..dfdd9fd --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.guacamole.auth.ldap.group; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPEntry; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.guacamole.auth.ldap.ConfigurationService; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.ObjectQueryService; +import org.apache.guacamole.net.auth.UserGroup; +import org.apache.guacamole.net.auth.simple.SimpleUserGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for querying user group membership and retrieving user groups + * visible to a particular Guacamole user. + */ +public class UserGroupService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(UserGroupService.class); + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Service for executing LDAP queries. + */ + @Inject + private ObjectQueryService queryService; + + /** + * Returns the base search filter which should be used to retrieve user + * groups which do not represent Guacamole connections. As excluding the + * guacConfigGroup object class may not work as expected (may always return + * zero results) if guacConfigGroup object class is not defined, it should + * only be explicitly excluded if it is expected to have been defined. + * + * @return + * The base search filter which should be used to retrieve user groups. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + private String getGroupSearchFilter() throws GuacamoleException { + + // Explicitly exclude guacConfigGroup object class only if it should + // be assumed to be defined (query may fail due to no such object + // class existing otherwise) + if (confService.getConfigurationBaseDN() != null) + return "(!(objectClass=guacConfigGroup))"; + + // Read any object as a group if LDAP is not being used for connection + // storage (guacConfigGroup) + return "(objectClass=*)"; + + } + + /** + * Returns all Guacamole user groups accessible to the user currently bound + * under the given LDAP connection. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @return + * All user groups accessible to the user currently bound under the + * given LDAP connection, as a map of user group identifier to + * corresponding UserGroup object. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public Map<String, UserGroup> getUserGroups(LDAPConnection ldapConnection) + throws GuacamoleException { + + // Do not return any user groups if base DN is not specified + String groupBaseDN = confService.getGroupBaseDN(); + if (groupBaseDN == null) + return Collections.<String, UserGroup>emptyMap(); + + // Retrieve all visible user groups which are not guacConfigGroups + Collection<String> attributes = confService.getGroupNameAttributes(); + List<LDAPEntry> results = queryService.search( + ldapConnection, + groupBaseDN, + getGroupSearchFilter(), + attributes, + null + ); + + // Convert retrieved user groups to map of identifier to Guacamole + // user group object + return queryService.asMap(results, entry -> { + + // Translate entry into UserGroup object having proper identifier + String name = queryService.getIdentifier(entry, attributes); + if (name != null) + return new SimpleUserGroup(name); + + // Ignore user groups which lack a name attribute + logger.debug("User group \"{}\" is missing a name attribute " + + "and will be ignored.", entry.getDN()); + return null; + + }); + + } + + /** + * Returns the LDAP entries representing all user groups that the given + * user is a member of. Only user groups which are readable by the current + * user will be retrieved. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param userDN + * The DN of the user whose group membership should be retrieved. + * + * @return + * The LDAP entries representing all readable parent user groups of the + * user having the given DN. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public List<LDAPEntry> getParentUserGroupEntries(LDAPConnection ldapConnection, + String userDN) throws GuacamoleException { + + // Do not return any user groups if base DN is not specified + String groupBaseDN = confService.getGroupBaseDN(); + if (groupBaseDN == null) + return Collections.<LDAPEntry>emptyList(); + + // Get all groups the user is a member of starting at the groupBaseDN, + // excluding guacConfigGroups + return queryService.search( + ldapConnection, + groupBaseDN, + getGroupSearchFilter(), + Collections.singleton("member"), + userDN + ); + + } + + /** + * Returns the identifiers of all user groups that the given user is a + * member of. Only identifiers of user groups which are readable by the + * current user will be retrieved. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @param userDN + * The DN of the user whose group membership should be retrieved. + * + * @return + * The identifiers of all readable parent user groups of the user + * having the given DN. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of user groups. + */ + public Set<String> getParentUserGroupIdentifiers(LDAPConnection ldapConnection, + String userDN) throws GuacamoleException { + + Collection<String> attributes = confService.getGroupNameAttributes(); + List<LDAPEntry> userGroups = getParentUserGroupEntries(ldapConnection, userDN); + + Set<String> identifiers = new HashSet<>(userGroups.size()); + userGroups.forEach(entry -> { + + // Determine unique identifier for user group + String name = queryService.getIdentifier(entry, attributes); + if (name != null) + identifiers.add(name); + + // Ignore user groups which lack a name attribute + else + logger.debug("User group \"{}\" is missing a name attribute " + + "and will be ignored.", entry.getDN()); + + }); + + return identifiers; + + } + +} http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java index 669efcd..85f004b 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.ldap.user; import com.google.inject.Inject; +import java.util.Set; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -43,13 +44,25 @@ public class AuthenticatedUser extends AbstractAuthenticatedUser { private Credentials credentials; /** - * Initializes this AuthenticatedUser using the given credentials. + * The unique identifiers of all user groups which affect the permissions + * available to this user. + */ + private Set<String> effectiveGroups; + + /** + * Initializes this AuthenticatedUser with the given credentials and set of + * effective user groups. * * @param credentials * The credentials provided when this user was authenticated. + * + * @param effectiveGroups + * The unique identifiers of all user groups which affect the + * permissions available to this user. */ - public void init(Credentials credentials) { + public void init(Credentials credentials, Set<String> effectiveGroups) { this.credentials = credentials; + this.effectiveGroups = effectiveGroups; setIdentifier(credentials.getUsername()); } @@ -63,4 +76,9 @@ public class AuthenticatedUser extends AbstractAuthenticatedUser { return credentials; } + @Override + public Set<String> getEffectiveUserGroups() { + return effectiveGroups; + } + } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/aa0c6542/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java index 26ea6b3..7c520d3 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java @@ -25,6 +25,7 @@ import java.util.Collections; import org.apache.guacamole.auth.ldap.connection.ConnectionService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider; +import org.apache.guacamole.auth.ldap.group.UserGroupService; import org.apache.guacamole.net.auth.AbstractUserContext; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -32,6 +33,7 @@ import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.net.auth.Directory; import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.UserGroup; import org.apache.guacamole.net.auth.simple.SimpleConnectionGroup; import org.apache.guacamole.net.auth.simple.SimpleDirectory; import org.apache.guacamole.net.auth.simple.SimpleUser; @@ -62,6 +64,12 @@ public class UserContext extends AbstractUserContext { private UserService userService; /** + * Service for retrieving user groups. + */ + @Inject + private UserGroupService userGroupService; + + /** * Reference to the AuthenticationProvider associated with this * UserContext. */ @@ -81,6 +89,12 @@ public class UserContext extends AbstractUserContext { private Directory<User> userDirectory; /** + * Directory containing all UserGroup objects accessible to the user + * associated with this UserContext. + */ + private Directory<UserGroup> userGroupDirectory; + + /** * Directory containing all Connection objects accessible to the user * associated with this UserContext. */ @@ -112,12 +126,17 @@ public class UserContext extends AbstractUserContext { throws GuacamoleException { // Query all accessible users - userDirectory = new SimpleDirectory<User>( + userDirectory = new SimpleDirectory<>( userService.getUsers(ldapConnection) ); + // Query all accessible user groups + userGroupDirectory = new SimpleDirectory<>( + userGroupService.getUserGroups(ldapConnection) + ); + // Query all accessible connections - connectionDirectory = new SimpleDirectory<Connection>( + connectionDirectory = new SimpleDirectory<>( connectionService.getConnections(user, ldapConnection) ); @@ -133,6 +152,7 @@ public class UserContext extends AbstractUserContext { self = new SimpleUser( user.getIdentifier(), userDirectory.getIdentifiers(), + userGroupDirectory.getIdentifiers(), connectionDirectory.getIdentifiers(), Collections.singleton(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP) ); @@ -155,6 +175,11 @@ public class UserContext extends AbstractUserContext { } @Override + public Directory<UserGroup> getUserGroupDirectory() throws GuacamoleException { + return userGroupDirectory; + } + + @Override public Directory<Connection> getConnectionDirectory() throws GuacamoleException { return connectionDirectory;