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

This commit is contained in:
Nick Couchman
2017-03-17 16:16:04 -04:00
committed by Nick Couchman
parent b8abcd6755
commit d98cdd2917
5 changed files with 271 additions and 12 deletions

View File

@@ -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,
@@ -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
);
}
}

View File

@@ -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

View File

@@ -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"; }
};
}

View File

@@ -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;
}
}

View File

@@ -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 {
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);
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;
}
}
}