GUACAMOLE-1218: Copy guacamole-auth-json source tree from glyptodon/guacamole-auth-json at commit f7b2eaf6a65b7cd25fd73437360e36fe46e0bcb9.

This commit is contained in:
Michael Jumper
2020-11-20 13:40:07 -08:00
parent cbcac3a5d5
commit cec53a24e6
23 changed files with 2919 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
import org.glyptodon.guacamole.auth.json.user.AuthenticatedUser;
import org.glyptodon.guacamole.auth.json.user.UserContext;
import org.glyptodon.guacamole.auth.json.user.UserData;
import org.glyptodon.guacamole.auth.json.user.UserDataService;
/**
* Service providing convenience functions for the JSONAuthenticationProvider.
*
* @author Michael Jumper
*/
public class AuthenticationProviderService {
/**
* Service for deriving Guacamole extension API data from UserData objects.
*/
@Inject
private UserDataService userDataService;
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<AuthenticatedUser> authenticatedUserProvider;
/**
* Provider for UserContext objects.
*/
@Inject
private Provider<UserContext> userContextProvider;
/**
* Returns an AuthenticatedUser representing the user authenticated by the
* given credentials.
*
* @param credentials
* The credentials to use for authentication.
*
* @return
* An AuthenticatedUser representing the user authenticated by the
* given credentials.
*
* @throws GuacamoleException
* If an error occurs while authenticating the user, or if access is
* denied.
*/
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Pull UserData from credentials, if possible
UserData userData = userDataService.fromCredentials(credentials);
if (userData == null)
throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.EMPTY);
// Produce AuthenticatedUser associated with derived UserData
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(credentials, userData);
return authenticatedUser;
}
/**
* Returns a UserContext object initialized with data accessible to the
* given AuthenticatedUser.
*
* @param authenticatedUser
* The AuthenticatedUser to retrieve data for.
*
* @return
* A UserContext object initialized with data accessible to the given
* AuthenticatedUser.
*
* @throws GuacamoleException
* If the UserContext cannot be created due to an error.
*/
public UserContext getUserContext(org.apache.guacamole.net.auth.AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// The JSONAuthenticationProvider only provides data for users it has
// authenticated itself
if (!(authenticatedUser instanceof AuthenticatedUser))
return null;
// Return UserContext containing data from the authenticated user's
// associated UserData object
UserContext userContext = userContextProvider.get();
userContext.init(((AuthenticatedUser) authenticatedUser).getUserData());
return userContext;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import javax.xml.bind.DatatypeConverter;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.properties.GuacamoleProperty;
/**
* A GuacamoleProperty whose value is a byte array. The bytes of the byte array
* must be represented as a hexadecimal string within the property value. The
* hexadecimal string is case-insensitive.
*
* @author Michael Jumper
*/
public abstract class ByteArrayProperty implements GuacamoleProperty<byte[]> {
@Override
public byte[] parseValue(String value) throws GuacamoleException {
// If no property provided, return null.
if (value == null)
return null;
// Return value parsed from hex
try {
return DatatypeConverter.parseHexBinary(value);
}
// Fail parse if hex invalid
catch (IllegalArgumentException e) {
throw new GuacamoleServerException("Invalid hexadecimal value.", e);
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
/**
* Service for retrieving configuration information regarding the JSON
* authentication provider.
*
* @author Michael Jumper
*/
public class ConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* The encryption key to use for all decryption and signature verification.
*/
private static final ByteArrayProperty JSON_SECRET_KEY = new ByteArrayProperty() {
@Override
public String getName() {
return "json-secret-key";
}
};
/**
* A comma-separated list of all IP addresses or CIDR subnets which should
* be allowed to perform authentication. If not specified, ALL address will
* be allowed.
*/
private static final StringListProperty JSON_TRUSTED_NETWORKS = new StringListProperty() {
@Override
public String getName() {
return "json-trusted-networks";
}
};
/**
* Returns the symmetric key which will be used to encrypt and sign all
* JSON data and should be used to decrypt and verify any received JSON
* data. This is dictated by the "json-secret-key" property specified
* within guacamole.properties.
*
* @return
* The key which should be used to decrypt received JSON data.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or if the
* "json-secret-key" property is missing.
*/
public byte[] getSecretKey() throws GuacamoleException {
return environment.getRequiredProperty(JSON_SECRET_KEY);
}
/**
* Returns a collection of all IP address or CIDR subnets which should be
* allowed to submit authentication requests. If empty, authentication
* attempts will be allowed through without restriction.
*
* @return
* A collection of all IP address or CIDR subnets which should be
* allowed to submit authentication requests.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public Collection<String> getTrustedNetworks() throws GuacamoleException {
return environment.getProperty(JSON_TRUSTED_NETWORKS, Collections.<String>emptyList());
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
/**
* Service for handling cryptography-related operations, such as decrypting
* encrypted data.
*
* @author Michael Jumper
*/
public class CryptoService {
/**
* The length of all signatures, in bytes.
*/
public static final int SIGNATURE_LENGTH = 32;
/**
* The name of the key generation algorithm used for decryption.
*/
private static final String DECRYPTION_KEY_GENERATION_ALGORITHM_NAME = "AES";
/**
* The name of the cipher transformation that should be used to decrypt any
* String provided to decrypt().
*/
private static final String DECRYPTION_CIPHER_NAME = "AES/CBC/PKCS5Padding";
/**
* The name of the key generation algorithm used for verifying signatures.
*/
private static final String SIGNATURE_KEY_GENERATION_ALGORITHM_NAME = "HmacSHA256";
/**
* The name of the MAC algorithm used for verifying signatures.
*/
private static final String SIGNATURE_MAC_ALGORITHM_NAME = "HmacSHA256";
/**
* IV which is all null bytes (all binary zeroes). Usually, using a null IV
* is a horrible idea. As our plaintext will always be prepended with the
* HMAC signature of the rest of the message, we are effectively using the
* HMAC signature itself as the IV. For our purposes, where the encrypted
* value becomes an authentication token, this is OK.
*/
private static final IvParameterSpec NULL_IV = new IvParameterSpec(new byte[] {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
});
/**
* Creates a new key suitable for decryption using the provided raw key
* bytes. The algorithm used to generate this key is dictated by
* DECRYPTION_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
* used by decrypt().
*
* @param keyBytes
* The raw bytes from which the encryption/decryption key should be
* generated.
*
* @return
* A new key suitable for encryption or decryption, generated from the
* given bytes.
*/
public SecretKey createEncryptionKey(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, DECRYPTION_KEY_GENERATION_ALGORITHM_NAME);
}
/**
* Creates a new key suitable for signature verification using the provided
* raw key bytes. The algorithm used to generate this key is dictated by
* SIGNATURE_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
* used by sign().
*
* @param keyBytes
* The raw bytes from which the signature verification key should be
* generated.
*
* @return
* A new key suitable for signature verification, generated from the
* given bytes.
*/
public SecretKey createSignatureKey(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, SIGNATURE_KEY_GENERATION_ALGORITHM_NAME);
}
/**
* Decrypts the given ciphertext using the provided key, returning the
* resulting plaintext. If any error occurs during decryption at all, a
* GuacamoleException is thrown. The IV used for the decryption process is
* a null IV (all binary zeroes).
*
* @param key
* The key to use to decrypt the provided ciphertext.
*
* @param cipherText
* The ciphertext to decrypt.
*
* @return
* The plaintext which results from decrypting the ciphertext with the
* provided key.
*
* @throws GuacamoleException
* If any error at all occurs during decryption.
*/
public byte[] decrypt(Key key, byte[] cipherText) throws GuacamoleException {
try {
// Init cipher for descryption using secret key
Cipher cipher = Cipher.getInstance(DECRYPTION_CIPHER_NAME);
cipher.init(Cipher.DECRYPT_MODE, key, NULL_IV);
// Perform decryption
return cipher.doFinal(cipherText);
}
// Rethrow all decryption failures identically
catch (InvalidAlgorithmParameterException e) {
throw new GuacamoleServerException(e);
}
catch (NoSuchAlgorithmException e) {
throw new GuacamoleServerException(e);
}
catch (NoSuchPaddingException e) {
throw new GuacamoleServerException(e);
}
catch (InvalidKeyException e) {
throw new GuacamoleServerException(e);
}
catch (IllegalBlockSizeException e) {
throw new GuacamoleServerException(e);
}
catch (BadPaddingException e) {
throw new GuacamoleServerException(e);
}
}
/**
* Signs the given arbitrary data using the provided key, returning the
* resulting signature. If any error occurs during signing at all, a
* GuacamoleException is thrown.
*
* @param key
* The key to use to sign the provided data.
*
* @param data
* The arbitrary data to sign.
*
* @return
* The signature which results from signing the arbitrary data with the
* provided key.
*
* @throws GuacamoleException
* If any error at all occurs during signing.
*/
public byte[] sign(Key key, byte[] data) throws GuacamoleException {
try {
// Init MAC for signing using secret key
Mac mac = Mac.getInstance(SIGNATURE_MAC_ALGORITHM_NAME);
mac.init(key);
// Sign provided data
return mac.doFinal(data);
}
// Rethrow all signature failures identically
catch (NoSuchAlgorithmException e) {
throw new GuacamoleServerException(e);
}
catch (InvalidKeyException e) {
throw new GuacamoleServerException(e);
}
catch (IllegalStateException e) {
throw new GuacamoleServerException(e);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
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.UserContext;
/**
* Allows users to be authenticated using encrypted blobs of JSON data. The
* username of the user, all available connections, and the parameters
* associated with those connections are all determined by the contents of the
* provided JSON. The JSON itself is authorized by virtue of being properly
* encrypted with a shared key.
*
* @author Michael Jumper
*/
public class JSONAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* Injector which will manage the object graph of this authentication
* provider.
*/
private final Injector injector;
/**
* Creates a new JSON AuthenticationProvider that authenticates users
* using encrypted blobs of JSON data.
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public JSONAuthenticationProvider() throws GuacamoleException {
// Set up Guice injector.
injector = Guice.createInjector(new JSONAuthenticationProviderModule(this));
}
@Override
public String getIdentifier() {
return "json";
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException {
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.authenticateUser(credentials);
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.getUserContext(authenticatedUser);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import com.google.inject.AbstractModule;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.glyptodon.guacamole.auth.json.connection.ConnectionService;
import org.glyptodon.guacamole.auth.json.user.UserDataService;
/**
* Guice module which configures injections specific to the JSON authentication
* provider.
*
* @author Michael Jumper
*/
public class JSONAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* A reference to the JSONAuthenticationProvider on behalf of which this
* module has configured injection.
*/
private final AuthenticationProvider authProvider;
/**
* Creates a new JSON authentication provider module which configures
* injection for the JSONAuthenticationProvider.
*
* @param authProvider
* The AuthenticationProvider for which injection is being configured.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public JSONAuthenticationProviderModule(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 JSON-specific services
bind(ConfigurationService.class);
bind(ConnectionService.class);
bind(CryptoService.class);
bind(RequestValidationService.class);
bind(UserDataService.class);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.util.matcher.IpAddressMatcher;
/**
* Service for testing the validity of received HTTP requests.
*
* @author Michael Jumper
*/
public class RequestValidationService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(RequestValidationService.class);
/**
* Service for retrieving configuration information regarding the
* JSONAuthenticationProvider.
*/
@Inject
private ConfigurationService confService;
/**
* Returns whether the given request can be used for authentication, taking
* into account restrictions specified within guacamole.properties.
*
* @param request
* The HTTP request to test.
*
* @return
* true if the given request comes from a trusted source and can be
* used for authentication, false otherwise.
*/
public boolean isAuthenticationAllowed(HttpServletRequest request) {
// Pull list of all trusted networks
Collection<String> trustedNetworks;
try {
trustedNetworks = confService.getTrustedNetworks();
}
// Deny all requests if restrictions cannot be parsed
catch (GuacamoleException e) {
logger.warn("Authentication request from \"{}\" is DENIED due to parse error: {}", request.getRemoteAddr(), e.getMessage());
logger.debug("Error parsing authentication request restrictions from guacamole.properties.", e);
return false;
}
// All requests are allowed if no restrictions are defined
if (trustedNetworks.isEmpty()) {
logger.debug("Authentication request from \"{}\" is ALLOWED (no restrictions).", request.getRemoteAddr());
return true;
}
// Build matchers for each trusted network
Collection<IpAddressMatcher> matchers = new ArrayList<IpAddressMatcher>(trustedNetworks.size());
for (String network : trustedNetworks)
matchers.add(new IpAddressMatcher(network));
// Otherwise ensure at least one subnet matches
for (IpAddressMatcher matcher : matchers) {
// Request is allowed if any subnet matches
if (matcher.matches(request)) {
logger.debug("Authentication request from \"{}\" is ALLOWED (matched subnet).", request.getRemoteAddr());
return true;
}
}
// Otherwise request is denied - no subnets matched
logger.debug("Authentication request from \"{}\" is DENIED (did not match subnet).", request.getRemoteAddr());
return false;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.properties.GuacamoleProperty;
/**
* A GuacamoleProperty whose value is a List of Strings. The string value
* parsed to produce this list is a comma-delimited list. Duplicate values are
* ignored, as is any whitespace following delimiters. To maintain
* compatibility with the behavior of Java properties in general, only
* whitespace at the beginning of each value is ignored; trailing whitespace
* becomes part of the value.
*
* @author Michael Jumper
*/
public abstract class StringListProperty implements GuacamoleProperty<List<String>> {
/**
* A pattern which matches against the delimiters between values. This is
* currently simply a comma and any following whitespace. Parts of the
* input string which match this pattern will not be included in the parsed
* result.
*/
private static final Pattern DELIMITER_PATTERN = Pattern.compile(",\\s*");
@Override
public List<String> parseValue(String values) throws GuacamoleException {
// If no property provided, return null.
if (values == null)
return null;
// Split string into a list of individual values
List<String> stringValues = Arrays.asList(DELIMITER_PATTERN.split(values));
if (stringValues.isEmpty())
return null;
return stringValues;
}
}

View File

@@ -0,0 +1,333 @@
/*
* Copyright (C) 2018 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
* NOTE: The code implemented provided here for establishing connections is
* based upon the connect() function of the SimpleConnection class, part of the
* "guacamole-ext" library, which is part of Apache Guacamole. The relevant
* code has been modified to suit the purposes of this extension.
*/
/*
* 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.glyptodon.guacamole.auth.json.connection;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SSLGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
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.glyptodon.guacamole.auth.json.user.UserData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service which provides a centralized means of establishing connections,
* tracking/joining active connections, and retrieving associated data.
*
* @author Michael Jumper
*/
@Singleton
public class ConnectionService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Mapping of the unique IDs of active connections (as specified within the
* UserData.Connection object) to the underlying connection ID (as returned
* via the Guacamole protocol handshake). Only connections with defined IDs
* are tracked here.
*/
private final ConcurrentHashMap<String, String> activeConnections =
new ConcurrentHashMap<>();
/**
* Mapping of the connection IDs of joinable connections (as returned via
* the Guacamole protocol handshake) to the Collection of tunnels shadowing
* those connections.
*/
private final ConcurrentHashMap<String, Collection<GuacamoleTunnel>> shadowers =
new ConcurrentHashMap<>();
/**
* Generates a new GuacamoleConfiguration from the associated protocol and
* parameters of the given UserData.Connection. If the configuration cannot
* be generated (because a connection is being joined by that connection is
* not actually active), null is returned.
*
* @param connection
* The UserData.Connection whose protocol and parameters should be used
* to construct the new GuacamoleConfiguration.
*
* @return
* A new GuacamoleConfiguration generated from the associated protocol
* and parameters of the given UserData.Connection, or null if the
* configuration cannot be generated.
*/
public GuacamoleConfiguration getConfiguration(UserData.Connection connection) {
GuacamoleConfiguration config = new GuacamoleConfiguration();
// Set connection ID if joining an active connection
String primaryConnection = connection.getPrimaryConnection();
if (primaryConnection != null) {
// Verify that the connection being joined actually exists
String id = activeConnections.get(primaryConnection);
if (id == null)
return null;
config.setConnectionID(id);
}
// Otherwise, require protocol
else
config.setProtocol(connection.getProtocol());
// Add all parameter name/value pairs
Map<String, String> parameters = connection.getParameters();
if (parameters != null)
config.setParameters(parameters);
return config;
}
/**
* Closes all tunnels within the given connection. If a GuacamoleException
* is thrown by any tunnel during closure, that exception is ignored.
*
* @param tunnels
* The Collection of tunnels to close.
*/
private void closeAll(Collection<GuacamoleTunnel> tunnels) {
for (GuacamoleTunnel tunnel : tunnels) {
try {
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Failure to close tunnel masked by closeAll().", e);
}
}
}
/**
* Establishes a connection to guacd using the information associated with
* the given connection object. The resulting connection will be provided
* the given client information during the Guacamole protocol handshake.
*
* @param connection
* The connection object describing the nature of the connection to be
* established.
*
* @param info
* Information associated with the connecting client.
*
* @return
* A fully-established GuacamoleTunnel.
*
* @throws GuacamoleException
* If an error occurs while connecting to guacd, or if permission to
* connect is denied.
*/
public GuacamoleTunnel connect(UserData.Connection connection,
GuacamoleClientInformation info) throws GuacamoleException {
// Retrieve proxy configuration from environment
GuacamoleProxyConfiguration proxyConfig = environment.getDefaultGuacamoleProxyConfiguration();
// Get guacd connection parameters
String hostname = proxyConfig.getHostname();
int port = proxyConfig.getPort();
// Generate and verify connection configuration
GuacamoleConfiguration config = getConfiguration(connection);
if (config == null) {
logger.debug("Configuration for connection could not be "
+ "generated. Perhaps the connection being joined is not "
+ "active?");
throw new GuacamoleResourceNotFoundException("No such connection");
}
// Determine socket type based on required encryption method
final ConfiguredGuacamoleSocket socket;
switch (proxyConfig.getEncryptionMethod()) {
// If guacd requires SSL, use it
case SSL:
socket = new ConfiguredGuacamoleSocket(
new SSLGuacamoleSocket(hostname, port),
config, info
);
break;
// Connect directly via TCP if encryption is not enabled
case NONE:
socket = new ConfiguredGuacamoleSocket(
new InetGuacamoleSocket(hostname, port),
config, info
);
break;
// Abort if encryption method is unknown
default:
throw new GuacamoleServerException("Unimplemented encryption method.");
}
final GuacamoleTunnel tunnel;
// If the current connection is not being tracked (no ID) just use a
// normal, non-tracking tunnel
final String id = connection.getId();
if (id == null)
tunnel = new SimpleGuacamoleTunnel(socket);
// Otherwise, create a tunnel with proper tracking which can be joined
else {
// Allow connection to be joined
final String connectionID = socket.getConnectionID();
final Collection<GuacamoleTunnel> existingTunnels = shadowers.putIfAbsent(connectionID,
Collections.synchronizedList(new ArrayList<>()));
// Duplicate connection IDs cannot exist
assert(existingTunnels == null);
// If the current connection is intended to be tracked (an ID was
// provided), but a connection is already in progress with that ID,
// log a warning that the original connection will no longer be tracked
String activeConnection = activeConnections.put(id, connectionID);
if (activeConnection != null)
logger.warn("A connection with ID \"{}\" is already in progress, "
+ "but another attempt to use this ID has been made. The "
+ "original connection will no longer be joinable.", id);
// Return a tunnel which automatically tracks the active connection
tunnel = new SimpleGuacamoleTunnel(new GuacamoleSocket() {
@Override
public GuacamoleReader getReader() {
return socket.getReader();
}
@Override
public GuacamoleWriter getWriter() {
return socket.getWriter();
}
@Override
public void close() throws GuacamoleException {
// Stop connection from being joined further
activeConnections.remove(id, connectionID);
// Close all connections sharing the closed connection
Collection<GuacamoleTunnel> tunnels = shadowers.remove(connectionID);
if (tunnels != null)
closeAll(tunnels);
socket.close();
}
@Override
public boolean isOpen() {
return socket.isOpen();
}
});
}
// Track tunnels which join connections, such that they can be
// automatically closed when the joined connection closes
String joinedConnection = config.getConnectionID();
if (joinedConnection != null) {
// Track shadower of joined connection if possible
Collection<GuacamoleTunnel> tunnels = shadowers.get(joinedConnection);
if (tunnels != null)
tunnels.add(tunnel);
// Close this tunnel in ALL CASES if the joined connection has
// closed. Note that it is insufficient to simply check whether the
// retrieved Collection is null here, as it may have been removed
// after retrieval. We must ensure that the tunnel is closed in any
// case where it will not automatically be closed due to the
// closure of the shadowed connection.
if (!shadowers.containsKey(joinedConnection))
tunnel.close();
}
return tunnel;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import com.google.inject.Inject;
import org.apache.guacamole.net.auth.AbstractAuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
/**
* An implementation of AuthenticatedUser specific to the
* JSONAuthenticationProvider, providing access to the decrypted contents of
* the JSON provided during authentication.
*
* @author Michael Jumper
*/
public class AuthenticatedUser extends AbstractAuthenticatedUser {
/**
* Reference to the authentication provider associated with this
* authenticated user.
*/
@Inject
private AuthenticationProvider authProvider;
/**
* The credentials provided when this user was authenticated.
*/
private Credentials credentials;
/**
* The UserData object derived from the data submitted when this user was
* authenticated.
*/
private UserData userData;
/**
* Initializes this AuthenticatedUser using the given credentials and
* UserData object. The provided UserData object MUST have been derived
* from the data submitted when the user authenticated.
*
* @param credentials
* The credentials provided when this user was authenticated.
*
* @param userData
* The UserData object derived from the data submitted when this user
* was authenticated.
*/
public void init(Credentials credentials, UserData userData) {
this.credentials = credentials;
this.userData = userData;
setIdentifier(userData.getUsername());
}
@Override
public AuthenticationProvider getAuthenticationProvider() {
return authProvider;
}
@Override
public Credentials getCredentials() {
return credentials;
}
/**
* Returns the UserData object derived from the data submitted when this
* user was authenticated.
*
* @return
* The UserData object derived from the data submitted when this user
* was authenticated.
*/
public UserData getUserData() {
return userData;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import com.google.inject.Inject;
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;
/**
* An implementation of UserContext specific to the JSONAuthenticationProvider
* which obtains all data from the encrypted JSON provided during
* authentication.
*
* @author Michael Jumper
*/
public class UserContext extends AbstractUserContext {
/**
* The identifier reserved for the root connection group.
*/
public static final String ROOT_CONNECTION_GROUP = DEFAULT_ROOT_CONNECTION_GROUP;
/**
* Reference to the AuthenticationProvider associated with this
* UserContext.
*/
@Inject
private AuthenticationProvider authProvider;
/**
* Service for deriving Guacamole extension API data from UserData objects.
*/
@Inject
private UserDataService userDataService;
/**
* The UserData object associated with the user to whom this UserContext
* belongs.
*/
private UserData userData;
/**
* Initializes this UserContext using the data associated with the provided
* UserData object.
*
* @param userData
* The UserData object derived from the JSON data received when the
* user authenticated.
*/
public void init(UserData userData) {
this.userData = userData;
}
@Override
public User self() {
return userDataService.getUser(userData);
}
@Override
public AuthenticationProvider getAuthenticationProvider() {
return authProvider;
}
@Override
public Directory<Connection> getConnectionDirectory() {
return userDataService.getConnectionDirectory(userData);
}
}

View File

@@ -0,0 +1,393 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
/**
* All data associated with a particular user, as parsed from the JSON supplied
* within the encrypted blob provided during authentication.
*
* @author Michael Jumper
*/
public class UserData {
/**
* The username of the user associated with this data.
*/
private String username;
/**
* The time after which this data is no longer valid and must not be used.
* This is a UNIX-style epoch timestamp, stored as the number of
* milliseconds since midnight of January 1, 1970 UTC.
*/
private Long expires;
/**
* Whether this data can only be used once. If set to true, reuse of the
* associated signed data will not be allowed. This is only valid if the
* expiration timestamp has been set.
*/
private boolean singleUse = false;
/**
* All connections accessible by this user. The key of each entry is both
* the connection identifier and the connection name.
*/
private ConcurrentMap<String, Connection> connections;
/**
* The data associated with a Guacamole connection stored within a UserData
* object.
*
* @author Michael Jumper
*/
public static class Connection {
/**
* An arbitrary, opaque, unique ID for this connection. If specified
* via the "join" (primaryConnection) property of another connection,
* that connection may be used to join this connection.
*/
private String id;
/**
* The protocol that this connection should use, such as "vnc" or "rdp".
*/
private String protocol;
/**
* The opaque ID of the connection being joined (shared), as given with
* the "id" property. If specified, the provided protocol is ignored.
* This value is exposed via the "join" property within JSON.
*/
private String primaryConnection;
/**
* Map of all connection parameter values, where each key is the parameter
* name. Legal parameter names are dictated by the specified protocol and
* are documented within the Guacamole manual:
*
* http://guac-dev.org/doc/gug/configuring-guacamole.html#connection-configuration
*/
private Map<String, String> parameters;
/**
* Whether this connection can only be used once. If set to true, the
* connection will be removed from the connections directory
* immediately upon use.
*/
private boolean singleUse = false;
/**
* Returns an arbitrary, opaque, unique ID for this connection. If
* defined, this ID may be used via the "join" (primaryConnection)
* property of another connection to join (share) this connection while
* it is in progress.
*
* @return
* An arbitrary, opaque, unique ID for this connection.
*/
public String getId() {
return id;
}
/**
* Sets an arbitrary, opaque ID which uniquely identifies this
* connection. This ID may be used via the "join" (primaryConnection)
* property of another connection to join (share) this connection while
* it is in progress.
*
* @param id
* An arbitrary, opaque, unique ID for this connection.
*/
public void setId(String id) {
this.id = id;
}
/**
* Returns the protocol that this connection should use, such as "vnc"
* or "rdp".
*
* @return
* The name of the protocol to use, such as "vnc" or "rdp".
*/
public String getProtocol() {
return protocol;
}
/**
* Sets the protocol that this connection should use, such as "vnc"
* or "rdp".
*
* @param protocol
* The name of the protocol to use, such as "vnc" or "rdp".
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Returns the opaque ID of the connection being joined (shared), if
* any. If specified, any provided protocol is ignored. This value is
* exposed via the "join" property within JSON.
*
* @return
* The opaque ID of the connection being joined (shared), if any.
*/
@JsonProperty("join")
public String getPrimaryConnection() {
return primaryConnection;
}
/**
* Sets the opaque ID of the connection being joined (shared). If
* specified, any provided protocol is ignored. This is exposed via the
* "join" property within JSON.
*
* @param primaryConnection
* The opaque ID of the connection being joined (shared).
*/
@JsonProperty("join")
public void setPrimaryConnection(String primaryConnection) {
this.primaryConnection = primaryConnection;
}
/**
* Returns a map of all parameter name/value pairs, where the key of
* each entry in the map is the corresponding parameter name. Changes
* to this map directly affect the parameters associated with this
* connection.
*
* @return
* A map of all parameter name/value pairs associated with this
* connection.
*/
public Map<String, String> getParameters() {
return parameters;
}
/**
* Replaces all parameters associated with this connection with the
* name/value pairs in the provided map, where the key of each entry
* in the map is the corresponding parameter name. Changes to this map
* directly affect the parameters associated with this connection.
*
* @param parameters
* The map of all parameter name/value pairs to associate with this
* connection.
*/
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
/**
* Returns whether this connection is intended for single-use only. A
* single-use connection cannot be used more than once.
*
* After a single-use connection is used, it should be automatically
* and atomically removed from any underlying data (such as with
* UserData.removeConnection()).
*
* @return
* true if this connection is intended for single-use only, false
* otherwise.
*/
public boolean isSingleUse() {
return singleUse;
}
/**
* Sets whether this connection is intended for single-use only. A
* single-use connection cannot be used more than once. By default,
* connections are NOT single-use.
*
* After a single-use connection is used, it should be automatically
* and atomically removed from any underlying data (such as with
* UserData.removeConnection()).
*
* @param singleUse
* true if this connection is intended for single-use only, false
* otherwise.
*/
public void setSingleUse(boolean singleUse) {
this.singleUse = singleUse;
}
}
/**
* Returns the username of the user associated with the data stored in this
* object.
*
* @return
* The username of the user associated with the data stored in this
* object.
*/
public String getUsername() {
return username;
}
/**
* Sets the username of the user associated with the data stored in this
* object.
*
* @param username
* The username of the user to associate with the data stored in this
* object.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the time after which the data stored in this object is invalid
* and must not be used. The time returned is a UNIX-style epoch timestamp
* whose value is the number of milliseconds since midnight of January 1,
* 1970 UTC. If this object does not expire, null is returned.
*
* @return
* The time after which the data stored in this object is invalid and
* must not be used, or null if this object does not expire.
*/
public Long getExpires() {
return expires;
}
/**
* Sets the time after which the data stored in this object is invalid
* and must not be used. The time provided MUST be a UNIX-style epoch
* timestamp whose value is the number of milliseconds since midnight of
* January 1, 1970 UTC. If this object should not expire, the value
* provided should be null.
*
* @param expires
* The time after which the data stored in this object is invalid and
* must not be used, or null if this object does not expire.
*/
public void setExpires(Long expires) {
this.expires = expires;
}
/**
* Returns whether this user data is intended for single-use only.
* Single-use data cannot be used more than once. This flag only has
* meaning if the data also has an expires timestamp.
*
* @return
* true if this data is intended for single-use only, false
* otherwise.
*/
public boolean isSingleUse() {
return singleUse;
}
/**
* Sets whether this user data is intended for single-use only. Single-use
* data cannot be used more than once. This flag only has meaning if the
* data also has an expires timestamp. By default, user data is NOT
* single-use.
*
* @param singleUse
* true if this data is intended for single-use only, false
* otherwise.
*/
public void setSingleUse(boolean singleUse) {
this.singleUse = singleUse;
}
/**
* Returns all connections stored within this UserData object as an
* unmodifiable map. Each of these connections is accessible by the user
* specified by getUsername(). The key of each entry within the map is the
* identifier and human-readable name of the corresponding connection.
*
* @return
* An unmodifiable map of all connections stored within this
* UserData object, where the key of each entry is the identifier of
* the corresponding connection.
*/
public Map<String, Connection> getConnections() {
return connections == null ? null : Collections.unmodifiableMap(connections);
}
/**
* Replaces all connections stored within this UserData object with the
* given connections. Each of these connections will be accessible by the
* user specified by getUsername(). The key of each entry within the map is
* the identifier and human-readable name of the corresponding connection.
*
* @param connections
* A map of all connections to be stored within this UserData object,
* where the key of each entry is the identifier of the corresponding
* connection.
*/
public void setConnections(Map<String, Connection> connections) {
this.connections = new ConcurrentHashMap<String, Connection>(connections);
}
/**
* Removes the connection having the given identifier from the overall map
* of connections, such that it cannot be used further. This operation is
* atomic.
*
* @param identifier
* The identifier of the connection to remove.
*
* @return
* The connection that was removed, or null if no such connection
* exists.
*/
public Connection removeConnection(String identifier) {
return connections.remove(identifier);
}
/**
* Returns whether the data within this UserData object is expired, and
* thus must not be used, according to the timestamp returned by
* getExpires().
*
* @return
* true if the data within this UserData object is expired and must not
* be used, false otherwise.
*/
@JsonIgnore
public boolean isExpired() {
// Do not bother comparing if this UserData object does not expire
Long expirationTimestamp = getExpires();
if (expirationTimestamp == null)
return false;
// Otherwise, compare expiration timestamp against system time
return System.currentTimeMillis() > expirationTimestamp;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Atomic blacklist of UserData objects, stored by their associated
* cryptographic signatures. UserData objects stored within this blacklist MUST
* have an associated expiration timestamp, and will automatically be removed
* from the blacklist once they have expired.
*
* @author Michael Jumper
*/
public class UserDataBlacklist {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(UserDataBlacklist.class);
/**
* All blacklisted UserData objects, stored by their associated
* cryptographic signatures. NOTE: Each key into this map is the hex
* string produced by encoding the binary signature using DatatypeConverter.
* A byte[] cannot be used directly.
*/
private final ConcurrentMap<String, UserData> blacklist =
new ConcurrentHashMap<String, UserData>();
/**
* Removes all expired UserData objects from the blacklist. This will
* automatically be invoked whenever new UserData is added to the blacklist.
*/
public void removeExpired() {
// Remove expired data from blacklist
Iterator<Map.Entry<String, UserData>> current = blacklist.entrySet().iterator();
while (current.hasNext()) {
// Remove entry from map if its associated with expired data
Map.Entry<String, UserData> entry = current.next();
if (entry.getValue().isExpired())
current.remove();
}
}
/**
* Adds the given UserData to the blacklist, storing it according to the
* provided cryptographic signature. The UserData MUST have an associated
* expiration timestamp. If any UserData objects already within the
* blacklist have expired, they will automatically be removed when this
* function is invoked.
*
* @param data
* The UserData to store within the blacklist.
*
* @param signature
* The cryptographic signature associated with the UserData.
*
* @return
* true if the UserData was not already blacklisted and has
* successfully been added, false otherwise.
*/
public boolean add(UserData data, byte[] signature) {
// Expiration timestamps must be provided
if (data.getExpires() == null) {
logger.warn("An expiration timestamp MUST be provided for "
+ "single-use data.");
return false;
}
// Remove any expired entries
removeExpired();
// Expired user data is implicitly blacklisted
if (data.isExpired())
return false;
// Add to blacklist only if not already present
String signatureHex = DatatypeConverter.printHexBinary(signature);
return blacklist.putIfAbsent(signatureHex, data) == null;
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2016 Glyptodon, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionRecord;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.glyptodon.guacamole.auth.json.connection.ConnectionService;
/**
* Connection implementation which automatically manages related UserData if
* the connection is used. Connections which are marked as single-use will
* be removed from the given UserData such that only the first connection
* attempt can succeed.
*
* @author Michael Jumper
*/
public class UserDataConnection implements Connection {
/**
* Service for establishing and managing connections.
*/
@Inject
private ConnectionService connectionService;
/**
* A human-readable value which both uniquely identifies this connection
* and serves as the connection display name.
*/
private String identifier;
/**
* The UserData associated with this connection. This UserData will be
* automatically updated as this connection is used.
*/
private UserData data;
/**
* The connection entry for this connection within the associated UserData.
*/
private UserData.Connection connection;
/**
* Initializes this UserDataConnection with the given data, unique
* identifier, and connection information. This function MUST be invoked
* before any particular UserDataConnection is actually used.
*
* @param data
* The UserData that this connection should manage.
*
* @param identifier
* The identifier associated with this connection within the given
* UserData.
*
* @param connection
* The connection data associated with this connection within the given
* UserData.
*
* @return
* A reference to this UserDataConnection.
*/
public UserDataConnection init(UserData data, String identifier,
UserData.Connection connection) {
this.identifier = identifier;
this.data = data;
this.connection = connection;
return this;
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public void setIdentifier(String identifier) {
throw new UnsupportedOperationException("UserDataConnection is immutable.");
}
@Override
public String getName() {
return identifier;
}
@Override
public void setName(String name) {
throw new UnsupportedOperationException("UserDataConnection is immutable.");
}
@Override
public String getParentIdentifier() {
return UserContext.ROOT_CONNECTION_GROUP;
}
@Override
public void setParentIdentifier(String parentIdentifier) {
throw new UnsupportedOperationException("UserDataConnection is immutable.");
}
@Override
public GuacamoleConfiguration getConfiguration() {
// Generate configuration, using a skeleton configuration if generation
// fails
GuacamoleConfiguration config = connectionService.getConfiguration(connection);
if (config == null)
config = new GuacamoleConfiguration();
return config;
}
@Override
public void setConfiguration(GuacamoleConfiguration config) {
throw new UnsupportedOperationException("UserDataConnection is immutable.");
}
@Override
public Map<String, String> getAttributes() {
return Collections.<String, String>emptyMap();
}
@Override
public void setAttributes(Map<String, String> attributes) {
throw new UnsupportedOperationException("UserDataConnection is immutable.");
}
@Override
public Date getLastActive() {
return null;
}
@Override
public List<? extends ConnectionRecord> getHistory() throws GuacamoleException {
return Collections.<ConnectionRecord>emptyList();
}
@Override
public Set<String> getSharingProfileIdentifiers() throws GuacamoleException {
return Collections.<String>emptySet();
}
@Override
public int getActiveConnections() {
return 0;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info)
throws GuacamoleException {
// Prevent future use immediately upon connect
if (connection.isSingleUse()) {
// Deny access if another user already used the connection
if (data.removeConnection(getIdentifier()) == null)
throw new GuacamoleSecurityException("Permission denied");
}
// Perform connection operation
return connectionService.connect(connection, info);
}
}

View File

@@ -0,0 +1,378 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.auth.json.user;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.Credentials;
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.codehaus.jackson.map.ObjectMapper;
import org.glyptodon.guacamole.auth.json.ConfigurationService;
import org.glyptodon.guacamole.auth.json.CryptoService;
import org.glyptodon.guacamole.auth.json.RequestValidationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for deriving Guacamole extension API data from UserData objects.
*
* @author Michael Jumper
*/
@Singleton
public class UserDataService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(UserDataService.class);
/**
* ObjectMapper for deserializing UserData objects.
*/
private static final ObjectMapper mapper = new ObjectMapper();
/**
* Blacklist of single-use user data objects which have already been used.
*/
private final UserDataBlacklist blacklist = new UserDataBlacklist();
/**
* Service for retrieving configuration information regarding the
* JSONAuthenticationProvider.
*/
@Inject
private ConfigurationService confService;
/**
* Service for testing the validity of HTTP requests.
*/
@Inject
private RequestValidationService requestService;
/**
* Service for handling cryptography-related operations.
*/
@Inject
private CryptoService cryptoService;
/**
* Provider for UserDataConnection instances.
*/
@Inject
private Provider<UserDataConnection> userDataConnectionProvider;
/**
* The name of the HTTP parameter from which base64-encoded, encrypted JSON
* data should be read. The value of this parameter, when decoded and
* decrypted, must be valid JSON prepended with the 32-byte raw binary
* signature generated through signing the JSON with the secret key using
* HMAC/SHA-256.
*/
public static final String ENCRYPTED_DATA_PARAMETER = "data";
/**
* Derives a new UserData object from the data contained within the given
* Credentials. If no such data is present, or the data present is invalid,
* null is returned.
*
* @param credentials
* The Credentials from which the new UserData object should be
* derived.
*
* @return
* A new UserData object derived from the data contained within the
* given Credentials, or null if no such data is present or if the data
* present is invalid.
*/
public UserData fromCredentials(Credentials credentials) {
String json;
byte[] correctSignature;
// Pull HTTP request, if available
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// Abort if the request itself is not allowed
if (!requestService.isAuthenticationAllowed(request))
return null;
// Pull base64-encoded, encrypted JSON data from HTTP request, if any
// such data is present
String base64 = request.getParameter(ENCRYPTED_DATA_PARAMETER);
if (base64 == null)
return null;
// Decrypt base64-encoded parameter
try {
// Decrypt using defined encryption key
byte[] decrypted = cryptoService.decrypt(
cryptoService.createEncryptionKey(confService.getSecretKey()),
DatatypeConverter.parseBase64Binary(base64)
);
// Abort if decrypted value cannot possibly have a signature AND data
if (decrypted.length <= CryptoService.SIGNATURE_LENGTH) {
logger.warn("Submitted data is too small to contain both a signature and JSON.");
return null;
}
// Split data into signature and JSON portions
byte[] receivedSignature = Arrays.copyOf(decrypted, CryptoService.SIGNATURE_LENGTH);
byte[] receivedJSON = Arrays.copyOfRange(decrypted, CryptoService.SIGNATURE_LENGTH, decrypted.length);
// Produce signature for decrypted data
correctSignature = cryptoService.sign(
cryptoService.createSignatureKey(confService.getSecretKey()),
receivedJSON
);
// Verify signatures
if (!Arrays.equals(receivedSignature, correctSignature)) {
logger.warn("Signature of submitted data is incorrect.");
return null;
}
// Convert from UTF-8
json = new String(receivedJSON, "UTF-8");
}
// Fail if base64 data is not valid
catch (IllegalArgumentException e) {
logger.warn("Submitted data is not proper base64.");
logger.debug("Invalid base64 data.", e);
return null;
}
// Handle lack of standard UTF-8 support (should never happen)
catch (UnsupportedEncodingException e) {
logger.error("Unexpected lack of support for UTF-8: {}", e.getMessage());
logger.debug("Unable to decode base64 data as UTF-8.", e);
return null;
}
// Fail if decryption or key retrieval fails for any reason
catch (GuacamoleException e) {
logger.error("Decryption of received data failed: {}", e.getMessage());
logger.debug("Unable to decrypt received data.", e);
return null;
}
// Deserialize UserData from submitted JSON data
try {
// Deserialize UserData, but reject if expired
UserData userData = mapper.readValue(json, UserData.class);
if (userData.isExpired())
return null;
// Reject if data is single-use and already present in the blacklist
if (userData.isSingleUse() && !blacklist.add(userData, correctSignature))
return null;
return userData;
}
// Fail UserData creation if JSON is invalid/unreadable
catch (IOException e) {
logger.error("Received JSON is invalid: {}", e.getMessage());
logger.debug("Error parsing UserData JSON.", e);
return null;
}
}
/**
* Returns the identifiers of all users readable by the user whose data is
* given by the provided UserData object. As users of the
* JSONAuthenticationProvider can only see themselves, this will always
* simply be a set of the user's own username.
*
* @param userData
* All data associated with the user whose accessible user identifiers
* are being retrieved.
*
* @return
* A set containing the identifiers of all users readable by the user
* whose data is given by the provided UserData object.
*/
public Set<String> getUserIdentifiers(UserData userData) {
// Each user can only see themselves
return Collections.singleton(userData.getUsername());
}
/**
* Returns the user object of the user to whom the given UserData object
* belongs.
*
* @param userData
* All data associated with the user whose own user object is being
* retrieved.
*
* @return
* The user object of the user to whom the given UserData object
* belongs.
*/
public User getUser(UserData userData) {
// Build user object with READ access to all available data
return new SimpleUser(userData.getUsername()) {
@Override
public ObjectPermissionSet getUserPermissions() throws GuacamoleException {
return new SimpleObjectPermissionSet(getUserIdentifiers(userData));
}
@Override
public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException {
return new SimpleObjectPermissionSet(getConnectionIdentifiers(userData));
}
@Override
public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException {
return new SimpleObjectPermissionSet(getConnectionGroupIdentifiers(userData));
}
};
}
/**
* Returns the identifiers of all connections readable by the user whose
* data is given by the provided UserData object. If the provided UserData
* is not expired, this will be the set of all connection identifiers
* within the UserData. If the UserData is expired, this will be an empty
* set.
*
* @param userData
* All data associated with the user whose accessible connection
* identifiers are being retrieved.
*
* @return
* A set containing the identifiers of all connections readable by the
* user whose data is given by the provided UserData object.
*/
public Set<String> getConnectionIdentifiers(UserData userData) {
// Do not return any connections if empty or expired
Map<String, UserData.Connection> connections = userData.getConnections();
if (connections == null || userData.isExpired())
return Collections.<String>emptySet();
// Return all available connection identifiers
return connections.keySet();
}
/**
* Returns a Directory containing all connections accessible by the user
* whose data is given by the provided UserData object. If the given
* UserData object is not expired, this Directory will contain absolutely
* all connections defined within the given UserData. If the given UserData
* object is expired, this Directory will be empty.
*
* @param userData
* All data associated with the user whose connection directory is
* being retrieved.
*
* @return
* A Directory containing all connections accessible by the user whose
* data is given by the provided UserData object.
*/
public Directory<Connection> getConnectionDirectory(UserData userData) {
// Do not return any connections if empty or expired
Map<String, UserData.Connection> connections = userData.getConnections();
if (connections == null || userData.isExpired())
return new SimpleDirectory<>();
// Convert UserData.Connection objects to normal Connections
Map<String, Connection> directoryContents = new HashMap<>();
for (Map.Entry<String, UserData.Connection> entry : connections.entrySet()) {
// Pull connection and associated identifier
String identifier = entry.getKey();
UserData.Connection connection = entry.getValue();
// Create Guacamole connection containing the defined identifier
// and parameters
Connection guacConnection = userDataConnectionProvider.get().init(
userData,
identifier,
connection
);
// Add corresponding Connection to directory
directoryContents.put(identifier, guacConnection);
}
return new SimpleDirectory<>(directoryContents);
}
/**
* Returns the identifiers of all connection groups readable by the user
* whose data is given by the provided UserData object. This will always be
* a set containing only the root connection group identifier. The
* JSONAuthenticationProvider does not define any other connection groups.
*
* @param userData
* All data associated with the user whose accessible connection group
* identifiers are being retrieved.
*
* @return
* A set containing the identifiers of all connection groups readable
* by the user whose data is given by the provided UserData object.
*/
public Set<String> getConnectionGroupIdentifiers(UserData userData) {
// The only connection group available is the root group
return Collections.singleton(UserContext.ROOT_CONNECTION_GROUP);
}
}

View File

@@ -0,0 +1,16 @@
{
"guacamoleVersion" : "1.0.0",
"name" : "Encrypted JSON Authentication",
"namespace" : "guac-json",
"authProviders" : [
"org.glyptodon.guacamole.auth.json.JSONAuthenticationProvider"
],
"translations" : [
"translations/en.json"
]
}

View File

@@ -0,0 +1,7 @@
{
"DATA_SOURCE_JSON" : {
"NAME" : "JSON"
}
}