GUACAMOLE-243: Implement LDAP referral handling in Guacamole LDAP extension.


Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/d98cdd29
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/d98cdd29
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/d98cdd29

Branch: refs/heads/staging/0.9.14-incubating
Commit: d98cdd29178b5623032b7d340658221549114361
Parents: b8abcd6
Author: Nick Couchman <[email protected]>
Authored: Fri Mar 17 16:16:04 2017 -0400
Committer: Nick Couchman <[email protected]>
Committed: Mon Oct 23 09:34:22 2017 -0400

----------------------------------------------------------------------
 .../auth/ldap/ConfigurationService.java         | 84 ++++++++++++++++-
 .../auth/ldap/LDAPConnectionService.java        | 22 +++++
 .../auth/ldap/LDAPGuacamoleProperties.java      | 41 +++++++++
 .../auth/ldap/ReferralAuthHandler.java          | 97 ++++++++++++++++++++
 .../guacamole/auth/ldap/user/UserService.java   | 39 +++++---
 5 files changed, 271 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d98cdd29/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java
----------------------------------------------------------------------
diff --git 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java
 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java
index c7e4819..0b6f9e9 100644
--- 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java
+++ 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConfigurationService.java
@@ -252,6 +252,24 @@ public class ConfigurationService {
     }
 
     /**
+     * Returns the boolean value for whether the connection should
+     * follow referrals or not.  By default, it will not.
+     *
+     * @return
+     *     The boolean value of whether to follow referrals
+     *     as configured in guacamole.properties
+     *
+     * @throws GuacamoleException
+     *     If guacamole.properties cannot be parsed.
+     */
+    public boolean getFollowReferrals() throws GuacamoleException {
+        return environment.getProperty(
+            LDAPGuacamoleProperties.LDAP_FOLLOW_REFERRALS,
+            false
+        );
+    }
+
+    /**
      * Returns a set of LDAPSearchConstraints to apply globally
      * to all LDAP searches.
      *
@@ -273,6 +291,23 @@ public class ConfigurationService {
     }
 
     /**
+     * Returns the maximum number of referral hops to follow.
+     *
+     * @return
+     *     The maximum number of referral hops to follow
+     *     as configured in guacamole.properties
+     *
+     * @throws GuacamoleException
+     *     If guacamole.properties cannot be parsed.
+     */
+    public int getMaxReferralHops() throws GuacamoleException {
+        return environment.getProperty(
+            LDAPGuacamoleProperties.LDAP_MAX_REFERRAL_HOPS,
+            5
+        );
+    }
+
+    /**
      * Returns the search filter that should be used when querying the
      * LDAP server for Guacamole users.  If no filter is specified,
      * a default of "(objectClass=*)" is returned.
@@ -281,7 +316,6 @@ public class ConfigurationService {
      *     The search filter that should be used when querying the
      *     LDAP server for users that are valid in Guacamole, or
      *     "(objectClass=*)" if not specified.
-     *
      * @throws GuacamoleException
      *     If guacamole.properties cannot be parsed.
      */
@@ -292,4 +326,52 @@ public class ConfigurationService {
         );
     }
 
