GUACAMOLE-1025: Allow QuickConnect module to configure allowed and/or denied parameters.

This commit is contained in:
Virtually Nick
2020-04-14 18:52:09 -04:00
parent 754e9649f1
commit f04517ca5e
11 changed files with 669 additions and 166 deletions

View File

@@ -19,6 +19,8 @@
package org.apache.guacamole.auth.quickconnect;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
@@ -31,6 +33,20 @@ import org.apache.guacamole.net.auth.UserContext;
*/
public class QuickConnectAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* Injector which will manage the object graph of this authentication
* provider.
*/
private final Injector injector;
public QuickConnectAuthenticationProvider() throws GuacamoleException {
// Set up Guice injector.
injector = Guice.createInjector(
new QuickConnectAuthenticationProviderModule(this)
);
}
@Override
public String getIdentifier() {
return "quickconnect";
@@ -40,9 +56,13 @@ public class QuickConnectAuthenticationProvider extends AbstractAuthenticationPr
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
return new QuickConnectUserContext(this,
authenticatedUser.getIdentifier());
QuickConnectUserContext userContext =
injector.getInstance(QuickConnectUserContext.class);
userContext.init(authenticatedUser.getIdentifier());
return userContext;
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.quickconnect;
import com.google.inject.AbstractModule;
import org.apache.guacamole.auth.quickconnect.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.AuthenticationProvider;
/**
* Guice module which configures QuickConnect-specific injections.
*/
public class QuickConnectAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* A reference to the QuickConnectAuthenticationProvider on behalf of which
* this module has configured injection.
*/
private final AuthenticationProvider authProvider;
/**
* Creates a new QuickConnect authentication provider module which
* configures injection for the QuickConnectAuthenticationProvider.
*
* @param authProvider
* The AuthenticationProvider for which injection is being configured.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public QuickConnectAuthenticationProviderModule(
AuthenticationProvider authProvider) throws GuacamoleException {
// Get local environment
this.environment = new LocalEnvironment();
// Store associated auth provider
this.authProvider = authProvider;
}
@Override
protected void configure() {
// Bind core implementations of guacamole-ext classes
bind(AuthenticationProvider.class).toInstance(authProvider);
bind(Environment.class).toInstance(environment);
// Bind QuickConnect-specific services
bind(ConfigurationService.class);
bind(QuickConnectUserContext.class);
bind(QuickConnectDirectory.class);
}
}

View File

@@ -19,11 +19,13 @@
package org.apache.guacamole.auth.quickconnect;
import com.google.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.quickconnect.utility.QCParser;
import org.apache.guacamole.auth.quickconnect.conf.ConfigurationService;
import org.apache.guacamole.net.auth.ConnectionGroup;
import org.apache.guacamole.net.auth.simple.SimpleConnection;
import org.apache.guacamole.net.auth.simple.SimpleDirectory;
@@ -35,40 +37,46 @@ import org.apache.guacamole.protocol.GuacamoleConfiguration;
* completely in memory.
*/
public class QuickConnectDirectory extends SimpleDirectory<Connection> {
/**
* The configuration service for the QuickConnect module.
*/
@Inject
private ConfigurationService confService;
/**
* The connections to store.
*/
private final Map<String, Connection> connections =
new ConcurrentHashMap<String, Connection>();
new ConcurrentHashMap<>();
/**
* The root connection group for this directory.
*/
private final QuickConnectionGroup rootGroup;
private QuickConnectionGroup rootGroup;
/**
* The internal counter for connection IDs.
*/
private final AtomicInteger connectionId;
private AtomicInteger connectionId;
/**
* Creates a new QuickConnectDirectory with the default
* empty Map for Connection objects, and the specified
* ConnectionGroup at the root of the directory.
* Initialize the QuickConnectDirectory with the default empty Map for
* Connection objects, and the specified ConnectionGroup at the root of
* the directory.
*
* @param rootGroup
* A group that should be at the root of this directory.
*/
public QuickConnectDirectory(ConnectionGroup rootGroup) {
public void init(ConnectionGroup rootGroup) {
this.rootGroup = (QuickConnectionGroup)rootGroup;
this.connectionId = new AtomicInteger();
super.setObjects(this.connections);
}
/**
* Returns the current connection identifier counter and
* then increments it.
* Returns the current connection identifier counter and then increments it.
*
* @return
* An int representing the next available connection
@@ -84,13 +92,14 @@ public class QuickConnectDirectory extends SimpleDirectory<Connection> {
}
/**
* Create a SimpleConnection object from a GuacamoleConfiguration,
* obtain an identifier, and place it on the tree, returning the
* identifier value of the new connection.
* Taking a URI, parse the URI into a GuacamoleConfiguration object and
* then use the configuration to create a SimpleConnection object, obtain
* an identifier, and place it on the tree, returning the identifier value
* of the new connection.
*
* @param config
* The GuacamoleConfiguration object to use to create the
* SimpleConnection object.
* @param uri
* The URI to parse into a GuacamoleConfiguration, which will then be
* used to generate the SimpleConnection.
*
* @return
* The identifier of the connection created in the directory.
@@ -98,13 +107,20 @@ public class QuickConnectDirectory extends SimpleDirectory<Connection> {
* @throws GuacamoleException
* If an error occurs adding the object to the tree.
*/
public String create(GuacamoleConfiguration config) throws GuacamoleException {
public String create(String uri) throws GuacamoleException {
// Get the next available connection identifier.
String newConnectionId = Integer.toString(getNextConnectionID());
// Get a new QCParser
QCParser parser = new QCParser(confService.getAllowedParameters(),
confService.getDeniedParameters());
// Parse the URI into a configuration
GuacamoleConfiguration config = parser.getConfiguration(uri);
// Generate a name for the configuration.
String name = QCParser.getName(config);
String name = parser.getName(config);
// Create a new connection and set the parent identifier.
Connection connection = new SimpleConnection(name, newConnectionId, config, true);

View File

@@ -19,6 +19,7 @@
package org.apache.guacamole.auth.quickconnect;
import com.google.inject.Inject;
import java.util.Collections;
import org.apache.guacamole.auth.quickconnect.rest.QuickConnectREST;
import org.apache.guacamole.GuacamoleException;
@@ -45,32 +46,29 @@ public class QuickConnectUserContext extends AbstractUserContext {
/**
* The AuthenticationProvider that created this UserContext.
*/
private final AuthenticationProvider authProvider;
@Inject
private AuthenticationProvider authProvider;
/**
* Reference to the user whose permissions dictate the configurations
* accessible within this UserContext.
*/
private final User self;
private User self;
/**
* The Directory with access to all connections within the root group
* associated with this UserContext.
*/
private final QuickConnectDirectory connectionDirectory;
@Inject
private QuickConnectDirectory connectionDirectory;
/**
* The root connection group.
*/
private final ConnectionGroup rootGroup;
private ConnectionGroup rootGroup;
/**
* Construct a QuickConnectUserContext using the authProvider and
* the username.
*
* @param authProvider
* The authentication provider module instantiating this
* this class.
* Initialize a QuickConnectUserContext using the provided username.
*
* @param username
* The name of the user logging in that will be associated
@@ -80,8 +78,7 @@ public class QuickConnectUserContext extends AbstractUserContext {
* If errors occur initializing the ConnectionGroup,
* ConnectionDirectory, or User.
*/
public QuickConnectUserContext(AuthenticationProvider authProvider,
String username) throws GuacamoleException {
public void init(String username) throws GuacamoleException {
// Initialize the rootGroup to a QuickConnectionGroup with a
// single root identifier.
@@ -91,7 +88,7 @@ public class QuickConnectUserContext extends AbstractUserContext {
);
// Initialize the connection directory
this.connectionDirectory = new QuickConnectDirectory(this.rootGroup);
this.connectionDirectory.init(this.rootGroup);
// Initialize the user to a SimpleUser with the provided username,
// no connections, and the single root group.
@@ -109,9 +106,6 @@ public class QuickConnectUserContext extends AbstractUserContext {
};
// Set the authProvider to the calling authProvider object.
this.authProvider = authProvider;
}
@Override

View File

@@ -0,0 +1,97 @@
/*
* 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.quickconnect.conf;
import com.google.inject.Inject;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.StringListProperty;
/**
* Configuration options to control the QuickConnect module.
*/
public class ConfigurationService {
/**
* The environment of the Guacamole Server.
*/
@Inject
private Environment environment;
/**
* A list of parameters that, if set, will limit the parameters allowed to
* be defined by connections created using the QuickConnect module to only
* the parameters defined in this list. Defaults to null (all parameters
* are allowed).
*/
public static final StringListProperty QUICKCONNECT_ALLOWED_PARAMETERS = new StringListProperty() {
@Override
public String getName() { return "quickconnect-allowed-parameters"; }
};
/**
* A list of parameters that, if set, will limit the parameters allowed to
* be defined by connections created using the QuickConnect module to any
* except the ones defined in this list. Defaults to null (all parameters
* are allowed).
*/
public static final StringListProperty QUICKCONNECT_DENIED_PARAMETERS = new StringListProperty() {
@Override
public String getName() { return "quickconnect-denied-parameters"; }
};
/**
* Return the list of allowed parameters to be set by connections created
* using the QuickConnect module, or null if none are defined (thereby
* allowing all parameters to be set).
*
* @return
* The list of allowed parameters to be set by connections crated using
* the QuickConnect module.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public List<String> getAllowedParameters() throws GuacamoleException {
return environment.getProperty(QUICKCONNECT_ALLOWED_PARAMETERS);
}
/**
* Return the list of denied parameters for connections created using the
* QuickConnect module, or null if none are defined (thereby allowing all
* parameters to be set).
*
* @return
* The list of parameters that cannot be set by connections created
* using the QuickConnect module.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public List<String> getDeniedParameters() throws GuacamoleException {
return environment.getProperty(QUICKCONNECT_DENIED_PARAMETERS);
}
}

View File

@@ -36,7 +36,7 @@ import org.apache.guacamole.auth.quickconnect.utility.QCParser;
*/
@Produces(MediaType.APPLICATION_JSON)
public class QuickConnectREST {
/**
* The connection directory for this REST endpoint.
*/
@@ -74,8 +74,7 @@ public class QuickConnectREST {
public Map<String, String> create(@FormParam("uri") String uri)
throws GuacamoleException {
return Collections.singletonMap("identifier",
directory.create(QCParser.getConfiguration(uri)));
return Collections.singletonMap("identifier", directory.create(uri));
}

View File

@@ -25,9 +25,8 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.guacamole.GuacamoleServerException;
@@ -55,7 +54,48 @@ public class QCParser {
* THe regex group of the password.
*/
private static final int PASSWORD_GROUP = 2;
/**
* The list of parameters that are allowed to be placed into a configuration
* by this parser. If not defined, all parameters will be allowed unless
* explicitly denied.
*/
private final List<String> allowedParams;
/**
* The list of parameters that are explicitly denied from being placed into
* a configuration by this parser.
*/
private final List<String> deniedParams;
/**
* Create a new instance of the QCParser class, with the provided allowed
* and denied parameter lists, if any.
*
* @param allowedParams
* A list of parameters that are allowed to be parsed and placed into
* a connection configuration, or null or empty if all parameters are
* allowed.
*
* @param deniedParams
* A list of parameters, if any, that should be explicitly denied from
* being placed into a connection configuration.
*/
public QCParser(List<String> allowedParams, List<String> deniedParams) {
this.allowedParams = allowedParams;
this.deniedParams = deniedParams;
}
/**
* Create a new instance of the QCParser class, initializing the allowed
* and denied parameter lists to empty lists, which means all parameters
* will be allowed and none will be denied.
*/
public QCParser() {
this.allowedParams = Collections.emptyList();
this.deniedParams = Collections.emptyList();
}
/**
* Parse out a URI string and get a GuacamoleConfiguration
* from that string, or an exception if the parsing fails.
@@ -70,7 +110,7 @@ public class QCParser {
* @throws GuacamoleException
* If an error occurs parsing the URI.
*/
public static GuacamoleConfiguration getConfiguration(String uri)
public GuacamoleConfiguration getConfiguration(String uri)
throws GuacamoleException {
// Parse the provided String into a URI object.
@@ -104,77 +144,68 @@ public class QCParser {
"QUICKCONNECT.ERROR_NO_PROTOCOL");
// Check for provided port number
if (port > 0)
if (port > 0 && paramIsAllowed("port"))
qcConfig.setParameter("port", Integer.toString(port));
// Check for provided host, or throw an error if not present
if (host != null && !host.isEmpty())
if (host != null && !host.isEmpty() && paramIsAllowed("hostname"))
qcConfig.setParameter("hostname", host);
else
throw new TranslatableGuacamoleClientException("No host specified.",
"QUICKCONNECT.ERROR_NO_HOST");
// Look for extra query parameters and parse them out.
if (query != null && !query.isEmpty()) {
try {
Map<String, String> queryParams = parseQueryString(query);
if (queryParams != null)
for (Map.Entry<String, String> entry: queryParams.entrySet())
qcConfig.setParameter(entry.getKey(), entry.getValue());
}
catch (UnsupportedEncodingException e) {
throw new GuacamoleServerException("Unexpected lack of UTF-8 encoding support.", e);
}
}
if (query != null && !query.isEmpty())
parseQueryString(query, qcConfig);
// Look for the username and password and parse them out.
if (userInfo != null && !userInfo.isEmpty()) {
try {
if (userInfo != null && !userInfo.isEmpty())
parseUserInfo(userInfo, qcConfig);
}
catch (UnsupportedEncodingException e) {
throw new GuacamoleServerException("Unexpected lack of UTF-8 encoding support.", e);
}
}
return qcConfig;
}
/**
* Parse the given string for parameter key/value pairs and return
* a map with the parameters.
* Parse the given string for parameter key/value pairs and update the
* provided GuacamoleConfiguration object with the parsed values, checking
* to make sure that the parser is allowed to provide the requested
* parameters.
*
* @param queryStr
* The query string to parse for key/value pairs.
*
* @return
* A map with the key/value pairs.
* @param config
* The GuacamoleConfiguration object that should be updated with the
* parsed parameters.
*
* @throws UnsupportedEncodingException
* If Java lacks UTF-8 support.
* @throws GuacamoleException
* If Java unexpectedly lacks UTF-8 support.
*/
public static Map<String, String> parseQueryString(String queryStr)
throws UnsupportedEncodingException {
private void parseQueryString(String queryStr, GuacamoleConfiguration config)
throws GuacamoleException {
// Split the query string into the pairs
List<String> paramList = Arrays.asList(queryStr.split("&"));
Map<String, String> parameters = new HashMap<String,String>();
// Loop through key/value pairs and put them in the Map.
for (String param : paramList) {
String[] paramArray = param.split("=", 2);
parameters.put(URLDecoder.decode(paramArray[0], "UTF-8"),
URLDecoder.decode(paramArray[1], "UTF-8"));
try {
String paramName = URLDecoder.decode(paramArray[0], "UTF-8");
String paramValue = URLDecoder.decode(paramArray[1], "UTF-8");
if (paramIsAllowed(paramName))
config.setParameter(paramName, paramValue);
}
catch (UnsupportedEncodingException e) {
throw new GuacamoleServerException("Unexpected lack of UTF-8 encoding support.", e);
}
}
return parameters;
}
/**
* Parse the given string for username and password values,
* and, if values are present, decode them and set them in
* Parse the given string for username and password values, and, if values
* are present and allowed by the configuration, decode them and set them in
* the provided GuacamoleConfiguration object.
*
* @param userInfo
@@ -184,12 +215,12 @@ public class QCParser {
* The GuacamoleConfiguration object to store the username
* and password in.
*
* @throws UnsupportedEncodingException
* If Java lacks UTF-8 support.
* @throws GuacamoleException
* If Java unexpectedly lacks UTF-8 support.
*/
public static void parseUserInfo(String userInfo,
private void parseUserInfo(String userInfo,
GuacamoleConfiguration config)
throws UnsupportedEncodingException {
throws GuacamoleException {
Matcher userinfoMatcher = userinfoPattern.matcher(userInfo);
@@ -197,13 +228,25 @@ public class QCParser {
String username = userinfoMatcher.group(USERNAME_GROUP);
String password = userinfoMatcher.group(PASSWORD_GROUP);
if (username != null && !username.isEmpty())
config.setParameter("username",
URLDecoder.decode(username, "UTF-8"));
if (username != null && !username.isEmpty() && paramIsAllowed("username")) {
try {
config.setParameter("username",
URLDecoder.decode(username, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new GuacamoleServerException("Unexpected lack of UTF-8 encoding support.", e);
}
}
if (password != null && !password.isEmpty())
config.setParameter("password",
URLDecoder.decode(password, "UTF-8"));
if (password != null && !password.isEmpty() && paramIsAllowed("password")) {
try {
config.setParameter("password",
URLDecoder.decode(password, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new GuacamoleServerException("Unexpected lack of UTF-8 encoding support.", e);
}
}
}
}
@@ -223,7 +266,7 @@ public class QCParser {
* @throws GuacamoleException
* If an error occurs getting items in the configuration.
*/
public static String getName(GuacamoleConfiguration config)
public String getName(GuacamoleConfiguration config)
throws GuacamoleException {
if (config == null)
@@ -252,5 +295,36 @@ public class QCParser {
return name.toString();
}
/**
* For a given parameter, check to make sure the parameter is allowed to be
* used in the connection configuration, first by checking to see if
* allowed parameters are defined and the given parameter is present, then
* by checking for explicitly denied parameters. Returns false if the
* configuration prevents the parameter from being used, otherwise true.
*
* @param param
* The name of the parameter to check.
*
* @return
* False if the configuration prevents the parameter from being used,
* otherwise true.
*/
private boolean paramIsAllowed(String param) {
// If allowed parameters are defined and not empty,
// check to see if this parameter is allowed.
if (allowedParams != null && !allowedParams.isEmpty() && !allowedParams.contains(param))
return false;
// If denied parameters are defined and not empty,
// check to see if this parameter is denied.
if (deniedParams != null && !deniedParams.isEmpty() && deniedParams.contains(param))
return false;
// By default, the parameter is allowed.
return true;
}
}