From 1210d5624c4eb173417cab8358eca4cc3b6c0ebe Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 4 Oct 2018 00:41:07 -0700 Subject: [PATCH 1/6] GUACAMOLE-524: Deprecate and replace StandardTokens with arbitrary tokens provided to Connectable.connect(). --- .../jdbc/connection/ConnectionService.java | 10 +- .../jdbc/connection/ModeledConnection.java | 5 +- .../ConnectionGroupService.java | 11 +- .../ModeledConnectionGroup.java | 6 +- .../connectiongroup/RootConnectionGroup.java | 4 +- .../sharing/connection/SharedConnection.java | 6 +- .../SharedRootConnectionGroup.java | 4 +- .../AbstractGuacamoleTunnelService.java | 45 +++--- .../jdbc/tunnel/GuacamoleTunnelService.java | 21 ++- .../ldap/connection/ConnectionService.java | 9 -- .../quickconnect/QuickConnectionGroup.java | 4 +- .../guacamole/net/auth/Connectable.java | 17 ++- .../net/auth/DelegatingConnection.java | 6 +- .../net/auth/DelegatingConnectionGroup.java | 5 +- .../simple/SimpleAuthenticationProvider.java | 77 +--------- .../net/auth/simple/SimpleConnection.java | 20 ++- .../auth/simple/SimpleConnectionGroup.java | 4 +- .../guacamole/token/StandardTokens.java | 5 + .../rest/connection/APIConnectionWrapper.java | 3 +- .../APIConnectionGroupWrapper.java | 3 +- .../guacamole/tunnel/StandardTokenMap.java | 139 ++++++++++++++++++ .../tunnel/TunnelRequestService.java | 18 ++- 22 files changed, 268 insertions(+), 154 deletions(-) create mode 100644 guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java index 8dcf6f59a..e2f3c1523 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ConnectionService.java @@ -499,6 +499,10 @@ public class ConnectionService extends ModeledChildDirectoryObjectService tokens) throws GuacamoleException { // Connect only if READ permission is granted if (hasObjectPermission(user, connection.getIdentifier(), ObjectPermission.Type.READ)) - return tunnelService.getGuacamoleTunnel(user, connection, info); + return tunnelService.getGuacamoleTunnel(user, connection, info, tokens); // The user does not have permission to connect throw new GuacamoleSecurityException("Permission denied."); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java index 660212cdc..b49262668 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connection/ModeledConnection.java @@ -259,8 +259,9 @@ public class ModeledConnection extends ModeledChildDirectoryObject tokens) throws GuacamoleException { + return connectionService.connect(getCurrentUser(), this, info, tokens); } @Override diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java index 01119b92e..3e9ec72a8 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ConnectionGroupService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.jdbc.connectiongroup; import com.google.inject.Inject; import com.google.inject.Provider; +import java.util.Map; import java.util.Set; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper; @@ -243,6 +244,10 @@ public class ConnectionGroupService extends ModeledChildDirectoryObjectService tokens) throws GuacamoleException { // Connect only if READ permission is granted if (hasObjectPermission(user, connectionGroup.getIdentifier(), ObjectPermission.Type.READ)) - return tunnelService.getGuacamoleTunnel(user, connectionGroup, info); + return tunnelService.getGuacamoleTunnel(user, connectionGroup, info, tokens); // The user does not have permission to connect throw new GuacamoleSecurityException("Permission denied."); diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java index 3aac52d59..bcf457af0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/ModeledConnectionGroup.java @@ -135,9 +135,9 @@ public class ModeledConnectionGroup extends ModeledChildDirectoryObject tokens) throws GuacamoleException { + return connectionGroupService.connect(getCurrentUser(), this, info, tokens); } @Override diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/RootConnectionGroup.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/RootConnectionGroup.java index d2e555132..08b32fd8f 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/RootConnectionGroup.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/connectiongroup/RootConnectionGroup.java @@ -122,8 +122,8 @@ public class RootConnectionGroup extends RestrictedObject } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { throw new GuacamoleSecurityException("Permission denied."); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java index 5483d0267..cf0083167 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connection/SharedConnection.java @@ -131,9 +131,9 @@ public class SharedConnection implements Connection { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { - return tunnelService.getGuacamoleTunnel(user, definition, info); + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + return tunnelService.getGuacamoleTunnel(user, definition, info, tokens); } @Override diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connectiongroup/SharedRootConnectionGroup.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connectiongroup/SharedRootConnectionGroup.java index 71b997c25..33d9ca7e3 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connectiongroup/SharedRootConnectionGroup.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/sharing/connectiongroup/SharedRootConnectionGroup.java @@ -98,8 +98,8 @@ public class SharedRootConnectionGroup implements ConnectionGroup { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { throw new GuacamoleSecurityException("Permission denied."); } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java index 5f7fc1ba3..20ac29995 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/AbstractGuacamoleTunnelService.java @@ -52,7 +52,6 @@ import org.apache.guacamole.net.auth.ConnectionGroup; import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket; import org.apache.guacamole.protocol.GuacamoleClientInformation; import org.apache.guacamole.protocol.GuacamoleConfiguration; -import org.apache.guacamole.token.StandardTokens; import org.apache.guacamole.token.TokenFilter; import org.mybatis.guice.transactional.Transactional; import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterMapper; @@ -233,13 +232,6 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS for (ConnectionParameterModel parameter : parameters) config.setParameter(parameter.getName(), parameter.getValue()); - // Build token filter containing credential tokens - TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user); - - // Filter the configuration - tokenFilter.filterValues(config.getParameters()); - return config; } @@ -279,13 +271,6 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS for (SharingProfileParameterModel parameter : parameters) config.setParameter(parameter.getName(), parameter.getValue()); - // Build token filter containing credential tokens - TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user); - - // Filter the configuration - tokenFilter.filterValues(config.getParameters()); - return config; } @@ -454,6 +439,10 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * Information describing the Guacamole client connecting to the given * connection. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. + * * @param interceptErrors * Whether errors from the upstream remote desktop should be * intercepted and rethrown as GuacamoleUpstreamExceptions. @@ -467,7 +456,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS * while connection configuration information is being retrieved. */ private GuacamoleTunnel assignGuacamoleTunnel(ActiveConnectionRecord activeConnection, - GuacamoleClientInformation info, boolean interceptErrors) throws GuacamoleException { + GuacamoleClientInformation info, Map tokens, + boolean interceptErrors) throws GuacamoleException { // Record new active connection Runnable cleanupTask = new ConnectionCleanupTask(activeConnection); @@ -504,6 +494,13 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS } + // Build token filter containing credential tokens + TokenFilter tokenFilter = new TokenFilter(); + tokenFilter.setTokens(tokens); + + // Filter the configuration + tokenFilter.filterValues(config.getParameters()); + // Obtain socket which will automatically run the cleanup task ConfiguredGuacamoleSocket socket = new ConfiguredGuacamoleSocket( getUnconfiguredGuacamoleSocket(connection.getGuacamoleProxyConfiguration(), @@ -651,8 +648,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS @Override @Transactional public GuacamoleTunnel getGuacamoleTunnel(final ModeledAuthenticatedUser user, - final ModeledConnection connection, GuacamoleClientInformation info) - throws GuacamoleException { + final ModeledConnection connection, GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { // Acquire access to single connection, ignoring the failover-only flag acquire(user, Collections.singletonList(connection), true); @@ -660,7 +657,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS // Connect only if the connection was successfully acquired ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get(); connectionRecord.init(user, connection); - return assignGuacamoleTunnel(connectionRecord, info, false); + return assignGuacamoleTunnel(connectionRecord, info, tokens, false); } @@ -673,7 +670,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS @Transactional public GuacamoleTunnel getGuacamoleTunnel(ModeledAuthenticatedUser user, ModeledConnectionGroup connectionGroup, - GuacamoleClientInformation info) throws GuacamoleException { + GuacamoleClientInformation info, Map tokens) + throws GuacamoleException { // Track failures in upstream (remote desktop) connections boolean upstreamHasFailed = false; @@ -706,7 +704,8 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS // Connect to acquired child ActiveConnectionRecord connectionRecord = activeConnectionRecordProvider.get(); connectionRecord.init(user, connectionGroup, connection); - GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, connections.size() > 1); + GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, + info, tokens, connections.size() > 1); // If session affinity is enabled, prefer this connection going forward if (connectionGroup.isSessionAffinityEnabled()) @@ -755,7 +754,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS @Transactional public GuacamoleTunnel getGuacamoleTunnel(RemoteAuthenticatedUser user, SharedConnectionDefinition definition, - GuacamoleClientInformation info) + GuacamoleClientInformation info, Map tokens) throws GuacamoleException { // Create a connection record which describes the shared connection @@ -764,7 +763,7 @@ public abstract class AbstractGuacamoleTunnelService implements GuacamoleTunnelS definition.getSharingProfile()); // Connect to shared connection described by the created record - GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, false); + GuacamoleTunnel tunnel = assignGuacamoleTunnel(connectionRecord, info, tokens, false); // Register tunnel, such that it is closed when the // SharedConnectionDefinition is invalidated diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java index 34d929340..bad521951 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/tunnel/GuacamoleTunnelService.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.jdbc.tunnel; import java.util.Collection; +import java.util.Map; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.auth.jdbc.connection.ModeledConnection; import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup; @@ -73,6 +74,10 @@ public interface GuacamoleTunnelService { * Information describing the Guacamole client connecting to the given * connection. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. + * * @return * A new GuacamoleTunnel which is configured and connected to the given * connection. @@ -82,8 +87,8 @@ public interface GuacamoleTunnelService { * rules. */ GuacamoleTunnel getGuacamoleTunnel(ModeledAuthenticatedUser user, - ModeledConnection connection, GuacamoleClientInformation info) - throws GuacamoleException; + ModeledConnection connection, GuacamoleClientInformation info, + Map tokens) throws GuacamoleException; /** * Returns a collection containing connection records representing all @@ -117,6 +122,10 @@ public interface GuacamoleTunnelService { * Information describing the Guacamole client connecting to the given * connection group. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. + * * @return * A new GuacamoleTunnel which is configured and connected to the given * connection group. @@ -127,7 +136,7 @@ public interface GuacamoleTunnelService { */ GuacamoleTunnel getGuacamoleTunnel(ModeledAuthenticatedUser user, ModeledConnectionGroup connectionGroup, - GuacamoleClientInformation info) + GuacamoleClientInformation info, Map tokens) throws GuacamoleException; /** @@ -163,6 +172,10 @@ public interface GuacamoleTunnelService { * Information describing the Guacamole client connecting to the given * connection. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. + * * @return * A new GuacamoleTunnel which is configured and connected to the given * active connection. @@ -173,7 +186,7 @@ public interface GuacamoleTunnelService { */ GuacamoleTunnel getGuacamoleTunnel(RemoteAuthenticatedUser user, SharedConnectionDefinition definition, - GuacamoleClientInformation info) + GuacamoleClientInformation info, Map tokens) throws GuacamoleException; } 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 984e77211..18e3b9ceb 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 @@ -39,8 +39,6 @@ import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; import org.apache.guacamole.net.auth.simple.SimpleConnection; import org.apache.guacamole.protocol.GuacamoleConfiguration; -import org.apache.guacamole.token.StandardTokens; -import org.apache.guacamole.token.TokenFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,10 +120,6 @@ public class ConnectionService { confService.getLDAPSearchConstraints() ); - // Build token filter containing credential tokens - TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, user); - // Produce connections for each readable configuration Map connections = new HashMap(); while (results.hasMore()) { @@ -180,9 +174,6 @@ public class ConnectionService { } - // 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); diff --git a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectionGroup.java b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectionGroup.java index cbce37976..dbb693467 100644 --- a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectionGroup.java +++ b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectionGroup.java @@ -108,8 +108,8 @@ public class QuickConnectionGroup extends AbstractConnectionGroup { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { // This group does not support connections throw new GuacamoleSecurityException("Permission denied."); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connectable.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connectable.java index 7face9264..39b12f98f 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connectable.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/Connectable.java @@ -19,6 +19,7 @@ package org.apache.guacamole.net.auth; +import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.protocol.GuacamoleClientInformation; @@ -31,11 +32,21 @@ public interface Connectable { /** * Establishes a connection to guacd using the information associated with * this object. The connection will be provided the given client - * information. + * information. Implementations which support parameter tokens should + * apply the given tokens when configuring the connection, such as with a + * {@link org.apache.guacamole.token.TokenFilter}. + * + * @see Parameter Tokens * * @param info * Information associated with the connecting client. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. If the + * implementation does not support parameter tokens, this Map may be + * ignored. + * * @return * A fully-established GuacamoleTunnel. * @@ -43,8 +54,8 @@ public interface Connectable { * If an error occurs while connecting to guacd, or if permission to * connect is denied. */ - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException; + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException; /** * Returns the number of active connections associated with this object. diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnection.java index fa1ab7849..b80e8683a 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnection.java @@ -128,9 +128,9 @@ public class DelegatingConnection implements Connection { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { - return connection.connect(info); + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + return connection.connect(info, tokens); } @Override diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnectionGroup.java index db647d617..1d958bdb2 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnectionGroup.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/DelegatingConnectionGroup.java @@ -119,8 +119,9 @@ public class DelegatingConnectionGroup implements ConnectionGroup { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException { - return connectionGroup.connect(info); + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + return connectionGroup.connect(info, tokens); } @Override diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java index 7b1e3e7f0..6d08d99ac 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleAuthenticationProvider.java @@ -31,8 +31,6 @@ import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.protocol.GuacamoleConfiguration; -import org.apache.guacamole.token.StandardTokens; -import org.apache.guacamole.token.TokenFilter; /** * Provides means of retrieving a set of named GuacamoleConfigurations for a @@ -140,84 +138,13 @@ public abstract class SimpleAuthenticationProvider } - /** - * Given an arbitrary credentials object, returns a Map containing all - * configurations authorized by those credentials, filtering those - * configurations using a TokenFilter and the standard credential tokens - * (like ${GUAC_USERNAME} and ${GUAC_PASSWORD}). The keys of this Map - * are Strings which uniquely identify each configuration. - * - * @param credentials - * The credentials to use to retrieve authorized configurations. - * - * @return - * A Map of all configurations authorized by the given credentials, or - * null if the credentials given are not authorized. - * - * @throws GuacamoleException - * If an error occurs while retrieving configurations. - */ - private Map - getFilteredAuthorizedConfigurations(Credentials credentials) - throws GuacamoleException { - - // Get configurations - Map configs = - getAuthorizedConfigurations(credentials); - - // Return as unauthorized if not authorized to retrieve configs - if (configs == null) - return null; - - // Build credential TokenFilter - TokenFilter tokenFilter = new TokenFilter(); - StandardTokens.addStandardTokens(tokenFilter, credentials); - - // Filter each configuration - for (GuacamoleConfiguration config : configs.values()) - tokenFilter.filterValues(config.getParameters()); - - return configs; - - } - - /** - * Given a user who has already been authenticated, returns a Map - * containing all configurations for which that user is authorized, - * filtering those configurations using a TokenFilter and the standard - * credential tokens (like ${GUAC_USERNAME} and ${GUAC_PASSWORD}). The keys - * of this Map are Strings which uniquely identify each configuration. - * - * @param authenticatedUser - * The user whose authorized configurations are to be retrieved. - * - * @return - * A Map of all configurations authorized for use by the given user, or - * null if the user is not authorized to use any configurations. - * - * @throws GuacamoleException - * If an error occurs while retrieving configurations. - */ - private Map - getFilteredAuthorizedConfigurations(AuthenticatedUser authenticatedUser) - throws GuacamoleException { - - // Pull cached configurations, if any - if (authenticatedUser instanceof SimpleAuthenticatedUser && authenticatedUser.getAuthenticationProvider() == this) - return ((SimpleAuthenticatedUser) authenticatedUser).getAuthorizedConfigurations(); - - // Otherwise, pull using credentials - return getFilteredAuthorizedConfigurations(authenticatedUser.getCredentials()); - - } - @Override public AuthenticatedUser authenticateUser(final Credentials credentials) throws GuacamoleException { // Get configurations Map configs = - getFilteredAuthorizedConfigurations(credentials); + getAuthorizedConfigurations(credentials); // Return as unauthorized if not authorized to retrieve configs if (configs == null) @@ -233,7 +160,7 @@ public abstract class SimpleAuthenticationProvider // Get configurations Map configs = - getFilteredAuthorizedConfigurations(authenticatedUser); + getAuthorizedConfigurations(authenticatedUser.getCredentials()); // Return as unauthorized if not authorized to retrieve configs if (configs == null) diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java index 85783a0a4..f9da240c4 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnection.java @@ -38,9 +38,14 @@ import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration; import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket; import org.apache.guacamole.protocol.GuacamoleClientInformation; import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.apache.guacamole.token.TokenFilter; /** - * An extremely basic Connection implementation. + * An extremely basic Connection implementation. The underlying connection to + * guacd is established using the configuration information provided in + * guacamole.properties. Parameter tokens provided to connect() are + * automatically applied. Tracking of active connections and connection history + * is not provided. */ public class SimpleConnection extends AbstractConnection { @@ -95,8 +100,8 @@ public class SimpleConnection extends AbstractConnection { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { // Retrieve proxy configuration from environment Environment environment = new LocalEnvironment(); @@ -106,6 +111,11 @@ public class SimpleConnection extends AbstractConnection { String hostname = proxyConfig.getHostname(); int port = proxyConfig.getPort(); + // Apply tokens to config parameters + GuacamoleConfiguration filteredConfig = new GuacamoleConfiguration(config); + TokenFilter tokenFilter = new TokenFilter(); + tokenFilter.filterValues(config.getParameters()); + GuacamoleSocket socket; // Determine socket type based on required encryption method @@ -115,7 +125,7 @@ public class SimpleConnection extends AbstractConnection { case SSL: socket = new ConfiguredGuacamoleSocket( new SSLGuacamoleSocket(hostname, port), - config, info + filteredConfig, info ); break; @@ -123,7 +133,7 @@ public class SimpleConnection extends AbstractConnection { case NONE: socket = new ConfiguredGuacamoleSocket( new InetGuacamoleSocket(hostname, port), - config, info + filteredConfig, info ); break; diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionGroup.java index 3a7df28c2..a077eb312 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionGroup.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/simple/SimpleConnectionGroup.java @@ -109,8 +109,8 @@ public class SimpleConnectionGroup extends AbstractConnectionGroup { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) - throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { throw new GuacamoleSecurityException("Permission denied."); } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java index 8faca158f..22a042e0c 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java @@ -29,7 +29,12 @@ import org.apache.guacamole.net.auth.Credentials; /** * Utility class which provides access to standardized token names, as well as * facilities for generating those tokens from common objects. + * + * @deprecated Standard tokens are now supplied by default to the connect() + * functions of connections and connection groups. Manually generating the + * standard tokens is not necessary. */ +@Deprecated public class StandardTokens { /** diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java index 3a987e544..704db2397 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connection/APIConnectionWrapper.java @@ -128,7 +128,8 @@ public class APIConnectionWrapper implements Connection { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { throw new UnsupportedOperationException("Operation not supported."); } diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java index 625b009d6..552b7871b 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/connectiongroup/APIConnectionGroupWrapper.java @@ -112,7 +112,8 @@ public class APIConnectionGroupWrapper implements ConnectionGroup { } @Override - public GuacamoleTunnel connect(GuacamoleClientInformation info) throws GuacamoleException { + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { throw new UnsupportedOperationException("Operation not supported."); } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java new file mode 100644 index 000000000..1d357d21b --- /dev/null +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java @@ -0,0 +1,139 @@ +/* + * 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.tunnel; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.apache.guacamole.net.auth.AuthenticatedUser; +import org.apache.guacamole.net.auth.Credentials; + +/** + * Map which is automatically populated with the name/value pairs of all + * standardized tokens available for a particular AuthenticatedUser. + */ +public class StandardTokenMap extends HashMap { + + /** + * The name of the token containing the user's username. + */ + public static final String USERNAME_TOKEN = "GUAC_USERNAME"; + + /** + * The name of the token containing the user's password. + */ + public static final String PASSWORD_TOKEN = "GUAC_PASSWORD"; + + /** + * The name of the token containing the hostname/address of the machine the + * user authenticated from. + */ + public static final String CLIENT_HOSTNAME_TOKEN = "GUAC_CLIENT_HOSTNAME"; + + /** + * The name of the token containing the IP address of the machine the user + * authenticated from. + */ + public static final String CLIENT_ADDRESS_TOKEN = "GUAC_CLIENT_ADDRESS"; + + /** + * The name of the token containing the current date (server-local time). + */ + public static final String DATE_TOKEN = "GUAC_DATE"; + + /** + * The name of the token containing the current time (server-local time). + */ + public static final String TIME_TOKEN = "GUAC_TIME"; + + /** + * The date format that should be used for the date token. This format must + * be compatible with Java's SimpleDateFormat. + */ + private static final String DATE_FORMAT = "yyyyMMdd"; + + /** + * The date format that should be used for the time token. This format must + * be compatible with Java's SimpleDateFormat. + */ + private static final String TIME_FORMAT = "HHmmss"; + + /** + * The prefix of the arbitrary attribute tokens. + */ + public static final String ATTR_TOKEN_PREFIX = "GUAC_ATTR_"; + + /** + * Creates a new StandardTokenMap which is pre-populated with the + * name/value pairs of all standardized tokens available for the given + * AuthenticatedUser. + * + * @param authenticatedUser + * The AuthenticatedUser to generate standard tokens for. + */ + public StandardTokenMap(AuthenticatedUser authenticatedUser) { + + // Add date/time tokens (server-local time) + Date currentTime = new Date(); + put(DATE_TOKEN, new SimpleDateFormat(DATE_FORMAT).format(currentTime)); + put(TIME_TOKEN, new SimpleDateFormat(TIME_FORMAT).format(currentTime)); + + Credentials credentials = authenticatedUser.getCredentials(); + Map attributes = authenticatedUser.getAttributes(); + + // Add username token + String username = credentials.getUsername(); + if (username != null) + put(USERNAME_TOKEN, username); + + // Default to the authenticated user's username for the GUAC_USERNAME + // token + else + put(USERNAME_TOKEN, authenticatedUser.getIdentifier()); + + // Add password token + String password = credentials.getPassword(); + if (password != null) + put(PASSWORD_TOKEN, password); + + // Add client hostname token + String hostname = credentials.getRemoteHostname(); + if (hostname != null) + put(CLIENT_HOSTNAME_TOKEN, hostname); + + // Add client address token + String address = credentials.getRemoteAddress(); + if (address != null) + put(CLIENT_ADDRESS_TOKEN, address); + + // Add tokens for all attributes on the AuthenticatedUser + if (attributes != null) { + for (Map.Entry entry : attributes.entrySet()) { + String key = entry.getKey().toString(); + String tokenName = ATTR_TOKEN_PREFIX + key.toUpperCase(); + String tokenValue = entry.getValue().toString(); + put(tokenName, tokenValue); + } + } + + } + +} diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index e023a706f..1479d8243 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -22,6 +22,7 @@ package org.apache.guacamole.tunnel; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.List; +import java.util.Map; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleSession; @@ -187,6 +188,10 @@ public class TunnelRequestService { * @param info * Information describing the connected Guacamole client. * + * @param tokens + * A Map containing the token names and corresponding values to be + * applied as parameter tokens when establishing the connection. + * * @return * A new tunnel, connected as required by the request. * @@ -195,7 +200,7 @@ public class TunnelRequestService { */ protected GuacamoleTunnel createConnectedTunnel(UserContext context, final TunnelRequest.Type type, String id, - GuacamoleClientInformation info) + GuacamoleClientInformation info, Map tokens) throws GuacamoleException { // Create connected tunnel from identifier @@ -216,7 +221,7 @@ public class TunnelRequestService { } // Connect tunnel - tunnel = connection.connect(info); + tunnel = connection.connect(info, tokens); logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id); break; } @@ -235,7 +240,7 @@ public class TunnelRequestService { } // Connect tunnel - tunnel = group.connect(info); + tunnel = group.connect(info, tokens); logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id); break; } @@ -385,16 +390,17 @@ public class TunnelRequestService { GuacamoleClientInformation info = getClientInformation(request); GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); + AuthenticatedUser authenticatedUser = session.getAuthenticatedUser(); UserContext userContext = session.getUserContext(authProviderIdentifier); try { // Create connected tunnel using provided connection ID and client information - GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info); + GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, + id, info, new StandardTokenMap(authenticatedUser)); // Notify listeners to allow connection to be vetoed - fireTunnelConnectEvent(session.getAuthenticatedUser(), - session.getAuthenticatedUser().getCredentials(), tunnel); + fireTunnelConnectEvent(authenticatedUser, authenticatedUser.getCredentials(), tunnel); // Associate tunnel with session return createAssociatedTunnel(tunnel, authToken, session, userContext, type, id); From 7a3f51be71596dba55ec8963a1675920cf3d5e90 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 4 Oct 2018 10:31:23 -0700 Subject: [PATCH 2/6] GUACAMOLE-524: Rename LDAP-specific AuthenticatedUser / UserContext to not conflict with guacamole-ext classes. --- .../ldap/AuthenticationProviderService.java | 17 +++++++++-------- ...atedUser.java => LDAPAuthenticatedUser.java} | 2 +- .../{UserContext.java => LDAPUserContext.java} | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) rename extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/{AuthenticatedUser.java => LDAPAuthenticatedUser.java} (97%) rename extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/{UserContext.java => LDAPUserContext.java} (97%) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index b7e983014..c82018951 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -30,11 +30,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.guacamole.auth.ldap.user.AuthenticatedUser; -import org.apache.guacamole.auth.ldap.user.UserContext; +import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; +import org.apache.guacamole.auth.ldap.user.LDAPUserContext; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.ldap.user.UserService; +import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; @@ -74,13 +75,13 @@ public class AuthenticationProviderService { * Provider for AuthenticatedUser objects. */ @Inject - private Provider authenticatedUserProvider; + private Provider authenticatedUserProvider; /** * Provider for UserContext objects. */ @Inject - private Provider userContextProvider; + private Provider userContextProvider; /** * Determines the DN which corresponds to the user having the given @@ -211,7 +212,7 @@ public class AuthenticationProviderService { * If an error occurs while authenticating the user, or if access is * denied. */ - public AuthenticatedUser authenticateUser(Credentials credentials) + public LDAPAuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { // Attempt bind @@ -231,7 +232,7 @@ public class AuthenticationProviderService { try { // Return AuthenticatedUser if bind succeeds - AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); authenticatedUser.init(credentials, getLDAPAttributes(ldapConnection, credentials.getUsername())); return authenticatedUser; @@ -318,7 +319,7 @@ public class AuthenticationProviderService { * @throws GuacamoleException * If the UserContext cannot be created due to an error. */ - public UserContext getUserContext(org.apache.guacamole.net.auth.AuthenticatedUser authenticatedUser) + public LDAPUserContext getUserContext(AuthenticatedUser authenticatedUser) throws GuacamoleException { // Bind using credentials associated with AuthenticatedUser @@ -330,7 +331,7 @@ public class AuthenticationProviderService { try { // Build user context by querying LDAP - UserContext userContext = userContextProvider.get(); + LDAPUserContext userContext = userContextProvider.get(); userContext.init(authenticatedUser, ldapConnection); return userContext; diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java similarity index 97% rename from extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java rename to extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java index aadb687f1..8969e736c 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/AuthenticatedUser.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java @@ -29,7 +29,7 @@ import org.apache.guacamole.net.auth.Credentials; * An LDAP-specific implementation of AuthenticatedUser, associating a * particular set of credentials with the LDAP authentication provider. */ -public class AuthenticatedUser extends AbstractAuthenticatedUser { +public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { /** * Reference to the authentication provider associated with this diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java similarity index 97% rename from extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java rename to extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java index 26ea6b319..8f29a9694 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserContext.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java @@ -42,12 +42,12 @@ import org.slf4j.LoggerFactory; * An LDAP-specific implementation of UserContext which queries all Guacamole * connections and users from the LDAP directory. */ -public class UserContext extends AbstractUserContext { +public class LDAPUserContext extends AbstractUserContext { /** * Logger for this class. */ - private final Logger logger = LoggerFactory.getLogger(UserContext.class); + private final Logger logger = LoggerFactory.getLogger(LDAPUserContext.class); /** * Service for retrieving Guacamole connections from the LDAP server. From 0d7cff5f2d394fa7a7d8a5878895f3b5b9ffea4d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 4 Oct 2018 21:17:22 -0700 Subject: [PATCH 3/6] GUACAMOLE-524: Add convenience classes for injecting custom parameter tokens through decoration. --- .../net/auth/TokenInjectingConnection.java | 69 +++++++++ .../auth/TokenInjectingConnectionGroup.java | 69 +++++++++ .../net/auth/TokenInjectingUserContext.java | 144 ++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java create mode 100644 guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java create mode 100644 guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java new file mode 100644 index 000000000..8a826d88c --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnection.java @@ -0,0 +1,69 @@ +/* + * 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.net.auth; + +import java.util.HashMap; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.protocol.GuacamoleClientInformation; + +/** + * Connection implementation which overrides the connect() function of an + * underlying Connection, adding a given set of parameter tokens to the tokens + * already supplied. + */ +public class TokenInjectingConnection extends DelegatingConnection { + + /** + * The additional tokens to include with each call to connect(). + */ + private final Map tokens; + + /** + * Wraps the given Connection, automatically adding the given tokens to + * each invocation of connect(). Any additional tokens which have the same + * name as existing tokens will override the existing values. + * + * @param connection + * The Connection to wrap. + * + * @param tokens + * The additional tokens to include with each call to connect(). + */ + public TokenInjectingConnection(Connection connection, + Map tokens) { + super(connection); + this.tokens = tokens; + } + + @Override + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + + // Apply provided tokens over those given to connect() + tokens = new HashMap<>(tokens); + tokens.putAll(this.tokens); + + return super.connect(info, tokens); + + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java new file mode 100644 index 000000000..0ec93baf4 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingConnectionGroup.java @@ -0,0 +1,69 @@ +/* + * 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.net.auth; + +import java.util.HashMap; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.protocol.GuacamoleClientInformation; + +/** + * ConnectionGroup implementation which overrides the connect() function of an + * underlying ConnectionGroup, adding a given set of parameter tokens to the + * tokens already supplied. + */ +public class TokenInjectingConnectionGroup extends DelegatingConnectionGroup { + + /** + * The additional tokens to include with each call to connect(). + */ + private final Map tokens; + + /** + * Wraps the given ConnectionGroup, automatically adding the given tokens + * to each invocation of connect(). Any additional tokens which have the + * same name as existing tokens will override the existing values. + * + * @param connectionGroup + * The ConnectionGroup to wrap. + * + * @param tokens + * The additional tokens to include with each call to connect(). + */ + public TokenInjectingConnectionGroup(ConnectionGroup connectionGroup, + Map tokens) { + super(connectionGroup); + this.tokens = tokens; + } + + @Override + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + + // Apply provided tokens over those given to connect() + tokens = new HashMap<>(tokens); + tokens.putAll(this.tokens); + + return super.connect(info, tokens); + + } + +} diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java new file mode 100644 index 000000000..a1ede96a5 --- /dev/null +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/TokenInjectingUserContext.java @@ -0,0 +1,144 @@ +/* + * 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.net.auth; + +import java.util.Collections; +import java.util.Map; +import org.apache.guacamole.GuacamoleException; + +/** + * UserContext implementation which decorates a given UserContext, + * automatically applying additional parameter tokens during the connection + * process of any retrieved Connections and ConnectionGroups. + */ +public class TokenInjectingUserContext extends DelegatingUserContext { + + /** + * The additional tokens to include with each call to connect() if + * getTokens() is not overridden. + */ + private final Map tokens; + + /** + * Wraps the given UserContext, overriding the connect() function of each + * retrieved Connection and ConnectionGroup such that the given additional + * parameter tokens are included. Any additional tokens which have the same + * name as existing tokens will override the existing values. If tokens + * specific to a particular connection or connection group need to be + * included, getTokens() may be overridden to provide a different set of + * tokens. + * + * @param userContext + * The UserContext to wrap. + * + * @param tokens + * The additional tokens to include with each call to connect(). + */ + public TokenInjectingUserContext(UserContext userContext, + Map tokens) { + super(userContext); + this.tokens = tokens; + } + + /** + * Wraps the given UserContext, overriding the connect() function of each + * retrieved Connection and ConnectionGroup such that the additional + * parameter tokens returned by getTokens() are included. Any additional + * tokens which have the same name as existing tokens will override the + * existing values. + * + * @param userContext + * The UserContext to wrap. + */ + public TokenInjectingUserContext(UserContext userContext) { + this(userContext, Collections.emptyMap()); + } + + /** + * Returns the tokens which should be added to an in-progress call to + * connect() for the given Connection. If not overridden, this function + * will return the tokens provided when this instance of + * TokenInjectingUserContext was created. + * + * @param connection + * The Connection on which connect() has been called. + * + * @return + * The tokens which should be added to the in-progress call to + * connect(). + */ + protected Map getTokens(Connection connection) { + return tokens; + } + + /** + * Returns the tokens which should be added to an in-progress call to + * connect() for the given ConnectionGroup. If not overridden, this + * function will return the tokens provided when this instance of + * TokenInjectingUserContext was created. + * + * @param connectionGroup + * The ConnectionGroup on which connect() has been called. + * + * @return + * The tokens which should be added to the in-progress call to + * connect(). + */ + protected Map getTokens(ConnectionGroup connectionGroup) { + return tokens; + } + + @Override + public Directory getConnectionGroupDirectory() + throws GuacamoleException { + return new DecoratingDirectory(super.getConnectionGroupDirectory()) { + + @Override + protected ConnectionGroup decorate(ConnectionGroup object) throws GuacamoleException { + return new TokenInjectingConnectionGroup(object, getTokens(object)); + } + + @Override + protected ConnectionGroup undecorate(ConnectionGroup object) throws GuacamoleException { + return ((TokenInjectingConnectionGroup) object).getDelegateConnectionGroup(); + } + + }; + } + + @Override + public Directory getConnectionDirectory() + throws GuacamoleException { + return new DecoratingDirectory(super.getConnectionDirectory()) { + + @Override + protected Connection decorate(Connection object) throws GuacamoleException { + return new TokenInjectingConnection(object, getTokens(object)); + } + + @Override + protected Connection undecorate(Connection object) throws GuacamoleException { + return ((TokenInjectingConnection) object).getDelegateConnection(); + } + + }; + } + +} From 98bd3ead2179febdafcae7935811da88a05beda1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 4 Oct 2018 23:37:16 -0700 Subject: [PATCH 4/6] GUACAMOLE-524: Remove Attributes interface from AuthenticatedUser. Rely instead on tokens injected via decoration of connections. --- .../jdbc/user/ModeledAuthenticatedUser.java | 1 - .../jdbc/user/RemoteAuthenticatedUser.java | 11 ----- .../ldap/AuthenticationProviderService.java | 45 +++++++++++-------- .../auth/ldap/ConfigurationService.java | 3 +- .../auth/ldap/LDAPAuthenticationProvider.java | 17 +++++++ .../ldap/connection/ConnectionService.java | 9 ++++ .../auth/ldap/user/LDAPAuthenticatedUser.java | 38 +++++++++------- .../net/auth/AbstractAuthenticatedUser.java | 11 ----- .../guacamole/net/auth/AuthenticatedUser.java | 2 +- .../guacamole/token/StandardTokens.java | 43 ++---------------- .../guacamole/tunnel/StandardTokenMap.java | 17 ------- 11 files changed, 82 insertions(+), 115 deletions(-) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java index 539cec007..828b05e9c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/ModeledAuthenticatedUser.java @@ -79,7 +79,6 @@ public class ModeledAuthenticatedUser extends RemoteAuthenticatedUser { super(authenticatedUser.getAuthenticationProvider(), authenticatedUser.getCredentials(), authenticatedUser.getEffectiveUserGroups()); this.modelAuthenticationProvider = modelAuthenticationProvider; this.user = user; - super.setAttributes(authenticatedUser.getAttributes()); } /** diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java index f99465529..a936e4ecc 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/RemoteAuthenticatedUser.java @@ -19,7 +19,6 @@ package org.apache.guacamole.auth.jdbc.user; -import java.util.Map; import java.util.Collections; import java.util.Set; import org.apache.guacamole.net.auth.AuthenticatedUser; @@ -52,16 +51,6 @@ public abstract class RemoteAuthenticatedUser implements AuthenticatedUser { */ private final Set effectiveGroups; - @Override - public Map getAttributes() { - return Collections.emptyMap(); - } - - @Override - public void setAttributes(Map attributes) { - // No attributes supported - } - /** * Creates a new RemoteAuthenticatedUser, deriving the associated remote * host from the given credentials. diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index c82018951..aa4382e8d 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -53,6 +53,12 @@ public class AuthenticationProviderService { */ private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); + /** + * The prefix string to add to each parameter token generated from an LDAP + * attribute name. + */ + private static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_ATTR_"; + /** * Service for creating and managing connections to LDAP servers. */ @@ -233,7 +239,7 @@ public class AuthenticationProviderService { try { // Return AuthenticatedUser if bind succeeds LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(credentials, getLDAPAttributes(ldapConnection, credentials.getUsername())); + authenticatedUser.init(credentials, getAttributeTokens(ldapConnection, credentials.getUsername())); return authenticatedUser; @@ -246,43 +252,44 @@ public class AuthenticationProviderService { } /** - * Returns all custom LDAP attributes on the user currently bound under - * the given LDAP connection. The custom attributes are specified in - * guacamole.properties. If no attributes are specified or none are + * Returns parameter tokens generated from LDAP attributes on the user + * currently bound under the given LDAP connection. The attributes to be + * converted into parameter tokens must be explicitly listed in + * guacamole.properties. If no attributes are specified or none are * found on the LDAP user object, an empty map is returned. * * @param ldapConnection - * LDAP connection to find the custom LDAP attributes. + * LDAP connection to use to read the attributes of the user. * * @param username - * The username of the user whose attributes are queried. + * The username of the user whose attributes are to be queried. * * @return - * All attributes on the user currently bound under the - * given LDAP connection, as a map of attribute name to - * corresponding attribute value, or an empty map if no - * attributes are specified or none are found on the user - * object. + * A map of parameter tokens generated from attributes on the user + * currently bound under the given LDAP connection, as a map of token + * name to corresponding value, or an empty map if no attributes are + * specified or none are found on the user object. * * @throws GuacamoleException * If an error occurs retrieving the user DN or the attributes. */ - private Map getLDAPAttributes(LDAPConnection ldapConnection, + private Map getAttributeTokens(LDAPConnection ldapConnection, String username) throws GuacamoleException { // Get attributes from configuration information List attrList = confService.getAttributes(); // If there are no attributes there is no reason to search LDAP - if (attrList == null || attrList.isEmpty()) + if (attrList.isEmpty()) return Collections.emptyMap(); // Build LDAP query parameters String[] attrArray = attrList.toArray(new String[attrList.size()]); String userDN = getUserBindDN(username); - Map attrMap = new HashMap(); + Map tokens = new HashMap(); try { + // Get LDAP attributes by querying LDAP LDAPEntry userEntry = ldapConnection.read(userDN, attrArray); if (userEntry == null) @@ -292,17 +299,19 @@ public class AuthenticationProviderService { if (attrSet == null) return Collections.emptyMap(); - // Add each attribute into Map + // Convert each retrieved attribute into a corresponding token for (Object attrObj : attrSet) { LDAPAttribute attr = (LDAPAttribute)attrObj; - attrMap.put(attr.getName(), attr.getStringValue()); + tokens.put(LDAP_ATTRIBUTE_TOKEN_PREFIX + attr.getName(), attr.getStringValue()); } + } catch (LDAPException e) { - throw new GuacamoleServerException("Error while querying for User Attributes.", e); + throw new GuacamoleServerException("Could not query LDAP user attributes.", e); } - return attrMap; + return tokens; + } /** 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 b52ad50d2..4347d4570 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 @@ -355,7 +355,8 @@ public class ConfigurationService { */ public List getAttributes() throws GuacamoleException { return environment.getProperty( - LDAPGuacamoleProperties.LDAP_USER_ATTRIBUTES + LDAPGuacamoleProperties.LDAP_USER_ATTRIBUTES, + Collections.emptyList() ); } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java index 31aa4e2e2..48e275c0c 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPAuthenticationProvider.java @@ -23,9 +23,11 @@ package org.apache.guacamole.auth.ldap; import com.google.inject.Guice; import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.net.auth.TokenInjectingUserContext; import org.apache.guacamole.net.auth.UserContext; /** @@ -85,4 +87,19 @@ public class LDAPAuthenticationProvider extends AbstractAuthenticationProvider { } + @Override + public UserContext decorate(UserContext context, + AuthenticatedUser authenticatedUser, Credentials credentials) + throws GuacamoleException { + + // Only decorate if the user authenticated against LDAP + if (!(authenticatedUser instanceof LDAPAuthenticatedUser)) + return context; + + // Apply LDAP-specific tokens to all connections / connection groups + return new TokenInjectingUserContext(context, + ((LDAPAuthenticatedUser) authenticatedUser).getTokens()); + + } + } 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 18e3b9ceb..b69a13ae3 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 @@ -35,8 +35,10 @@ import org.apache.guacamole.auth.ldap.ConfigurationService; import org.apache.guacamole.auth.ldap.EscapingService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.TokenInjectingConnection; import org.apache.guacamole.net.auth.simple.SimpleConnection; import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; @@ -178,6 +180,13 @@ public class ConnectionService { String name = cn.getStringValue(); Connection connection = new SimpleConnection(name, name, config); connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); + + // Inject LDAP-specific tokens only if LDAP handled user + // authentication + if (user instanceof LDAPAuthenticatedUser) + connection = new TokenInjectingConnection(connection, + ((LDAPAuthenticatedUser) user).getTokens()); + connections.put(name, connection); } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java index 8969e736c..36f6695eb 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.ldap.user; import com.google.inject.Inject; +import java.util.Collections; import java.util.Map; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -44,35 +45,40 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser { private Credentials credentials; /** - * Arbitrary attributes associated with this AuthenticatedUser object. + * Name/value pairs to be applied as parameter tokens when connections + * are established using this AuthenticatedUser. */ - private Map attributes; + private Map tokens; /** * Initializes this AuthenticatedUser using the given credentials and - * arbitrary attributes. + * connection parameter tokens. * * @param credentials * The credentials provided when this user was authenticated. * - * @param attributes - * The map of arbitrary attribute name/value pairs to associate with - * this AuthenticatedUser. + * @param tokens + * A Map of all name/value pairs that should be applied as parameter + * tokens when connections are established using the AuthenticatedUser. */ - public void init(Credentials credentials, Map attributes) { + public void init(Credentials credentials, Map tokens) { this.credentials = credentials; - this.attributes = attributes; + this.tokens = Collections.unmodifiableMap(tokens); setIdentifier(credentials.getUsername()); } - @Override - public Map getAttributes() { - return attributes; - } - - @Override - public void setAttributes(Map attributes) { - // All attributes are read-only + /** + * Returns a Map of all name/value pairs that should be applied as + * parameter tokens when connections are established using this + * AuthenticatedUser. + * + * @return + * A Map of all name/value pairs that should be applied as parameter + * tokens when connections are established using this + * AuthenticatedUser. + */ + public Map getTokens() { + return tokens; } @Override diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java index 828ad8904..36c4571e0 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AbstractAuthenticatedUser.java @@ -20,7 +20,6 @@ package org.apache.guacamole.net.auth; import java.util.Collections; -import java.util.Map; import java.util.Set; /** @@ -42,14 +41,4 @@ public abstract class AbstractAuthenticatedUser extends AbstractIdentifiable // Nothing to invalidate } - @Override - public Map getAttributes() { - return Collections.emptyMap(); - } - - @Override - public void setAttributes(Map attributes) { - //do nothing - } - } diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java index 14f25978a..a799937e8 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/net/auth/AuthenticatedUser.java @@ -25,7 +25,7 @@ import java.util.Set; * A user of the Guacamole web application who has been authenticated by an * AuthenticationProvider. */ -public interface AuthenticatedUser extends Identifiable, Attributes { +public interface AuthenticatedUser extends Identifiable { /** * The identifier reserved for representing a user that has authenticated diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java index 22a042e0c..3118cf29d 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/StandardTokens.java @@ -21,8 +21,6 @@ package org.apache.guacamole.token; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Map; -import java.util.Set; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; @@ -81,11 +79,6 @@ public class StandardTokens { */ private static final String TIME_FORMAT = "HHmmss"; - /** - * The prefix of the arbitrary attribute tokens. - */ - public static final String ATTR_TOKEN_PREFIX = "GUAC_ATTR_"; - /** * This utility class should not be instantiated. */ @@ -155,11 +148,10 @@ public class StandardTokens { * Adds tokens which are standardized by guacamole-ext to the given * TokenFilter using the values from the given AuthenticatedUser object, * including any associated credentials. These standardized tokens include - * the current username (GUAC_USERNAME), password (GUAC_PASSWORD), the - * server date and time (GUAC_DATE and GUAC_TIME respectively), and custom - * user attributes. If either the username or password are not set within - * the given user or their provided credentials, the corresponding token(s) - * will remain unset. + * the current username (GUAC_USERNAME), password (GUAC_PASSWORD), and the + * server date and time (GUAC_DATE and GUAC_TIME respectively). If either + * the username or password are not set within the given user or their + * provided credentials, the corresponding token(s) will remain unset. * * @param filter * The TokenFilter to add standard tokens to. @@ -177,33 +169,6 @@ public class StandardTokens { // Add tokens specific to credentials addStandardTokens(filter, user.getCredentials()); - // Add custom attribute tokens - addAttributeTokens(filter, user.getAttributes()); - } - - /** - * Add attribute tokens to StandardTokens. These are arbitrary - * key/value pairs that may be configured by the various authentication - * extensions. - * - * @param filter - * The TokenFilter to add attribute tokens to. - * - * @param attributes - * The map of key/value pairs to add tokens for. - */ - public static void addAttributeTokens(TokenFilter filter, - Map attributes) { - - if (attributes != null) { - for (Map.Entry entry : attributes.entrySet()) { - String key = entry.getKey().toString(); - String tokenName = ATTR_TOKEN_PREFIX + key.toUpperCase(); - String tokenValue = entry.getValue().toString(); - filter.setToken(tokenName, tokenValue); - } - } - } } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java index 1d357d21b..339286194 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/StandardTokenMap.java @@ -22,7 +22,6 @@ package org.apache.guacamole.tunnel; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; -import java.util.Map; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; @@ -76,11 +75,6 @@ public class StandardTokenMap extends HashMap { */ private static final String TIME_FORMAT = "HHmmss"; - /** - * The prefix of the arbitrary attribute tokens. - */ - public static final String ATTR_TOKEN_PREFIX = "GUAC_ATTR_"; - /** * Creates a new StandardTokenMap which is pre-populated with the * name/value pairs of all standardized tokens available for the given @@ -97,7 +91,6 @@ public class StandardTokenMap extends HashMap { put(TIME_TOKEN, new SimpleDateFormat(TIME_FORMAT).format(currentTime)); Credentials credentials = authenticatedUser.getCredentials(); - Map attributes = authenticatedUser.getAttributes(); // Add username token String username = credentials.getUsername(); @@ -124,16 +117,6 @@ public class StandardTokenMap extends HashMap { if (address != null) put(CLIENT_ADDRESS_TOKEN, address); - // Add tokens for all attributes on the AuthenticatedUser - if (attributes != null) { - for (Map.Entry entry : attributes.entrySet()) { - String key = entry.getKey().toString(); - String tokenName = ATTR_TOKEN_PREFIX + key.toUpperCase(); - String tokenValue = entry.getValue().toString(); - put(tokenName, tokenValue); - } - } - } } From cb30b148b9fe8a2561dc1db9e134ee16c7845310 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 5 Oct 2018 00:12:50 -0700 Subject: [PATCH 5/6] GUACAMOLE-524: Consistently generate token names from LDAP attributes with arbitrary naming conventions. --- extensions/guacamole-auth-ldap/pom.xml | 8 ++ .../ldap/AuthenticationProviderService.java | 8 +- .../apache/guacamole/auth/ldap/TokenName.java | 106 ++++++++++++++++++ .../guacamole/auth/ldap/TokenNameTest.java | 53 +++++++++ 4 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java create mode 100644 extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml index 6872f6a46..f0ddb549a 100644 --- a/extensions/guacamole-auth-ldap/pom.xml +++ b/extensions/guacamole-auth-ldap/pom.xml @@ -153,6 +153,14 @@ 3.0 + + + junit + junit + 4.12 + test + + diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index aa4382e8d..4f5d76c6e 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -53,12 +53,6 @@ public class AuthenticationProviderService { */ private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); - /** - * The prefix string to add to each parameter token generated from an LDAP - * attribute name. - */ - private static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_ATTR_"; - /** * Service for creating and managing connections to LDAP servers. */ @@ -302,7 +296,7 @@ public class AuthenticationProviderService { // Convert each retrieved attribute into a corresponding token for (Object attrObj : attrSet) { LDAPAttribute attr = (LDAPAttribute)attrObj; - tokens.put(LDAP_ATTRIBUTE_TOKEN_PREFIX + attr.getName(), attr.getStringValue()); + tokens.put(TokenName.fromAttribute(attr.getName()), attr.getStringValue()); } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java new file mode 100644 index 000000000..2a99c3d34 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java @@ -0,0 +1,106 @@ +/* + * 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 java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class for generating parameter token names. + */ +public class TokenName { + + /** + * The prefix string to add to each parameter token generated from an LDAP + * attribute name. + */ + private static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_ATTR_"; + + /** + * Pattern which matches logical groupings of words within an LDAP + * attribute name. This pattern is intended to match logical groupings + * regardless of the naming convention used: "CamelCase", + * "headlessCamelCase", "lowercase_with_underscores", + * "lowercase-with-dashes" or even "aVery-INCONSISTENTMix_ofAllStyles". + */ + private static final Pattern LDAP_ATTRIBUTE_NAME_GROUPING = Pattern.compile( + + // "Camel" word groups + "\\p{javaUpperCase}\\p{javaLowerCase}+" + + // Groups of digits + + "|[0-9]+" + + // Groups of uppercase letters, excluding the uppercase letter + // which begins a following "Camel" group + + "|\\p{javaUpperCase}+(?!\\p{javaLowerCase})" + + // Groups of lowercase letters which match no other pattern + + "|\\p{javaLowerCase}+" + + // Groups of word characters letters which match no other pattern + + "|\\b\\w+\\b" + + ); + + /** + * This utility class should not be instantiated. + */ + private TokenName() {} + + /** + * Generates the name of the parameter token that should be populated with + * the value of the given LDAP attribute. The name of the LDAP attribute + * will automatically be transformed from "CamelCase", "headlessCamelCase", + * "lowercase_with_underscores", and "mixes_ofBoth_Styles" to consistent + * "UPPERCASE_WITH_UNDERSCORES". Each returned attribute will be prefixed + * with "LDAP_ATTR_". + * + * @param name + * The name of the LDAP attribute to use to generate the token name. + * + * @return + * The name of the parameter token that should be populated with the + * value of the LDAP attribute having the given name. + */ + public static String fromAttribute(String name) { + + // If even one logical word grouping cannot be found, default to + // simply converting the attribute to uppercase and adding the + // prefix + Matcher groupMatcher = LDAP_ATTRIBUTE_NAME_GROUPING.matcher(name); + if (!groupMatcher.find()) + return LDAP_ATTRIBUTE_TOKEN_PREFIX + name.toUpperCase(); + + // Split the given name into logical word groups, separated by + // underscores and converted to uppercase + StringBuilder builder = new StringBuilder(LDAP_ATTRIBUTE_TOKEN_PREFIX); + builder.append(groupMatcher.group(0).toUpperCase()); + + while (groupMatcher.find()) { + builder.append("_"); + builder.append(groupMatcher.group(0).toUpperCase()); + } + + return builder.toString(); + + } + +} diff --git a/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java b/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java new file mode 100644 index 000000000..3ce2cc89b --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java @@ -0,0 +1,53 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * Test which verifies automatic generation of LDAP-specific connection + * parameter token names. + */ +public class TokenNameTest { + + /** + * Verifies that TokenName.fromAttribute() generates token names as + * specified, regardless of the naming convention of the attribute. + */ + @Test + public void testFromAttribute() { + assertEquals("LDAP_ATTR_A", TokenName.fromAttribute("a")); + assertEquals("LDAP_ATTR_B", TokenName.fromAttribute("b")); + assertEquals("LDAP_ATTR_1", TokenName.fromAttribute("1")); + assertEquals("LDAP_ATTR_SOME_URL", TokenName.fromAttribute("someURL")); + assertEquals("LDAP_ATTR_LOWERCASE_WITH_DASHES", TokenName.fromAttribute("lowercase-with-dashes")); + assertEquals("LDAP_ATTR_HEADLESS_CAMEL_CASE", TokenName.fromAttribute("headlessCamelCase")); + assertEquals("LDAP_ATTR_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); + assertEquals("LDAP_ATTR_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); + assertEquals("LDAP_ATTR_LOWERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("lowercase_with_underscores")); + assertEquals("LDAP_ATTR_UPPERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("UPPERCASE_WITH_UNDERSCORES")); + assertEquals("LDAP_ATTR_A_VERY_INCONSISTENT_MIX_OF_ALL_STYLES", TokenName.fromAttribute("aVery-INCONSISTENTMix_ofAllStyles")); + assertEquals("LDAP_ATTR_ABC_123_DEF_456", TokenName.fromAttribute("abc123def456")); + assertEquals("LDAP_ATTR_ABC_123_DEF_456", TokenName.fromAttribute("ABC123DEF456")); + assertEquals("LDAP_ATTR_WORD_A_WORD_AB_WORD_ABC_WORD", TokenName.fromAttribute("WordAWordABWordABCWord")); + } + +} From 13e2b066663c0e5a6c6a52474d90a51c6ea48ef8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 5 Oct 2018 12:54:00 -0700 Subject: [PATCH 6/6] GUACAMOLE-524: Switch to "LDAP_" prefix for LDAP user attribute tokens. --- .../apache/guacamole/auth/ldap/TokenName.java | 4 +-- .../guacamole/auth/ldap/TokenNameTest.java | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java index 2a99c3d34..90de5bf84 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/TokenName.java @@ -31,7 +31,7 @@ public class TokenName { * The prefix string to add to each parameter token generated from an LDAP * attribute name. */ - private static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_ATTR_"; + private static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_"; /** * Pattern which matches logical groupings of words within an LDAP @@ -71,7 +71,7 @@ public class TokenName { * will automatically be transformed from "CamelCase", "headlessCamelCase", * "lowercase_with_underscores", and "mixes_ofBoth_Styles" to consistent * "UPPERCASE_WITH_UNDERSCORES". Each returned attribute will be prefixed - * with "LDAP_ATTR_". + * with "LDAP_". * * @param name * The name of the LDAP attribute to use to generate the token name. diff --git a/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java b/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java index 3ce2cc89b..2ba61dc20 100644 --- a/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java +++ b/extensions/guacamole-auth-ldap/src/test/java/org/apache/guacamole/auth/ldap/TokenNameTest.java @@ -34,20 +34,20 @@ public class TokenNameTest { */ @Test public void testFromAttribute() { - assertEquals("LDAP_ATTR_A", TokenName.fromAttribute("a")); - assertEquals("LDAP_ATTR_B", TokenName.fromAttribute("b")); - assertEquals("LDAP_ATTR_1", TokenName.fromAttribute("1")); - assertEquals("LDAP_ATTR_SOME_URL", TokenName.fromAttribute("someURL")); - assertEquals("LDAP_ATTR_LOWERCASE_WITH_DASHES", TokenName.fromAttribute("lowercase-with-dashes")); - assertEquals("LDAP_ATTR_HEADLESS_CAMEL_CASE", TokenName.fromAttribute("headlessCamelCase")); - assertEquals("LDAP_ATTR_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); - assertEquals("LDAP_ATTR_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); - assertEquals("LDAP_ATTR_LOWERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("lowercase_with_underscores")); - assertEquals("LDAP_ATTR_UPPERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("UPPERCASE_WITH_UNDERSCORES")); - assertEquals("LDAP_ATTR_A_VERY_INCONSISTENT_MIX_OF_ALL_STYLES", TokenName.fromAttribute("aVery-INCONSISTENTMix_ofAllStyles")); - assertEquals("LDAP_ATTR_ABC_123_DEF_456", TokenName.fromAttribute("abc123def456")); - assertEquals("LDAP_ATTR_ABC_123_DEF_456", TokenName.fromAttribute("ABC123DEF456")); - assertEquals("LDAP_ATTR_WORD_A_WORD_AB_WORD_ABC_WORD", TokenName.fromAttribute("WordAWordABWordABCWord")); + assertEquals("LDAP_A", TokenName.fromAttribute("a")); + assertEquals("LDAP_B", TokenName.fromAttribute("b")); + assertEquals("LDAP_1", TokenName.fromAttribute("1")); + assertEquals("LDAP_SOME_URL", TokenName.fromAttribute("someURL")); + assertEquals("LDAP_LOWERCASE_WITH_DASHES", TokenName.fromAttribute("lowercase-with-dashes")); + assertEquals("LDAP_HEADLESS_CAMEL_CASE", TokenName.fromAttribute("headlessCamelCase")); + assertEquals("LDAP_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); + assertEquals("LDAP_CAMEL_CASE", TokenName.fromAttribute("CamelCase")); + assertEquals("LDAP_LOWERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("lowercase_with_underscores")); + assertEquals("LDAP_UPPERCASE_WITH_UNDERSCORES", TokenName.fromAttribute("UPPERCASE_WITH_UNDERSCORES")); + assertEquals("LDAP_A_VERY_INCONSISTENT_MIX_OF_ALL_STYLES", TokenName.fromAttribute("aVery-INCONSISTENTMix_ofAllStyles")); + assertEquals("LDAP_ABC_123_DEF_456", TokenName.fromAttribute("abc123def456")); + assertEquals("LDAP_ABC_123_DEF_456", TokenName.fromAttribute("ABC123DEF456")); + assertEquals("LDAP_WORD_A_WORD_AB_WORD_ABC_WORD", TokenName.fromAttribute("WordAWordABWordABCWord")); } }