Re-validate UDS data for each connection attempt.

This commit is contained in:
Michael Jumper 2021-02-10 16:40:06 -08:00
parent 4cc11d783a
commit 584dee9fcd
5 changed files with 294 additions and 50 deletions

View File

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

View File

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

View File

@ -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<Connection> getConnectionDirectory() throws GuacamoleException {
return new SimpleDirectory<>(authenticatedUser.getConnection());
}
}

View File

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

View File

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