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;

Reply via email to