diff --git a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticatedUser.java b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticatedUser.java index 076731edf..187ba473c 100644 --- a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticatedUser.java +++ b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticatedUser.java @@ -32,7 +32,7 @@ import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; -import org.apache.guacamole.protocol.GuacamoleConfiguration; +import org.openuds.guacamole.connection.UDSConnection; /** * A Guacamole user that was authenticated by an external UDS service. @@ -50,10 +50,9 @@ public class UDSAuthenticatedUser extends AbstractAuthenticatedUser { private final Credentials credentials; /** - * The GuacamoleConfiguration generated from the connection information - * returned by the external UDS service when the user authenticated. + * The single connection that this user should be authorized to access. */ - private final GuacamoleConfiguration config; + private final UDSConnection connection; /** * Creates a new UDSAuthenticatedUser representing a Guacamole user that @@ -65,15 +64,14 @@ public class UDSAuthenticatedUser extends AbstractAuthenticatedUser { * @param credentials * The credentials provided by the user when they authenticated. * - * @param config - * The GuacamoleConfiguration generated from the connection information - * returned by the external UDS service when the user authenticated. + * @param connection + * The single connection that the user should be authorized to access. */ public UDSAuthenticatedUser(AuthenticationProvider authProvider, - Credentials credentials, GuacamoleConfiguration config) { + Credentials credentials, UDSConnection connection) { this.authProvider = authProvider; this.credentials = credentials; - this.config = config; + this.connection = connection; } @Override @@ -92,16 +90,14 @@ public class UDSAuthenticatedUser extends AbstractAuthenticatedUser { } /** - * Returns the GuacamoleConfiguration generated from the connection - * information provided by the external UDS service when the user - * authenticated. + * Returns the single connection that this user should be authorized to + * access. * * @return - * The GuacamoleConfiguration generated from the connection information - * provided by the external UDS service when the user authenticated. + * The single connection that this user should be authorized to access. */ - public GuacamoleConfiguration getGuacamoleConfiguration() { - return config; + public UDSConnection getConnection() { + return connection; } } diff --git a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticationProvider.java b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticationProvider.java index 6d10381f9..a91d31dd1 100644 --- a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticationProvider.java +++ b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSAuthenticationProvider.java @@ -33,7 +33,6 @@ import com.google.inject.Injector; import java.util.Collections; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; import org.apache.guacamole.net.auth.AuthenticatedUser; @@ -41,9 +40,8 @@ import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.UserContext; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; -import org.apache.guacamole.net.auth.simple.SimpleUserContext; -import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.openuds.guacamole.connection.ConnectionService; +import org.openuds.guacamole.connection.UDSConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,18 +51,20 @@ import org.slf4j.LoggerFactory; */ public class UDSAuthenticationProvider extends AbstractAuthenticationProvider { - /** - * The name of the single connection that should be exposed to any user - * that authenticates via UDS. - */ - private static final String CONNECTION_NAME = "UDS"; - /** * The name of the query parameter that should contain the data sent to * the UDS service for authentication. */ private static final String DATA_PARAMETER_NAME = "data"; + /** + * The form of credentials accepted by this extension. + */ + private static final CredentialsInfo UDS_CREDENTIALS = + new CredentialsInfo(Collections.singletonList( + new Field(DATA_PARAMETER_NAME, Field.Type.QUERY_PARAMETER) + )); + /** * Logger for this class. */ @@ -109,29 +109,26 @@ public class UDSAuthenticationProvider extends AbstractAuthenticationProvider { // Pull OpenUDS-specific "data" parameter String data = request.getParameter(DATA_PARAMETER_NAME); - if (data != null && !data.isEmpty()) { - - logger.debug("Retrieving connection configuration using data from \"{}\"...", data); - - // Retrieve connection information using provided data - GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data); - if (config != null) { - - // Report successful authentication as a temporary, anonymous user, - // storing the retrieved connection configuration data for future use - return new UDSAuthenticatedUser(this, credentials, config); - - } - + if (data == null || data.isEmpty()) { + logger.debug("UDS connection data was not provided. No connection retrieval from UDS will be performed."); + throw new GuacamoleInvalidCredentialsException("Connection data was not provided.", UDS_CREDENTIALS); } - // Required parameter was missing or was invalid - throw new GuacamoleInvalidCredentialsException( - "Connection data was not provided or was rejected by UDS.", - new CredentialsInfo(Collections.singletonList( - new Field(DATA_PARAMETER_NAME, Field.Type.QUERY_PARAMETER) - )) - ); + try { + + // Retrieve connection information using provided data + UDSConnection connection = new UDSConnection(connectionService, data); + + // Report successful authentication as a temporary, anonymous user, + // storing the retrieved connection configuration data for future use + return new UDSAuthenticatedUser(this, credentials, connection); + + } + catch (GuacamoleException e) { + logger.info("Provided connection data could not be validated with UDS: {}", e.getMessage()); + logger.debug("Validation of UDS connection data failed.", e); + throw new GuacamoleInvalidCredentialsException("Connection data was rejected by UDS.", e, UDS_CREDENTIALS); + } } @@ -145,10 +142,7 @@ public class UDSAuthenticationProvider extends AbstractAuthenticationProvider { // Expose a single connection (derived from the "data" parameter // provided during authentication) - return new SimpleUserContext(this, Collections.singletonMap( - CONNECTION_NAME, - ((UDSAuthenticatedUser) authenticatedUser).getGuacamoleConfiguration() - )); + return new UDSUserContext(this, (UDSAuthenticatedUser) authenticatedUser); } diff --git a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSUserContext.java b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSUserContext.java new file mode 100644 index 000000000..bfbe2c0f8 --- /dev/null +++ b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/UDSUserContext.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020 Virtual Cable S.L. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Virtual Cable S.L. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.openuds.guacamole; + +import java.util.Collections; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.AbstractUserContext; +import org.apache.guacamole.net.auth.AuthenticationProvider; +import org.apache.guacamole.net.auth.Connection; +import org.apache.guacamole.net.auth.Directory; +import org.apache.guacamole.net.auth.User; +import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; +import org.apache.guacamole.net.auth.simple.SimpleDirectory; +import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet; +import org.apache.guacamole.net.auth.simple.SimpleUser; +import org.openuds.guacamole.connection.UDSConnection; + +/** + * UserContext implementation which exposes access only to a single + * UDSConnection. The details of the connection exposed are determined by the + * UDS-specific data associated with the user. + */ +public class UDSUserContext extends AbstractUserContext { + + /** + * The unique identifier of the root connection group. + */ + public static final String ROOT_CONNECTION_GROUP = DEFAULT_ROOT_CONNECTION_GROUP; + + /** + * The AuthenticationProvider that produced this UserContext. + */ + private final AuthenticationProvider authProvider; + + /** + * The AuthenticatedUser for whom this UserContext was created. + */ + private final UDSAuthenticatedUser authenticatedUser; + + /** + * Creates a new UDSUserContext that is associated with the given + * AuthenticationProvider and uses the UDS-specific data of the given + * UDSAuthenticatedUser to determine the connection that user can access. + * + * @param authProvider + * The AuthenticationProvider that is producing the UserContext. + * + * @param authenticatedUser + * The AuthenticatedUser for whom this UserContext is being created. + */ + public UDSUserContext(AuthenticationProvider authProvider, + UDSAuthenticatedUser authenticatedUser) { + this.authProvider = authProvider; + this.authenticatedUser = authenticatedUser; + } + + @Override + public User self() { + return new SimpleUser() { + + @Override + public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(Collections.singleton(DEFAULT_ROOT_CONNECTION_GROUP)); + } + + @Override + public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { + return new SimpleObjectPermissionSet(Collections.singleton(UDSConnection.IDENTIFIER)); + } + + }; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + @Override + public Directory getConnectionDirectory() throws GuacamoleException { + return new SimpleDirectory<>(authenticatedUser.getConnection()); + } + +} diff --git a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/ConnectionService.java b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/ConnectionService.java index 207613c4c..e2be58b0d 100644 --- a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/ConnectionService.java +++ b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/ConnectionService.java @@ -174,6 +174,8 @@ public class ConnectionService { public GuacamoleConfiguration getConnectionConfiguration(String data) throws GuacamoleException { + logger.debug("Retrieving/validating connection configuration using data from \"{}\"...", data); + // Build URI of remote service from the base URI and given data URI serviceURI = UriBuilder.fromUri(configService.getUDSBaseURI()) .path(configService.getUDSConnectionPath()) diff --git a/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/UDSConnection.java b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/UDSConnection.java new file mode 100644 index 000000000..cb563356b --- /dev/null +++ b/guacamole-auth-uds/src/main/java/org/openuds/guacamole/connection/UDSConnection.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020 Virtual Cable S.L. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Virtual Cable S.L. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.openuds.guacamole.connection; + +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.net.auth.simple.SimpleConnection; +import org.apache.guacamole.protocol.GuacamoleClientInformation; +import org.openuds.guacamole.UDSUserContext; + +/** + * Connection implementation which uses provided data to communicate with a + * remote UDS service to dynamically authorize access to a remote desktop. The + * provided data is validated when the UDSConnection is created and upon each + * connection attempt. + */ +public class UDSConnection extends SimpleConnection { + + /** + * The name of the single connection that should be exposed to any user + * that authenticates via UDS. + */ + public static final String NAME = "UDS"; + + /** + * The unique identifier of the single connection that should be exposed to + * any user that authenticates via UDS. + */ + public static final String IDENTIFIER = NAME; + + /** + * Service for retrieving configuration information. + */ + private final ConnectionService connectionService; + + /** + * The UDS-specific data that should be provided to the remote UDS service + * to re-authenticate the user and determine the details of the connection + * they are authorized to access. + */ + private final String data; + + /** + * Creates a new UDSConnection which exposes access to a remote desktop + * that is dynamically authorized by exchanging arbitrary UDS-specific data + * with a remote service. If the data is accepted by the UDS service, the + * data will also be re-validated upon each connection attempt. + * + * @param connectionService + * The service that should be used to validate the provided UDS data + * and retrieve corresponding connection configuration information. + * + * @param data + * The UDS-specific data that should be provided to the remote UDS + * service. + * + * @throws GuacamoleException + * If the provided data is no longer valid or the UDS service does not + * respond successfully. + */ + public UDSConnection(ConnectionService connectionService, String data) + throws GuacamoleException { + + // Validate provided data + super.setConfiguration(connectionService.getConnectionConfiguration(data)); + + this.connectionService = connectionService; + this.data = data; + + } + + @Override + public String getParentIdentifier() { + return UDSUserContext.ROOT_CONNECTION_GROUP; + } + + @Override + public void setParentIdentifier(String parentIdentifier) { + throw new UnsupportedOperationException("UDSConnection is read-only."); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void setName(String name) { + throw new UnsupportedOperationException("UDSConnection is read-only."); + } + + @Override + public String getIdentifier() { + return IDENTIFIER; + } + + @Override + public void setIdentifier(String identifier) { + throw new UnsupportedOperationException("UDSConnection is read-only."); + } + + @Override + public GuacamoleTunnel connect(GuacamoleClientInformation info, + Map tokens) throws GuacamoleException { + + // Re-validate provided data (do not allow connections if data is no + // longer valid) + super.setConfiguration(connectionService.getConnectionConfiguration(data)); + + // Connect with configuration produced from data + return super.connect(info, tokens); + + } + +}