blob: 67ed4dec07c21c7b24c61d09c6af15ac10d07613 [file] [log] [blame]
/*
* 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 java.io.IOException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.api.ldap.model.message.BindRequest;
import org.apache.directory.api.ldap.model.message.BindRequestImpl;
import org.apache.directory.api.ldap.model.message.BindResponse;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.url.LdapUrl;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
import org.apache.guacamole.auth.ldap.conf.EncryptionMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for creating and managing connections to LDAP servers.
*/
public class LDAPConnectionService {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(LDAPConnectionService.class);
/**
* Service for retrieving LDAP server configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Creates a new instance of LdapNetworkConnection, configured as required
* to use whichever encryption method is requested within
* guacamole.properties.
*
* @return
* A new LdapNetworkConnection instance which has already been
* configured to use the encryption method requested within
* guacamole.properties.
*
* @throws GuacamoleException
* If an error occurs while parsing guacamole.properties, or if the
* requested encryption method is actually not implemented (a bug).
*/
private LdapNetworkConnection createLDAPConnection() throws GuacamoleException {
String host = confService.getServerHostname();
int port = confService.getServerPort();
// Map encryption method to proper connection and socket factory
EncryptionMethod encryptionMethod = confService.getEncryptionMethod();
switch (encryptionMethod) {
// Unencrypted LDAP connection
case NONE:
logger.debug("Connection to LDAP server without encryption.");
return new LdapNetworkConnection(host, port);
// LDAP over SSL (LDAPS)
case SSL:
logger.debug("Connecting to LDAP server using SSL/TLS.");
return new LdapNetworkConnection(host, port, true);
// LDAP + STARTTLS
case STARTTLS:
logger.debug("Connecting to LDAP server using STARTTLS.");
return new LdapNetworkConnection(host, port);
// The encryption method, though known, is not actually
// implemented. If encountered, this would be a bug.
default:
throw new GuacamoleUnsupportedException("Unimplemented encryption method: " + encryptionMethod);
}
}
/**
* Binds to the LDAP server using the provided user DN and password.
*
* @param userDN
* The DN of the user to bind as, or null to bind anonymously.
*
* @param password
* The password to use when binding as the specified user, or null to
* attempt to bind without a password.
*
* @return
* A bound LDAP connection, or null if the connection could not be
* bound.
*
* @throws GuacamoleException
* If the configuration details relevant to binding to the LDAP server
* cannot be read.
*/
public LdapNetworkConnection bindAs(Dn userDN, String password)
throws GuacamoleException {
// Get ldapConnection and try to connect and bind.
LdapNetworkConnection ldapConnection = createLDAPConnection();
try {
// Connect to LDAP server
ldapConnection.connect();
// Explicitly start TLS if requested
if (confService.getEncryptionMethod() == EncryptionMethod.STARTTLS)
ldapConnection.startTls();
// Bind using provided credentials
BindRequest bindRequest = new BindRequestImpl();
bindRequest.setDn(userDN);
bindRequest.setCredentials(password);
BindResponse bindResponse = ldapConnection.bind(bindRequest);
if (bindResponse.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS) {
ldapConnection.close();
logger.debug("LDAP bind attempt failed: {}", bindResponse.toString());
return null;
}
}
// Disconnect if an error occurs during bind
catch (LdapException e) {
ldapConnection.close();
logger.debug("Unable to bind to LDAP server.", e);
return null;
}
return ldapConnection;
}
/**
* Establishes a new network connection to the LDAP server indicated by the
* given LDAP referral URL. The credentials used to bind with the referred
* LDAP server will be the same as those used to bind with the original
* connection.
*
* @param ldapConnection
* The LDAP connection that bind credentials should be copied from.
*
* @param url
* The URL of the referred LDAP server to which a new network
* connection should be established.
*
* @return
* A LdapNetworkConnection representing a network connection to the
* LDAP server specified in the URL, or null if the specified URL is
* invalid.
*/
public LdapNetworkConnection getReferralConnection(
LdapNetworkConnection ldapConnection, String url) {
LdapConnectionConfig ldapConfig = ldapConnection.getConfig();
LdapConnectionConfig referralConfig = new LdapConnectionConfig();
// Copy bind name and password from original config
referralConfig.setName(ldapConfig.getName());
referralConfig.setCredentials(ldapConfig.getCredentials());
LdapUrl referralUrl;
try {
referralUrl = new LdapUrl(url);
}
catch (LdapException e) {
logger.debug("Referral URL \"{}\" is invalid.", url, e);
return null;
}
// Look for host - if not there, bail out.
String host = referralUrl.getHost();
if (host == null || host.isEmpty()) {
logger.debug("Referral URL \"{}\" is invalid as it contains "
+ "no hostname.", url );
return null;
}
referralConfig.setLdapHost(host);
// Look for port, or assign a default.
int port = referralUrl.getPort();
if (port < 1)
referralConfig.setLdapPort(389);
else
referralConfig.setLdapPort(port);
// Deal with SSL connections
if (referralUrl.getScheme().equals(LdapUrl.LDAPS_SCHEME))
referralConfig.setUseSsl(true);
else
referralConfig.setUseSsl(false);
return new LdapNetworkConnection(referralConfig);
}
/**
* Disconnects the given LDAP connection, logging any failure to do so
* appropriately.
*
* @param ldapConnection
* The LDAP connection to disconnect.
*/
public void disconnect(LdapConnection ldapConnection) {
// Attempt disconnect
try {
ldapConnection.close();
}
// Warn if disconnect unexpectedly fails
catch (IOException e) {
logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage());
logger.debug("LDAP disconnect failed.", e);
}
}
/**
* Generate a SearchRequest object using the given Base DN and filter
* and retrieving other properties from the LDAP configuration service.
*
* @param baseDn
* The LDAP Base DN at which to search the search.
*
* @param filter
* A string representation of a LDAP filter to use for the search.
*
* @return
* The properly-configured SearchRequest object.
*
* @throws GuacamoleException
* If an error occurs retrieving any of the configuration values.
*/
public SearchRequest getSearchRequest(Dn baseDn, ExprNode filter)
throws GuacamoleException {
SearchRequest searchRequest = new SearchRequestImpl();
searchRequest.setBase(baseDn);
searchRequest.setDerefAliases(confService.getDereferenceAliases());
searchRequest.setScope(SearchScope.SUBTREE);
searchRequest.setFilter(filter);
searchRequest.setSizeLimit(confService.getMaxResults());
searchRequest.setTimeLimit(confService.getOperationTimeout());
searchRequest.setTypesOnly(false);
if (confService.getFollowReferrals())
searchRequest.followReferrals();
return searchRequest;
}
}