+    /**
+     * Returns the authentication method to use during referral following.
+     *
+     * @return
+     *     The authentication method to use during referral following
+     *     as configured in guacamole.properties or as derived from
+     *     other configuration options.
+     *
+     * @throws GuacamoleException
+     *     If guacamole.properties cannot be parsed.
+     */
+    public String getReferralAuthentication() throws GuacamoleException {
+        String confMethod = environment.getProperty(
+            LDAPGuacamoleProperties.LDAP_REFERRAL_AUTHENTICATION
+        );
+
+        if (confMethod == null)
+
+            if (getSearchBindDN() != null && getSearchBindPassword() != null)
+                return "bind";
+
+            else
+                return "anonymous";
+
+        else if (confMethod.equals("bind") && (getSearchBindDN() == null || 
getSearchBindPassword() == null))
+            throw new GuacamoleException("Referral is set to bind with 
credentials, but credentials are not configured.");
+
+        return confMethod;
+
+    }
+
+    /**
+     * Returns the maximum number of seconds to wait for LDAP operations
+     *
+     * @return
+     *     The maximum number of seconds to wait for LDAP operations
+     *     as configured in guacamole.properties
+     *
+     * @throws GuacamoleException
+     *     If guacamole.properties cannot be parsed.
+     */
+    public int getOperationTimeout() throws GuacamoleException {
+        return environment.getProperty(
+            LDAPGuacamoleProperties.LDAP_OPERATION_TIMEOUT,
+            30
+        );
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d98cdd29/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
----------------------------------------------------------------------
diff --git 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
index bf0534c..c3b2e12 100644
--- 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
+++ 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
@@ -21,12 +21,14 @@ package org.apache.guacamole.auth.ldap;
 
 import com.google.inject.Inject;
 import com.novell.ldap.LDAPConnection;
+import com.novell.ldap.LDAPConstraints;
 import com.novell.ldap.LDAPException;
 import com.novell.ldap.LDAPJSSESecureSocketFactory;
 import com.novell.ldap.LDAPJSSEStartTLSFactory;
 import java.io.UnsupportedEncodingException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.GuacamoleUnsupportedException;
+import org.apache.guacamole.auth.ldap.ReferralAuthHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -111,6 +113,26 @@ public class LDAPConnectionService {
         // Obtain appropriately-configured LDAPConnection instance
         LDAPConnection ldapConnection = createLDAPConnection();
 
+        // Configure LDAP connection constraints
+        LDAPConstraints ldapConstraints = ldapConnection.getConstraints();
+        if (ldapConstraints == null)
+          ldapConstraints = new LDAPConstraints();
+
+        // Set whether or not we follow referrals, and max hops
+        ldapConstraints.setReferralFollowing(confService.getFollowReferrals());
+        String refAuthMethod = confService.getReferralAuthentication();
+
+        if (refAuthMethod != null && refAuthMethod.equals("bind"))
+            ldapConstraints.setReferralHandler(new ReferralAuthHandler(userDN, 
password));
+
+        ldapConstraints.setHopLimit(confService.getMaxReferralHops());
+
+        // Set timelimit to wait for LDAP operations, converting to ms
+        ldapConstraints.setTimeLimit(confService.getOperationTimeout() * 1000);
+
+        // Apply the constraints to the connection
+        ldapConnection.setConstraints(ldapConstraints);
+
         try {
 
             // Connect to LDAP server

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d98cdd29/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java
----------------------------------------------------------------------
diff --git 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java
 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java
index e13264d..7a1dcad 100644
--- 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java
+++ 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPGuacamoleProperties.java
@@ -19,6 +19,7 @@
 
 package org.apache.guacamole.auth.ldap;
 
+import org.apache.guacamole.properties.BooleanGuacamoleProperty;
 import org.apache.guacamole.properties.IntegerGuacamoleProperty;
 import org.apache.guacamole.properties.StringGuacamoleProperty;
 
@@ -174,4 +175,44 @@ public class LDAPGuacamoleProperties {
 
     };
 
+    /**
+     * Whether or not we should follow referrals
+     */
+    public static final BooleanGuacamoleProperty LDAP_FOLLOW_REFERRALS = new 
BooleanGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "ldap-follow-referrals"; }
+
+    };
+
+    /**
+     * Maximum number of referral hops to follow
+     */
+    public static final IntegerGuacamoleProperty LDAP_MAX_REFERRAL_HOPS = new 
IntegerGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "ldap-max-referral-hops"; }
+
+    };
+
+    /**
+     * Authentication method to use to follow referrals
+     */
+    public static final StringGuacamoleProperty LDAP_REFERRAL_AUTHENTICATION = 
new StringGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "ldap-referral-authentication"; }
+
+    };
+
+    /**
+     * Number of seconds to wait for LDAP operations to complete
+     */
+    public static final IntegerGuacamoleProperty LDAP_OPERATION_TIMEOUT = new 
IntegerGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "ldap-operation-timeout"; }
+
+    };
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d98cdd29/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ReferralAuthHandler.java
----------------------------------------------------------------------
diff --git 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ReferralAuthHandler.java
 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ReferralAuthHandler.java
