mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1218: Copy guacamole-auth-json source tree from glyptodon/guacamole-auth-json at commit f7b2eaf6a65b7cd25fd73437360e36fe46e0bcb9.
This commit is contained in:
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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"
|
||||
]
|
||||
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_JSON" : {
|
||||
"NAME" : "JSON"
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user