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 c7e4819d1..2ab7aadf6 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 @@ -251,6 +251,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. @@ -272,6 +290,23 @@ public class ConfigurationService { return constraints; } + /** + * 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, @@ -292,4 +327,21 @@ public class ConfigurationService { ); } + /** + * 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 + ); + } + } 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 bf0534c64..f84912610 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,27 @@ 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 + ldapConstraints.setReferralFollowing(confService.getFollowReferrals()); + + // Set referral authentication to use the provided credentials. + if (userDN != null && !userDN.isEmpty()) + ldapConstraints.setReferralHandler(new ReferralAuthHandler(userDN, password)); + + // Set the maximum number of referrals we follow + 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 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 e13264dd8..0d3823fed 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,34 @@ 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"; } + + }; + + /** + * 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"; } + + }; + } 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 000000000..e605b3c0b --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ReferralAuthHandler.java @@ -0,0 +1,76 @@ +/* + * 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; + +/** + * Class that implements the necessary authentication handling + * for following referrals in LDAP connections. + */ +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; + + /** + * Creates a ReferralAuthHandler object to handle authentication when + * following referrals in a LDAP connection, using the provided dn and + * password. + */ + public ReferralAuthHandler(String dn, String password) { + 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 UnsupportedOperationException("Unexpected lack of UTF-8 support.", e); + } + ldapAuth = new LDAPAuthProvider(dn, passwordBytes); + } + + @Override + public LDAPAuthProvider getAuthProvider(String host, int port) { + return ldapAuth; + } + +} 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 eea1a95ac..3ce00e3f2 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,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.Collections; import java.util.Enumeration; @@ -129,62 +130,79 @@ public class ConnectionService { Map connections = new HashMap(); while (results.hasMore()) { - LDAPEntry entry = results.next(); + try { - // Get common name (CN) - LDAPAttribute cn = entry.getAttribute("cn"); - if (cn == null) { - logger.warn("guacConfigGroup is missing a cn."); - continue; - } + LDAPEntry entry = results.next(); - // Get associated protocol - LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); - if (protocol == null) { - logger.warn("guacConfigGroup \"{}\" is missing the " - + "required \"guacConfigProtocol\" attribute.", - cn.getStringValue()); - continue; - } + // Get common name (CN) + LDAPAttribute cn = entry.getAttribute("cn"); + if (cn == null) { + logger.warn("guacConfigGroup is missing a cn."); + continue; + } - // Set protocol - GuacamoleConfiguration config = new GuacamoleConfiguration(); - config.setProtocol(protocol.getStringValue()); + // Get associated protocol + LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); + if (protocol == null) { + logger.warn("guacConfigGroup \"{}\" is missing the " + + "required \"guacConfigProtocol\" attribute.", + cn.getStringValue()); + continue; + } - // Get parameters, if any - LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); - if (parameterAttribute != null) { + // Set protocol + GuacamoleConfiguration config = new GuacamoleConfiguration(); + config.setProtocol(protocol.getStringValue()); - // For each parameter - Enumeration parameters = parameterAttribute.getStringValues(); - while (parameters.hasMoreElements()) { + // Get parameters, if any + LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); + if (parameterAttribute != null) { - String parameter = (String) parameters.nextElement(); + // For each parameter + Enumeration parameters = parameterAttribute.getStringValues(); + while (parameters.hasMoreElements()) { - // Parse parameter - int equals = parameter.indexOf('='); - if (equals != -1) { + String parameter = (String) parameters.nextElement(); - // Parse name - String name = parameter.substring(0, equals); - String value = parameter.substring(equals+1); + // Parse parameter + int equals = parameter.indexOf('='); + if (equals != -1) { - config.setParameter(name, value); + // Parse name + String name = parameter.substring(0, equals); + String value = parameter.substring(equals+1); + + config.setParameter(name, value); + + } } } + // Filter the configuration, substituting all defined tokens + tokenFilter.filterValues(config.getParameters()); + + // Store connection using cn for both identifier and name + String name = cn.getStringValue(); + Connection connection = new SimpleConnection(name, name, config); + connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); + connections.put(name, connection); + } - // Filter the configuration, substituting all defined tokens - tokenFilter.filterValues(config.getParameters()); - - // Store connection using cn for both identifier and name - String name = cn.getStringValue(); - Connection connection = new SimpleConnection(name, name, config); - connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); - connections.put(name, connection); + // Deal with issues following LDAP referrals + 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); + } + } } @@ -251,8 +269,22 @@ public class ConnectionService { // The guacConfig group uses the seeAlso attribute to refer // to these other groups while (userRoleGroupResults.hasMore()) { - LDAPEntry entry = userRoleGroupResults.next(); - connectionSearchFilter.append("(seeAlso=").append(escapingService.escapeLDAPSearchFilter(entry.getDN())).append(")"); + 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); + } + } } } 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 91f1636e5..9d27f1e00 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,36 @@ public class UserService { // Read all visible users while (results.hasMore()) { - LDAPEntry entry = results.next(); + try { + + 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); - // 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); + // Deal with errors trying to follow referrals + 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); + } + } } @@ -267,8 +285,23 @@ public class UserService { // Add all DNs for found users while (results.hasMore()) { - LDAPEntry entry = results.next(); - userDNs.add(entry.getDN()); + try { + LDAPEntry entry = results.next(); + userDNs.add(entry.getDN()); + } + + // Deal with errors following referrals + catch (LDAPReferralException e) { + if (confService.getFollowReferrals()) { + logger.error("Error trying to follow a referral: {}", e.getFailedReferral()); + logger.debug("Encountered an error trying to follow a referral.", e); + throw new GuacamoleServerException("Failed while trying to follow referrals.", e); + } + else { + logger.warn("Given a referral, not following it. Error was: {}", e.getMessage()); + logger.debug("Given a referral, but configured to not follow them.", e); + } + } } // Return all discovered DNs (if any)