new file mode 100644
index 0000000..21a7644
--- /dev/null
+++ 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ReferralAuthHandler.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import com.google.inject.Inject;
+import com.novell.ldap.LDAPAuthHandler;
+import com.novell.ldap.LDAPAuthProvider;
+import com.novell.ldap.LDAPConnection;
+import java.io.UnsupportedEncodingException;
+import org.apache.guacamole.GuacamoleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ReferralAuthHandler implements LDAPAuthHandler {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = 
LoggerFactory.getLogger(ReferralAuthHandler.class);
+
+    /**
+     * The LDAPAuthProvider object that will be set and returned to the 
referral handler.
+     */
+    private final LDAPAuthProvider ldapAuth;
+
+    /**
+     * Service for retrieving LDAP server configuration information.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+
+    public ReferralAuthHandler() throws GuacamoleException {
+        String binddn = confService.getSearchBindDN();
+        String password = confService.getSearchBindPassword();
+        byte[] passwordBytes;
+        try {
+
+            // Convert password into corresponding byte array
+            if (password != null)
+                passwordBytes = password.getBytes("UTF-8");
+            else
+                passwordBytes = null;
+
+        }
+        catch (UnsupportedEncodingException e) {
+            logger.error("Unexpected lack of support for UTF-8: {}", 
e.getMessage());
+            logger.debug("Support for UTF-8 (as required by Java spec) not 
found.", e);
+            throw new GuacamoleException("Could not set password due to 
missing support for UTF-8 encoding.");
+        }
+
+        ldapAuth = new LDAPAuthProvider(binddn, passwordBytes);
+
+    }
+
+    public ReferralAuthHandler(String dn, String password) throws 
GuacamoleException {
+        byte[] passwordBytes;
+        try {
+
+            // Convert password into corresponding byte array
+            if (password != null)
+                passwordBytes = password.getBytes("UTF-8");
+            else
+                passwordBytes = null;
+
+        }   
+        catch (UnsupportedEncodingException e) {
+            logger.error("Unexpected lack of support for UTF-8: {}", 
e.getMessage());
+            logger.debug("Support for UTF-8 (as required by Java spec) not 
found.", e); 
+            throw new GuacamoleException("Could not set password due to 
missing UTF-8 support.");
+        }
+        ldapAuth = new LDAPAuthProvider(dn, passwordBytes);
+    }
+
+    @Override
+    public LDAPAuthProvider getAuthProvider(String host, int port) {
+        return ldapAuth;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d98cdd29/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
----------------------------------------------------------------------
diff --git 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
index 91f1636..087365f 100644
--- 
a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
+++ 
b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
@@ -24,6 +24,7 @@ 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.ArrayList;
 import java.util.HashMap;
@@ -107,19 +108,35 @@ public class UserService {
             // Read all visible users
             while (results.hasMore()) {
 
-                LDAPEntry entry = results.next();
+                try {
 
-                // Get username from record
-                LDAPAttribute username = entry.getAttribute(usernameAttribute);
-                if (username == null) {
-                    logger.warn("Queried user is missing the username 
attribute \"{}\".", usernameAttribute);
-                    continue;
-                }
+                    LDAPEntry entry = results.next();
+
+                    // Get username from record
+                    LDAPAttribute username = 
entry.getAttribute(usernameAttribute);
+                    if (username == null) {
+                        logger.warn("Queried user is missing the username 
attribute \"{}\".", usernameAttribute);
+                        continue;
+                    }
 
-                // Store user using their username as the identifier
-                String identifier = username.getStringValue();
-                if (users.put(identifier, new SimpleUser(identifier)) != null)
-                    logger.warn("Possibly ambiguous user account: \"{}\".", 
identifier);
+                    // Store user using their username as the identifier
+                    String identifier = username.getStringValue();
+                    if (users.put(identifier, new SimpleUser(identifier)) != 
null)
+                        logger.warn("Possibly ambiguous user account: 
\"{}\".", identifier);
+
+                }
+                catch (LDAPReferralException e) {
+                    if (confService.getFollowReferrals()) {
+                        logger.error("Could not follow referral.", 
e.getMessage());
+                        logger.debug("Error encountered trying to follow 
referral.", e);
+                        throw new GuacamoleException("Could not follow LDAP 
referral.");
+                    }
+                    else {
+                        logger.warn("Encountered a referral, but not following 
it.", e.getMessage());
+                        logger.debug("Got a referral, but not configured to 
follow it.", e);
+                        continue;
+                    }
+                }
 
             }
 

Reply via email to