mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUAC-586: Implement listing of visible users within LDAP (by completely rewriting the LDAP auth provider).
This commit is contained in:
@@ -102,6 +102,18 @@
|
||||
<version>4.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guice -->
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-multibindings</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Glyptodon LLC
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,25 +23,15 @@
|
||||
package net.sourceforge.guacamole.net.auth.ldap;
|
||||
|
||||
|
||||
import com.novell.ldap.LDAPAttribute;
|
||||
import com.novell.ldap.LDAPConnection;
|
||||
import com.novell.ldap.LDAPEntry;
|
||||
import com.novell.ldap.LDAPException;
|
||||
import com.novell.ldap.LDAPSearchResults;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import org.glyptodon.guacamole.auth.ldap.AuthenticationProviderService;
|
||||
import org.glyptodon.guacamole.auth.ldap.LDAPAuthenticationProviderModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import net.sourceforge.guacamole.net.auth.ldap.properties.LDAPGuacamoleProperties;
|
||||
import org.glyptodon.guacamole.GuacamoleServerException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.environment.LocalEnvironment;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleAuthenticationProvider;
|
||||
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.glyptodon.guacamole.net.auth.UserContext;
|
||||
|
||||
/**
|
||||
* Allows users to be authenticated against an LDAP server. Each user may have
|
||||
@@ -50,17 +40,13 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider {
|
||||
public class LDAPAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
* Injector which will manage the object graph of this authentication
|
||||
* provider.
|
||||
*/
|
||||
private Logger logger = LoggerFactory.getLogger(LDAPAuthenticationProvider.class);
|
||||
|
||||
/**
|
||||
* Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
private final Injector injector;
|
||||
|
||||
/**
|
||||
* Creates a new LDAPAuthenticationProvider that authenticates users
|
||||
@@ -71,7 +57,12 @@ public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider {
|
||||
* a property.
|
||||
*/
|
||||
public LDAPAuthenticationProvider() throws GuacamoleException {
|
||||
environment = new LocalEnvironment();
|
||||
|
||||
// Set up Guice injector.
|
||||
injector = Guice.createInjector(
|
||||
new LDAPAuthenticationProviderModule(this)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,219 +70,33 @@ public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider {
|
||||
return "ldap";
|
||||
}
|
||||
|
||||
// Courtesy of OWASP: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
|
||||
private static String escapeLDAPSearchFilter(String filter) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < filter.length(); i++) {
|
||||
char curChar = filter.charAt(i);
|
||||
switch (curChar) {
|
||||
case '\\':
|
||||
sb.append("\\5c");
|
||||
break;
|
||||
case '*':
|
||||
sb.append("\\2a");
|
||||
break;
|
||||
case '(':
|
||||
sb.append("\\28");
|
||||
break;
|
||||
case ')':
|
||||
sb.append("\\29");
|
||||
break;
|
||||
case '\u0000':
|
||||
sb.append("\\00");
|
||||
break;
|
||||
default:
|
||||
sb.append(curChar);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@Override
|
||||
public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException {
|
||||
|
||||
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
|
||||
return authProviderService.authenticateUser(credentials);
|
||||
|
||||
// Courtesy of OWASP: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
|
||||
private static String escapeDN(String name) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if ((name.length() > 0) && ((name.charAt(0) == ' ') || (name.charAt(0) == '#'))) {
|
||||
sb.append('\\'); // add the leading backslash if needed
|
||||
}
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char curChar = name.charAt(i);
|
||||
switch (curChar) {
|
||||
case '\\':
|
||||
sb.append("\\\\");
|
||||
break;
|
||||
case ',':
|
||||
sb.append("\\,");
|
||||
break;
|
||||
case '+':
|
||||
sb.append("\\+");
|
||||
break;
|
||||
case '"':
|
||||
sb.append("\\\"");
|
||||
break;
|
||||
case '<':
|
||||
sb.append("\\<");
|
||||
break;
|
||||
case '>':
|
||||
sb.append("\\>");
|
||||
break;
|
||||
case ';':
|
||||
sb.append("\\;");
|
||||
break;
|
||||
default:
|
||||
sb.append(curChar);
|
||||
}
|
||||
}
|
||||
if ((name.length() > 1) && (name.charAt(name.length() - 1) == ' ')) {
|
||||
sb.insert(sb.length() - 1, '\\'); // add the trailing backslash if needed
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, GuacamoleConfiguration> getAuthorizedConfigurations(Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// Require username
|
||||
if (credentials.getUsername() == null) {
|
||||
logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider.");
|
||||
return null;
|
||||
public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
return authenticatedUser;
|
||||
}
|
||||
|
||||
// Require password, and do not allow anonymous binding
|
||||
if (credentials.getPassword() == null
|
||||
|| credentials.getPassword().length() == 0) {
|
||||
logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider.");
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Connect to LDAP server
|
||||
LDAPConnection ldapConnection;
|
||||
try {
|
||||
|
||||
ldapConnection = new LDAPConnection();
|
||||
ldapConnection.connect(
|
||||
environment.getRequiredProperty(LDAPGuacamoleProperties.LDAP_HOSTNAME),
|
||||
environment.getRequiredProperty(LDAPGuacamoleProperties.LDAP_PORT)
|
||||
);
|
||||
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
|
||||
return authProviderService.getUserContext(authenticatedUser);
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
throw new GuacamoleServerException("Unable to connect to LDAP server.", e);
|
||||
}
|
||||
|
||||
// Get username attribute
|
||||
String username_attribute = environment.getRequiredProperty(
|
||||
LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE
|
||||
);
|
||||
|
||||
// Get user base DN
|
||||
String user_base_dn = environment.getRequiredProperty(
|
||||
LDAPGuacamoleProperties.LDAP_USER_BASE_DN
|
||||
);
|
||||
|
||||
// Construct user DN
|
||||
String user_dn =
|
||||
escapeDN(username_attribute) + "=" + escapeDN(credentials.getUsername())
|
||||
+ "," + user_base_dn;
|
||||
|
||||
try {
|
||||
|
||||
// Bind as user
|
||||
try {
|
||||
ldapConnection.bind(
|
||||
LDAPConnection.LDAP_V3,
|
||||
user_dn,
|
||||
credentials.getPassword().getBytes("UTF-8")
|
||||
);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new GuacamoleException(e);
|
||||
}
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
logger.debug("LDAP bind failed.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get config base DN
|
||||
String config_base_dn = environment.getRequiredProperty(
|
||||
LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN
|
||||
);
|
||||
|
||||
// Pull all connections
|
||||
try {
|
||||
|
||||
// Find all guac configs for this user
|
||||
LDAPSearchResults results = ldapConnection.search(
|
||||
config_base_dn,
|
||||
LDAPConnection.SCOPE_SUB,
|
||||
"(&(objectClass=guacConfigGroup)(member=" + escapeLDAPSearchFilter(user_dn) + "))",
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// Add all configs
|
||||
Map<String, GuacamoleConfiguration> configs = new TreeMap<String, GuacamoleConfiguration>();
|
||||
while (results.hasMore()) {
|
||||
|
||||
LDAPEntry entry = results.next();
|
||||
|
||||
// New empty configuration
|
||||
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||
|
||||
// Get CN
|
||||
LDAPAttribute cn = entry.getAttribute("cn");
|
||||
if (cn == null)
|
||||
throw new GuacamoleException("guacConfigGroup without cn");
|
||||
|
||||
// Get protocol
|
||||
LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol");
|
||||
if (protocol == null)
|
||||
throw new GuacamoleException("guacConfigGroup without guacConfigProtocol");
|
||||
|
||||
// Set protocol
|
||||
config.setProtocol(protocol.getStringValue());
|
||||
|
||||
// Get parameters, if any
|
||||
LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter");
|
||||
if (parameterAttribute != null) {
|
||||
|
||||
// For each parameter
|
||||
Enumeration<?> parameters = parameterAttribute.getStringValues();
|
||||
while (parameters.hasMoreElements()) {
|
||||
|
||||
String parameter = (String) parameters.nextElement();
|
||||
|
||||
// Parse parameter
|
||||
int equals = parameter.indexOf('=');
|
||||
if (equals != -1) {
|
||||
|
||||
// Parse name
|
||||
String name = parameter.substring(0, equals);
|
||||
String value = parameter.substring(equals+1);
|
||||
|
||||
config.setParameter(name, value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Store config by CN
|
||||
configs.put(cn.getStringValue(), config);
|
||||
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
ldapConnection.disconnect();
|
||||
return configs;
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
throw new GuacamoleServerException("Error while querying for connections.", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext updateUserContext(UserContext context,
|
||||
AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.novell.ldap.LDAPConnection;
|
||||
import com.novell.ldap.LDAPException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import org.glyptodon.guacamole.auth.ldap.user.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.auth.ldap.user.UserContext;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service providing convenience functions for the LDAP AuthenticationProvider
|
||||
* implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class AuthenticationProviderService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class);
|
||||
|
||||
/**
|
||||
* Service for escaping parts of LDAP queries.
|
||||
*/
|
||||
@Inject
|
||||
private EscapingService escapingService;
|
||||
|
||||
/**
|
||||
* Service for retrieving LDAP server configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Provider for AuthenticatedUser objects.
|
||||
*/
|
||||
@Inject
|
||||
private Provider<AuthenticatedUser> authenticatedUserProvider;
|
||||
|
||||
/**
|
||||
* Provider for UserContext objects.
|
||||
*/
|
||||
@Inject
|
||||
private Provider<UserContext> userContextProvider;
|
||||
|
||||
/**
|
||||
* Binds to the LDAP server using the provided Guacamole credentials. The
|
||||
* DN of the user is derived using the LDAP configuration properties
|
||||
* provided in guacamole.properties, as is the server hostname and port
|
||||
* information.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use to bind to the LDAP server.
|
||||
*
|
||||
* @return
|
||||
* A bound LDAP connection, or null if the connection could not be
|
||||
* bound.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while binding to the LDAP server.
|
||||
*/
|
||||
private LDAPConnection bindAs(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
LDAPConnection ldapConnection;
|
||||
|
||||
// Require username
|
||||
if (credentials.getUsername() == null) {
|
||||
logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Require password, and do not allow anonymous binding
|
||||
if (credentials.getPassword() == null
|
||||
|| credentials.getPassword().length() == 0) {
|
||||
logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Connect to LDAP server
|
||||
try {
|
||||
ldapConnection = new LDAPConnection();
|
||||
ldapConnection.connect(
|
||||
confService.getServerHostname(),
|
||||
confService.getServerPort()
|
||||
);
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
logger.error("Unable to connect to LDAP server: {}", e.getMessage());
|
||||
logger.debug("Failed to connect to LDAP server.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Bind using provided credentials
|
||||
try {
|
||||
|
||||
// Construct user DN
|
||||
String userDN =
|
||||
escapingService.escapeDN(confService.getUsernameAttribute())
|
||||
+ "=" + escapingService.escapeDN(credentials.getUsername())
|
||||
+ "," + confService.getUserBaseDN();
|
||||
|
||||
// Bind as user
|
||||
try {
|
||||
ldapConnection.bind(LDAPConnection.LDAP_V3, userDN,
|
||||
credentials.getPassword().getBytes("UTF-8"));
|
||||
}
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disconnect if an error occurs during bind
|
||||
catch (LDAPException e) {
|
||||
ldapConnection.disconnect();
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
logger.debug("LDAP bind failed.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return ldapConnection;
|
||||
|
||||
}
|
||||
|
||||
public AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Attempt bind
|
||||
LDAPConnection ldapConnection = bindAs(credentials);
|
||||
if (ldapConnection == null)
|
||||
throw new GuacamoleInsufficientCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD);
|
||||
|
||||
try {
|
||||
|
||||
// Return AuthenticatedUser if bind succeeds
|
||||
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
|
||||
authenticatedUser.init(credentials);
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
// Always disconnect
|
||||
finally {
|
||||
|
||||
// Attempt disconnect
|
||||
try {
|
||||
ldapConnection.disconnect();
|
||||
}
|
||||
|
||||
// Warn if disconnect unexpectedly fails
|
||||
catch (LDAPException e) {
|
||||
logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage());
|
||||
logger.debug("LDAP disconnect failed.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UserContext object initialized with data accessible to the
|
||||
* given AuthenticatedUser.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The AuthenticatedUser to retrieve data for.
|
||||
*
|
||||
* @return
|
||||
* A UserContext object initialized with data accessible to the given
|
||||
* AuthenticatedUser.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the UserContext cannot be created due to an error.
|
||||
*/
|
||||
public UserContext getUserContext(org.glyptodon.guacamole.net.auth.AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Bind using credentials associated with AuthenticatedUser
|
||||
Credentials credentials = authenticatedUser.getCredentials();
|
||||
LDAPConnection ldapConnection = bindAs(credentials);
|
||||
if (ldapConnection == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
|
||||
// Build user context by querying LDAP
|
||||
UserContext userContext = userContextProvider.get();
|
||||
userContext.init(authenticatedUser, ldapConnection);
|
||||
return userContext;
|
||||
|
||||
}
|
||||
|
||||
// Always disconnect
|
||||
finally {
|
||||
|
||||
// Attempt disconnect
|
||||
try {
|
||||
ldapConnection.disconnect();
|
||||
}
|
||||
|
||||
// Warn if disconnect unexpectedly fails
|
||||
catch (LDAPException e) {
|
||||
logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage());
|
||||
logger.debug("LDAP disconnect failed.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
|
||||
/**
|
||||
* Service for retrieving configuration information regarding the LDAP server.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ConfigurationService {
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
@Inject
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Returns the hostname of the LDAP server as configured with
|
||||
* guacamole.properties. By default, this will be "localhost".
|
||||
*
|
||||
* @return
|
||||
* The hostname of the LDAP server, as configured with
|
||||
* guacamole.properties.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed.
|
||||
*/
|
||||
public String getServerHostname() throws GuacamoleException {
|
||||
return environment.getProperty(
|
||||
LDAPGuacamoleProperties.LDAP_HOSTNAME,
|
||||
"localhost"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port of the LDAP server configured with
|
||||
* guacamole.properties. By default, this will be 389 - the standard LDAP
|
||||
* port.
|
||||
*
|
||||
* @return
|
||||
* The port of the LDAP server, as configured with
|
||||
* guacamole.properties.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed.
|
||||
*/
|
||||
public int getServerPort() throws GuacamoleException {
|
||||
return environment.getProperty(
|
||||
LDAPGuacamoleProperties.LDAP_PORT,
|
||||
389
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username attribute which should be used to query and bind
|
||||
* users using the LDAP directory. By default, this will be "uid" - a
|
||||
* common attribute used for this purpose.
|
||||
*
|
||||
* @return
|
||||
* The username attribute which should be used to query and bind users
|
||||
* using the LDAP directory.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed.
|
||||
*/
|
||||
public String getUsernameAttribute() throws GuacamoleException {
|
||||
return environment.getProperty(
|
||||
LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE,
|
||||
"uid"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base DN under which all Guacamole users will be stored
|
||||
* within the LDAP directory.
|
||||
*
|
||||
* @return
|
||||
* The base DN under which all Guacamole users will be stored within
|
||||
* the LDAP directory.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if the user base DN
|
||||
* property is not specified.
|
||||
*/
|
||||
public String getUserBaseDN() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(
|
||||
LDAPGuacamoleProperties.LDAP_USER_BASE_DN
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base DN under which all Guacamole configurations
|
||||
* (connections) will be stored within the LDAP directory.
|
||||
*
|
||||
* @return
|
||||
* The base DN under which all Guacamole configurations will be stored
|
||||
* within the LDAP directory.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if the configuration
|
||||
* base DN property is not specified.
|
||||
*/
|
||||
public String getConfigurationBaseDN() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(
|
||||
LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap;
|
||||
|
||||
/**
|
||||
* Service for escaping LDAP filters, distinguished names (DN's), etc.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class EscapingService {
|
||||
|
||||
/**
|
||||
* Escapes the given string for use within an LDAP search filter. This
|
||||
* implementation is provided courtesy of OWASP:
|
||||
*
|
||||
* https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
|
||||
*
|
||||
* @param filter
|
||||
* The string to escape such that it has no special meaning within an
|
||||
* LDAP search filter.
|
||||
*
|
||||
* @return
|
||||
* The escaped string, safe for use within an LDAP search filter.
|
||||
*/
|
||||
public String escapeLDAPSearchFilter(String filter) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < filter.length(); i++) {
|
||||
char curChar = filter.charAt(i);
|
||||
switch (curChar) {
|
||||
case '\\':
|
||||
sb.append("\\5c");
|
||||
break;
|
||||
case '*':
|
||||
sb.append("\\2a");
|
||||
break;
|
||||
case '(':
|
||||
sb.append("\\28");
|
||||
break;
|
||||
case ')':
|
||||
sb.append("\\29");
|
||||
break;
|
||||
case '\u0000':
|
||||
sb.append("\\00");
|
||||
break;
|
||||
default:
|
||||
sb.append(curChar);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the given string such that it is safe for use within an LDAP
|
||||
* distinguished name (DN). This implementation is provided courtesy of
|
||||
* OWASP:
|
||||
*
|
||||
* https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
|
||||
*
|
||||
* @param name
|
||||
* The string to escape such that it has no special meaning within an
|
||||
* LDAP DN.
|
||||
*
|
||||
* @return
|
||||
* The escaped string, safe for use within an LDAP DN.
|
||||
*/
|
||||
public String escapeDN(String name) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if ((name.length() > 0) && ((name.charAt(0) == ' ') || (name.charAt(0) == '#'))) {
|
||||
sb.append('\\'); // add the leading backslash if needed
|
||||
}
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char curChar = name.charAt(i);
|
||||
switch (curChar) {
|
||||
case '\\':
|
||||
sb.append("\\\\");
|
||||
break;
|
||||
case ',':
|
||||
sb.append("\\,");
|
||||
break;
|
||||
case '+':
|
||||
sb.append("\\+");
|
||||
break;
|
||||
case '"':
|
||||
sb.append("\\\"");
|
||||
break;
|
||||
case '<':
|
||||
sb.append("\\<");
|
||||
break;
|
||||
case '>':
|
||||
sb.append("\\>");
|
||||
break;
|
||||
case ';':
|
||||
sb.append("\\;");
|
||||
break;
|
||||
default:
|
||||
sb.append(curChar);
|
||||
}
|
||||
}
|
||||
if ((name.length() > 1) && (name.charAt(name.length() - 1) == ' ')) {
|
||||
sb.insert(sb.length() - 1, '\\'); // add the trailing backslash if needed
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import org.glyptodon.guacamole.auth.ldap.connection.ConnectionService;
|
||||
import org.glyptodon.guacamole.auth.ldap.user.UserService;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.environment.Environment;
|
||||
import org.glyptodon.guacamole.environment.LocalEnvironment;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
|
||||
/**
|
||||
* Guice module which configures LDAP-specific injections.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class LDAPAuthenticationProviderModule extends AbstractModule {
|
||||
|
||||
/**
|
||||
* Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* A reference to the LDAPAuthenticationProvider on behalf of which this
|
||||
* module has configured injection.
|
||||
*/
|
||||
private final AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* Creates a new LDAP authentication provider module which configures
|
||||
* injection for the LDAPAuthenticationProvider.
|
||||
*
|
||||
* @param authProvider
|
||||
* The AuthenticationProvider for which injection is being configured.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the Guacamole server
|
||||
* environment.
|
||||
*/
|
||||
public LDAPAuthenticationProviderModule(AuthenticationProvider authProvider)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get local environment
|
||||
this.environment = new LocalEnvironment();
|
||||
|
||||
// Store associated auth provider
|
||||
this.authProvider = authProvider;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
// Bind core implementations of guacamole-ext classes
|
||||
bind(AuthenticationProvider.class).toInstance(authProvider);
|
||||
bind(Environment.class).toInstance(environment);
|
||||
|
||||
// Bind LDAP-specific services
|
||||
bind(ConfigurationService.class);
|
||||
bind(ConnectionService.class);
|
||||
bind(EscapingService.class);
|
||||
bind(UserService.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,7 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package net.sourceforge.guacamole.net.auth.ldap.properties;
|
||||
package org.glyptodon.guacamole.auth.ldap;
|
||||
|
||||
import org.glyptodon.guacamole.properties.IntegerGuacamoleProperty;
|
||||
import org.glyptodon.guacamole.properties.StringGuacamoleProperty;
|
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap.connection;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.novell.ldap.LDAPAttribute;
|
||||
import com.novell.ldap.LDAPConnection;
|
||||
import com.novell.ldap.LDAPEntry;
|
||||
import com.novell.ldap.LDAPException;
|
||||
import com.novell.ldap.LDAPSearchResults;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.glyptodon.guacamole.auth.ldap.ConfigurationService;
|
||||
import org.glyptodon.guacamole.auth.ldap.EscapingService;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleServerException;
|
||||
import org.glyptodon.guacamole.net.auth.Connection;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleConnection;
|
||||
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service for querying the connections available to a particular Guacamole
|
||||
* user according to an LDAP directory.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class ConnectionService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
|
||||
|
||||
/**
|
||||
* Service for escaping parts of LDAP queries.
|
||||
*/
|
||||
@Inject
|
||||
private EscapingService escapingService;
|
||||
|
||||
/**
|
||||
* Service for retrieving LDAP server configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Returns all Guacamole connections 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 connections accessible to the user currently bound under the
|
||||
* given LDAP connection, as a map of connection identifier to
|
||||
* corresponding connection object.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs preventing retrieval of connections.
|
||||
*/
|
||||
public Map<String, Connection> getConnections(LDAPConnection ldapConnection)
|
||||
throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Pull the current user DN from the LDAP connection
|
||||
String userDN = ldapConnection.getAuthenticationDN();
|
||||
|
||||
// Find all Guacamole connections for the given user
|
||||
LDAPSearchResults results = ldapConnection.search(
|
||||
confService.getConfigurationBaseDN(),
|
||||
LDAPConnection.SCOPE_SUB,
|
||||
"(&(objectClass=guacConfigGroup)(member=" + escapingService.escapeLDAPSearchFilter(userDN) + "))",
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// Produce connections for each readable configuration
|
||||
Map<String, Connection> connections = new HashMap<String, Connection>();
|
||||
while (results.hasMore()) {
|
||||
|
||||
LDAPEntry entry = results.next();
|
||||
|
||||
// Get common name (CN)
|
||||
LDAPAttribute cn = entry.getAttribute("cn");
|
||||
if (cn == null) {
|
||||
logger.warn("guacConfigGroup is missing a cn.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get associated protocol
|
||||
LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol");
|
||||
if (protocol == null) {
|
||||
logger.warn("guacConfigGroup \"{}\" is missing the "
|
||||
+ "required \"guacConfigProtocol\" attribute.",
|
||||
cn.getStringValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set protocol
|
||||
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||
config.setProtocol(protocol.getStringValue());
|
||||
|
||||
// Get parameters, if any
|
||||
LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter");
|
||||
if (parameterAttribute != null) {
|
||||
|
||||
// For each parameter
|
||||
Enumeration<?> parameters = parameterAttribute.getStringValues();
|
||||
while (parameters.hasMoreElements()) {
|
||||
|
||||
String parameter = (String) parameters.nextElement();
|
||||
|
||||
// Parse parameter
|
||||
int equals = parameter.indexOf('=');
|
||||
if (equals != -1) {
|
||||
|
||||
// Parse name
|
||||
String name = parameter.substring(0, equals);
|
||||
String value = parameter.substring(equals+1);
|
||||
|
||||
config.setParameter(name, value);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Store connection using cn for both identifier and name
|
||||
String name = cn.getStringValue();
|
||||
connections.put(name, new SimpleConnection(name, name, config));
|
||||
|
||||
}
|
||||
|
||||
// Return map of all connections
|
||||
return connections;
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
throw new GuacamoleServerException("Error while querying for connections.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap.user;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.glyptodon.guacamole.net.auth.AbstractAuthenticatedUser;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Credentials;
|
||||
|
||||
/**
|
||||
* An LDAP-specific implementation of AuthenticatedUser, associating a
|
||||
* particular set of credentials with the LDAP authentication provider.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class AuthenticatedUser extends AbstractAuthenticatedUser {
|
||||
|
||||
/**
|
||||
* Reference to the authentication provider associated with this
|
||||
* authenticated user.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* The credentials provided when this user was authenticated.
|
||||
*/
|
||||
private Credentials credentials;
|
||||
|
||||
/**
|
||||
* Initializes this AuthenticatedUser using the given credentials.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials provided when this user was authenticated.
|
||||
*/
|
||||
public void init(Credentials credentials) {
|
||||
this.credentials = credentials;
|
||||
setIdentifier(credentials.getUsername());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Credentials getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap.user;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.novell.ldap.LDAPConnection;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.glyptodon.guacamole.auth.ldap.connection.ConnectionService;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.form.Form;
|
||||
import org.glyptodon.guacamole.net.auth.ActiveConnection;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.glyptodon.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.glyptodon.guacamole.net.auth.Connection;
|
||||
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.Directory;
|
||||
import org.glyptodon.guacamole.net.auth.User;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleConnectionGroup;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleConnectionGroupDirectory;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleDirectory;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleUser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* An LDAP-specific implementation of UserContext which queries all Guacamole
|
||||
* connections and users from the LDAP directory.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class UserContext implements org.glyptodon.guacamole.net.auth.UserContext {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(UserContext.class);
|
||||
|
||||
/**
|
||||
* The identifier reserved for the root connection group.
|
||||
*/
|
||||
private static final String ROOT_CONNECTION_GROUP = "ROOT";
|
||||
|
||||
/**
|
||||
* Service for retrieving Guacamole connections from the LDAP server.
|
||||
*/
|
||||
@Inject
|
||||
private ConnectionService connectionService;
|
||||
|
||||
/**
|
||||
* Service for retrieving Guacamole users from the LDAP server.
|
||||
*/
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* Reference to the AuthenticationProvider associated with this
|
||||
* UserContext.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* Reference to a User object representing the user whose access level
|
||||
* dictates the users and connections visible through this UserContext.
|
||||
*/
|
||||
private User self;
|
||||
|
||||
/**
|
||||
* Directory containing all User objects accessible to the user associated
|
||||
* with this UserContext.
|
||||
*/
|
||||
private Directory<User> userDirectory;
|
||||
|
||||
/**
|
||||
* Directory containing all Connection objects accessible to the user
|
||||
* associated with this UserContext.
|
||||
*/
|
||||
private Directory<Connection> connectionDirectory;
|
||||
|
||||
/**
|
||||
* Directory containing all ConnectionGroup objects accessible to the user
|
||||
* associated with this UserContext.
|
||||
*/
|
||||
private Directory<ConnectionGroup> connectionGroupDirectory;
|
||||
|
||||
/**
|
||||
* Reference to the root connection group.
|
||||
*/
|
||||
private ConnectionGroup rootGroup;
|
||||
|
||||
/**
|
||||
* Initializes this UserContext using the provided AuthenticatedUser and
|
||||
* LDAPConnection.
|
||||
*
|
||||
* @param user
|
||||
* The AuthenticatedUser representing the user that authenticated. This
|
||||
* user may have been authenticated by a different authentication
|
||||
* provider (not LDAP).
|
||||
*
|
||||
* @param ldapConnection
|
||||
* The connection to the LDAP server to use when querying accessible
|
||||
* Guacamole users and connections.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If associated data stored within the LDAP directory cannot be
|
||||
* queried due to an error.
|
||||
*/
|
||||
public void init(AuthenticatedUser user, LDAPConnection ldapConnection)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Query all accessible users
|
||||
userDirectory = new SimpleDirectory<User>(
|
||||
userService.getUsers(ldapConnection)
|
||||
);
|
||||
|
||||
// Query all accessible connections
|
||||
connectionDirectory = new SimpleDirectory<Connection>(
|
||||
connectionService.getConnections(ldapConnection)
|
||||
);
|
||||
|
||||
// Root group contains only connections
|
||||
rootGroup = new SimpleConnectionGroup(
|
||||
ROOT_CONNECTION_GROUP, ROOT_CONNECTION_GROUP,
|
||||
connectionDirectory.getIdentifiers(),
|
||||
Collections.<String>emptyList()
|
||||
);
|
||||
|
||||
// Expose only the root group in the connection group directory
|
||||
connectionGroupDirectory = new SimpleConnectionGroupDirectory(Collections.singleton(rootGroup));
|
||||
|
||||
// Init self with basic permissions
|
||||
self = new SimpleUser(
|
||||
user.getIdentifier(),
|
||||
userDirectory.getIdentifiers(),
|
||||
connectionDirectory.getIdentifiers(),
|
||||
connectionGroupDirectory.getIdentifiers()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public User self() {
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<User> getUserDirectory() throws GuacamoleException {
|
||||
return userDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<Connection> getConnectionDirectory()
|
||||
throws GuacamoleException {
|
||||
return connectionDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<ConnectionGroup> getConnectionGroupDirectory()
|
||||
throws GuacamoleException {
|
||||
return connectionGroupDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionGroup getRootConnectionGroup() throws GuacamoleException {
|
||||
return rootGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Directory<ActiveConnection> getActiveConnectionDirectory()
|
||||
throws GuacamoleException {
|
||||
return new SimpleDirectory<ActiveConnection>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Form> getUserAttributes() {
|
||||
return Collections.<Form>emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Form> getConnectionAttributes() {
|
||||
return Collections.<Form>emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Form> getConnectionGroupAttributes() {
|
||||
return Collections.<Form>emptyList();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.glyptodon.guacamole.auth.ldap.user;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.novell.ldap.LDAPAttribute;
|
||||
import com.novell.ldap.LDAPConnection;
|
||||
import com.novell.ldap.LDAPEntry;
|
||||
import com.novell.ldap.LDAPException;
|
||||
import com.novell.ldap.LDAPSearchResults;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.glyptodon.guacamole.auth.ldap.ConfigurationService;
|
||||
import org.glyptodon.guacamole.auth.ldap.EscapingService;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleServerException;
|
||||
import org.glyptodon.guacamole.net.auth.User;
|
||||
import org.glyptodon.guacamole.net.auth.simple.SimpleUser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service for queries the users visible to a particular Guacamole user
|
||||
* according to an LDAP directory.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class UserService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
/**
|
||||
* Service for escaping parts of LDAP queries.
|
||||
*/
|
||||
@Inject
|
||||
private EscapingService escapingService;
|
||||
|
||||
/**
|
||||
* Service for retrieving LDAP server configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Returns all Guacamole users 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 users accessible to the user currently bound under the given
|
||||
* LDAP connection, as a map of connection identifier to corresponding
|
||||
* user object.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs preventing retrieval of users.
|
||||
*/
|
||||
public Map<String, User> getUsers(LDAPConnection ldapConnection)
|
||||
throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Get username attribute
|
||||
String usernameAttribute = confService.getUsernameAttribute();
|
||||
|
||||
// Find all Guacamole users underneath base DN
|
||||
LDAPSearchResults results = ldapConnection.search(
|
||||
confService.getUserBaseDN(),
|
||||
LDAPConnection.SCOPE_ONE,
|
||||
"(&(objectClass=*)(" + escapingService.escapeLDAPSearchFilter(usernameAttribute) + "=*))",
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
// Read all visible users
|
||||
Map<String, User> users = new HashMap<String, User>();
|
||||
while (results.hasMore()) {
|
||||
|
||||
LDAPEntry entry = results.next();
|
||||
|
||||
// Get common name (CN)
|
||||
LDAPAttribute username = entry.getAttribute(usernameAttribute);
|
||||
if (username == null) {
|
||||
logger.warn("Queried user is missing the username attribute \"{}\".", usernameAttribute);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store connection using cn for both identifier and name
|
||||
String identifier = username.getStringValue();
|
||||
users.put(identifier, new SimpleUser(identifier));
|
||||
|
||||
}
|
||||
|
||||
// Return map of all connections
|
||||
return users;
|
||||
|
||||
}
|
||||
catch (LDAPException e) {
|
||||
throw new GuacamoleServerException("Error while querying users.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Glyptodon LLC
|
||||
* Copyright (C) 2015 Glyptodon LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -28,7 +28,6 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.GuacamoleSecurityException;
|
||||
import org.glyptodon.guacamole.net.auth.AbstractUser;
|
||||
import org.glyptodon.guacamole.net.auth.permission.ObjectPermission;
|
||||
import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet;
|
||||
@@ -41,6 +40,12 @@ import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet;
|
||||
*/
|
||||
public class SimpleUser extends AbstractUser {
|
||||
|
||||
/**
|
||||
* All connection permissions granted to this user.
|
||||
*/
|
||||
private final Set<ObjectPermission> userPermissions =
|
||||
new HashSet<ObjectPermission>();
|
||||
|
||||
/**
|
||||
* All connection permissions granted to this user.
|
||||
*/
|
||||
@@ -59,6 +64,19 @@ public class SimpleUser extends AbstractUser {
|
||||
public SimpleUser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SimpleUser having the given username and no permissions.
|
||||
*
|
||||
* @param username
|
||||
* The username to assign to this SimpleUser.
|
||||
*/
|
||||
public SimpleUser(String username) {
|
||||
|
||||
// Set username
|
||||
setIdentifier(username);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new READ permission to the given set of permissions for each of
|
||||
* the given identifiers.
|
||||
@@ -89,6 +107,7 @@ public class SimpleUser extends AbstractUser {
|
||||
*
|
||||
* @param username
|
||||
* The username to assign to this SimpleUser.
|
||||
*
|
||||
* @param connectionIdentifiers
|
||||
* The identifiers of all connections this user has READ access to.
|
||||
*
|
||||
@@ -100,8 +119,7 @@ public class SimpleUser extends AbstractUser {
|
||||
Collection<String> connectionIdentifiers,
|
||||
Collection<String> connectionGroupIdentifiers) {
|
||||
|
||||
// Set username
|
||||
setIdentifier(username);
|
||||
this(username);
|
||||
|
||||
// Add permissions
|
||||
addReadPermissions(connectionPermissions, connectionIdentifiers);
|
||||
@@ -109,6 +127,37 @@ public class SimpleUser extends AbstractUser {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SimpleUser having the given username and READ access to
|
||||
* the users, connections, and groups having the given identifiers.
|
||||
*
|
||||
* @param username
|
||||
* The username to assign to this SimpleUser.
|
||||
*
|
||||
* @param userIdentifiers
|
||||
* The identifiers of all users this user has READ access to.
|
||||
*
|
||||
* @param connectionIdentifiers
|
||||
* The identifiers of all connections this user has READ access to.
|
||||
*
|
||||
* @param connectionGroupIdentifiers
|
||||
* The identifiers of all connection groups this user has READ access
|
||||
* to.
|
||||
*/
|
||||
public SimpleUser(String username,
|
||||
Collection<String> userIdentifiers,
|
||||
Collection<String> connectionIdentifiers,
|
||||
Collection<String> connectionGroupIdentifiers) {
|
||||
|
||||
this(username);
|
||||
|
||||
// Add permissions
|
||||
addReadPermissions(userPermissions, userIdentifiers);
|
||||
addReadPermissions(connectionPermissions, connectionIdentifiers);
|
||||
addReadPermissions(connectionGroupPermissions, connectionGroupIdentifiers);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.<String, String>emptyMap();
|
||||
@@ -140,7 +189,7 @@ public class SimpleUser extends AbstractUser {
|
||||
@Override
|
||||
public ObjectPermissionSet getUserPermissions()
|
||||
throws GuacamoleException {
|
||||
return new SimpleObjectPermissionSet();
|
||||
return new SimpleObjectPermissionSet(userPermissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Reference in New Issue
Block a user