mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-05 20:57:40 +00:00
GUACAMOLE-1218: Copy guacamole-auth-json source tree from glyptodon/guacamole-auth-json at commit f7b2eaf6a65b7cd25fd73437360e36fe46e0bcb9.
This commit is contained in:
2
extensions/guacamole-auth-json/.gitignore
vendored
Normal file
2
extensions/guacamole-auth-json/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
*~
|
19
extensions/guacamole-auth-json/LICENSE
Normal file
19
extensions/guacamole-auth-json/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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.
|
189
extensions/guacamole-auth-json/README.md
Normal file
189
extensions/guacamole-auth-json/README.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
guacamole-auth-json
|
||||||
|
===================
|
||||||
|
|
||||||
|
guacamole-auth-json is an authentication extension for [Apache
|
||||||
|
Guacamole](http://guacamole.apache.org/) which authenticates users using JSON
|
||||||
|
which has been signed using **HMAC/SHA-256** and encrypted with **128-bit AES
|
||||||
|
in CBC mode**. This JSON contains all information describing the user being
|
||||||
|
authenticated, as well as any connections they have access to.
|
||||||
|
|
||||||
|
Configuring Guacamole to accept encrypted JSON
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
To verify and decrypt the received signed and encrypted JSON, a secret key must
|
||||||
|
be generated which will be shared by both the Guacamole server and systems that
|
||||||
|
will generate the JSON data. As guacamole-auth-json uses 128-bit AES, this key
|
||||||
|
must be 128 bits.
|
||||||
|
|
||||||
|
An easy way of generating such a key is to echo a passphrase through the
|
||||||
|
"md5sum" utility. This is the technique OpenSSL itself uses to generate 128-bit
|
||||||
|
keys from passphrases. For example:
|
||||||
|
|
||||||
|
$ echo -n "ThisIsATest" | md5sum
|
||||||
|
4c0b569e4c96df157eee1b65dd0e4d41 -
|
||||||
|
|
||||||
|
The generated key must then be saved within `guacamole.properties` as the full
|
||||||
|
32-digit hex value using the `json-secret-key` property:
|
||||||
|
|
||||||
|
json-secret-key: 4c0b569e4c96df157eee1b65dd0e4d41
|
||||||
|
|
||||||
|
JSON format
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The general format of the JSON (prior to being encrypted, signed, and sent to
|
||||||
|
Guacamole), is as follows:
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
"username" : "arbitraryUsername",
|
||||||
|
"expires" : TIMESTAMP,
|
||||||
|
"connections" : {
|
||||||
|
|
||||||
|
"Connection Name" : {
|
||||||
|
"protocol" : "PROTOCOL",
|
||||||
|
"parameters" : {
|
||||||
|
"name1" : "value1",
|
||||||
|
"name2" : "value2",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
where `TIMESTAMP` is a standard UNIX epoch timestamp with millisecond
|
||||||
|
resolution (the number of milliseconds since midnight of January 1, 1970 UTC)
|
||||||
|
and `PROTOCOL` is the internal name of any of Guacamole's supported protocols,
|
||||||
|
such as `vnc`, `rdp`, or `ssh`.
|
||||||
|
|
||||||
|
The JSON will cease to be accepted as valid after the server time passes the
|
||||||
|
timestamp. If no timestamp is specified, the data will not expire.
|
||||||
|
|
||||||
|
The top-level JSON object which must be submitted to Guacamole has the
|
||||||
|
following properties:
|
||||||
|
|
||||||
|
Property name | Type | Description
|
||||||
|
--------------|----------|------------
|
||||||
|
`username` | `string` | The unique username of the user authenticated by the JSON. If the user is anonymous, this should be the empty string (`""`).
|
||||||
|
`expires` | `number` | The absolute time after which the JSON should no longer be accepted, even if the signature is valid, as a standard UNIX epoch timestamp with millisecond resolution (the number of milliseconds since midnight of January 1, 1970 UTC).
|
||||||
|
`connections` | `object` | The set of connections which should be exposed to the user by their corresponding, unique names. If no connections will be exposed to the user, this can simply be an empty object (`{}`).
|
||||||
|
|
||||||
|
Each normal connection defined within each submitted JSON object has the
|
||||||
|
following properties:
|
||||||
|
|
||||||
|
Property name | Type | Description
|
||||||
|
--------------|----------|------------
|
||||||
|
`id` | `string` | An optional opaque value which uniquely identifies this connection across all other connections which may be active at any given time. This property is only required if you wish to allow the connection to be shared or shadowed.
|
||||||
|
`protocol` | `string` | The internal name of a supported protocol, such as `vnc`, `rdp`, or `ssh`.
|
||||||
|
`parameters` | `object` | An object representing the connection parameter name/value pairs to apply to the connection, as documented in the [Guacamole manual](https://guacamole.apache.org/doc/gug/configuring-guacamole.html#connection-configuration).
|
||||||
|
|
||||||
|
Connections which share or shadow other connections use a `join` property
|
||||||
|
instead of a `protocol` property, where `join` contains the value of the `id`
|
||||||
|
property of the connection being joined:
|
||||||
|
|
||||||
|
Property name | Type | Description
|
||||||
|
--------------|----------|------------
|
||||||
|
`id` | `string` | An optional opaque value which uniquely identifies this connection across all other connections which may be active at any given time. This property is only required if you wish to allow the connection to be shared or shadowed. (Yes, a connection which shadows another connection may itself be shadowed.)
|
||||||
|
`join` | `string` | The opaque ID given within the `id` property of the connection being joined (shared / shadowed).
|
||||||
|
`parameters` | `object` | An object representing the connection parameter name/value pairs to apply to the connection, as documented in the [Guacamole manual](https://guacamole.apache.org/doc/gug/configuring-guacamole.html#connection-configuration). Most of the connection configuration is inherited from the connection being joined. In general, the only property relevant to joining connections is `read-only`.
|
||||||
|
|
||||||
|
If a connection is configured to join another connection, that connection will
|
||||||
|
only be usable if the connection being joined is currently active. If two
|
||||||
|
connections are established having the same `id` value, only the last
|
||||||
|
connection will be joinable using the given `id`.
|
||||||
|
|
||||||
|
Generating encrypted JSON
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
To authenticate a user with the above JSON format, the JSON must be both signed
|
||||||
|
and encrypted using the same 128-bit secret key specified with the
|
||||||
|
`json-secret-key` within `guacamole.properties`:
|
||||||
|
|
||||||
|
1. Generate JSON in the format described above
|
||||||
|
2. Sign the JSON using the secret key (the same 128-bit key stored within
|
||||||
|
`guacamole.properties` with the `json-secret-key` property) with
|
||||||
|
**HMAC/SHA-256**. Prepend the binary result of the signing process to the
|
||||||
|
plaintext JSON that was signed.
|
||||||
|
3. Encrypt the result of (2) above using **AES in CBC mode**, with the initial
|
||||||
|
vector (IV) set to all zero bytes.
|
||||||
|
4. Encode the encrypted result using base64.
|
||||||
|
5. POST the encrypted result to the `/api/tokens` REST endpoint as the value of
|
||||||
|
an HTTP parameter named `data` (or include it in the URL of any Guacamole
|
||||||
|
page as a query parameter named `data`).
|
||||||
|
|
||||||
|
For example, if Guacamole is running on localhost at `/guacamole`, and
|
||||||
|
`BASE64_RESULT` is the result of the above process, the equivalent run of
|
||||||
|
the "curl" utility would be:
|
||||||
|
|
||||||
|
$ curl --data-urlencode "data=BASE64_RESULT" http://localhost:8080/guacamole/api/tokens
|
||||||
|
|
||||||
|
**NOTE:** Be sure to URL-encode the base64-encoded result prior to POSTing
|
||||||
|
it to `/api/tokens` or including it in the URL. Base64 can contain both "+"
|
||||||
|
and "=" characters, which have special meaning within URLs.
|
||||||
|
|
||||||
|
If the data is invalid in any way, if the signature does not match, if
|
||||||
|
decryption or signature verification fails, or if the submitted data has
|
||||||
|
expired, the REST service will return an invalid credentials error and fail
|
||||||
|
without user-visible explanation. Details describing the error that occurred
|
||||||
|
will be in the Tomcat logs, however.
|
||||||
|
|
||||||
|
Reference implementation
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The source includes a shell script, `doc/encrypt-json.sh`, which uses the
|
||||||
|
OpenSSL command-line utility to encrypt and sign JSON in the manner that
|
||||||
|
guacamole-auth-json requires. It is thoroughly commented and should work well
|
||||||
|
as a reference implementation, for testing, and as a point of comparison for
|
||||||
|
development. The script is run as:
|
||||||
|
|
||||||
|
$ ./encrypt-json.sh HEX_ENCRYPTION_KEY file-to-sign-and-encrypt.json
|
||||||
|
|
||||||
|
For example, if you have a file called `auth.json` containing the following:
|
||||||
|
|
||||||
|
{
|
||||||
|
"username" : "test",
|
||||||
|
"expires" : "1446323765000",
|
||||||
|
"connections" : {
|
||||||
|
"My Connection" : {
|
||||||
|
"protocol" : "rdp",
|
||||||
|
"parameters" : {
|
||||||
|
"hostname" : "10.10.209.63",
|
||||||
|
"port" : "3389"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"My OTHER Connection" : {
|
||||||
|
"protocol" : "rdp",
|
||||||
|
"parameters" : {
|
||||||
|
"hostname" : "10.10.209.64",
|
||||||
|
"port" : "3389"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
and you run:
|
||||||
|
|
||||||
|
$ ./encrypt-json.sh 4C0B569E4C96DF157EEE1B65DD0E4D41 auth.json
|
||||||
|
|
||||||
|
You will receive the following output:
|
||||||
|
|
||||||
|
le2Ug6YIo4perD2GV17QtWvOdfSemVDDtCOdRYJlbdUf3fhN+63LpQa1RDkzU7Zc
|
||||||
|
DW3+OtyTCBGQ7OLO+HpG6pHNom76BXpmnHSRx1UdQ3WVZelPUXEDzxe74aN6DUP9
|
||||||
|
G9isXhBMdLUhZwEJf4k4Gpzt9MHAH5PufSKq3DO1UHnrRjdGbKKddug2BcuDrwJM
|
||||||
|
UJf1tRX9CAEC11/gWEwrHDOhH/abeyeDyElbaEG/oOY8EdoFNYgUsjI2x31OpCuB
|
||||||
|
sEv7FOFafL05wEoIFv0/pPft0DHk7GuvHBBCqXuK98yMEo3d0zD5D+IsOY8Rmm1+
|
||||||
|
0CoWkX22mqyRQMFS2fTp/fClCN4QLb0aNn+unweTimd2SXN9cjREmZknXf7Tj8oU
|
||||||
|
/FNXc37i0HEfG5aVgp5znMCwwRAOFnFhLqG3K2yaTRE+hLNBxltIjLfFmNG5TZZA
|
||||||
|
gUdKyuegsOd0KS5iHdW6tPI01AwfRO9y2z20t3flsgDp50EGWjT2/TTA5Nkjnnjk
|
||||||
|
JXNzCOfM7DCI/ioEz6Ga140qXfOX/g8SGiukpwt+j0ANI573TdVt7nsp7MZX2qKg
|
||||||
|
2GcoNqjBqQxqpqI5ZYz4KVfD4cYu8KDZ9MiFMzbUwwKNSzYxiep1KJwiG0HQThHg
|
||||||
|
oX2FJYOFCFcinQgGkUOaBJK1K0bo1ouaBSe4iGPjd54=
|
||||||
|
|
||||||
|
The resulting base64 data above, if submitted using the `data` parameter to
|
||||||
|
Guacamole, will authenticate a user and grant them access to the connections
|
||||||
|
described in the JSON (at least until the expires timestamp is reached, at
|
||||||
|
which point the JSON will no longer be accepted).
|
||||||
|
|
119
extensions/guacamole-auth-json/doc/encrypt-json.sh
Executable file
119
extensions/guacamole-auth-json/doc/encrypt-json.sh
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
##
|
||||||
|
## @fn encrypt-json.sh
|
||||||
|
##
|
||||||
|
## Encrypts and signs JSON using the provided key, returning base64-encoded
|
||||||
|
## data ready to be submitted to Guacamole and used by the guacamole-auth-json
|
||||||
|
## authentication provider. Beware that this base64-encoded must still be
|
||||||
|
## URL-encoded prior to submission to /api/tokens via POST. Base64 encoding may
|
||||||
|
## contain + and = characters, which have special meaning in URLs.
|
||||||
|
##
|
||||||
|
## To submit the resulting data easily via curl, the following will work:
|
||||||
|
##
|
||||||
|
## curl --data-urlencode "data=$(<file_containing_result)" GUAC_URL/api/tokens
|
||||||
|
##
|
||||||
|
## @param SECRET_KEY
|
||||||
|
## The key to encrypt and sign the JSON file with, as a 16-byte (32-digit)
|
||||||
|
## hexadecimal value. This key must match the key specified within
|
||||||
|
## guacamole.properties using the "json-secret-key" property.
|
||||||
|
##
|
||||||
|
## @param JSON_FILENAME
|
||||||
|
## The filename of the JSON to encrypt and sign.
|
||||||
|
##
|
||||||
|
|
||||||
|
##
|
||||||
|
## Encryption/signing key.
|
||||||
|
##
|
||||||
|
SECRET_KEY="$1"
|
||||||
|
|
||||||
|
##
|
||||||
|
## The filename of the JSON data being signed and encrypted.
|
||||||
|
##
|
||||||
|
JSON_FILENAME="$2"
|
||||||
|
|
||||||
|
##
|
||||||
|
## A null (all zeroes) IV.
|
||||||
|
##
|
||||||
|
NULL_IV="00000000000000000000000000000000"
|
||||||
|
|
||||||
|
##
|
||||||
|
## Signs the contents of the given file using the given key. The signature is
|
||||||
|
## created using HMAC/SHA-256, and is output in binary form to STDOUT, followed
|
||||||
|
## by the raw contents of the file.
|
||||||
|
##
|
||||||
|
## @param KEY
|
||||||
|
## The key to use to sign the contents of the given file with HMAC/SHA-256.
|
||||||
|
##
|
||||||
|
## @param FILENAME
|
||||||
|
## The filename of the file to sign.
|
||||||
|
##
|
||||||
|
sign() {
|
||||||
|
|
||||||
|
KEY="$1"
|
||||||
|
FILENAME="$2"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write out signature
|
||||||
|
#
|
||||||
|
|
||||||
|
openssl dgst \
|
||||||
|
-sha256 -mac HMAC -macopt hexkey:"$KEY" \
|
||||||
|
-binary "$FILENAME"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write out file contents
|
||||||
|
#
|
||||||
|
|
||||||
|
cat "$FILENAME"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
## Encrypts all data received through STDIN using the provided key. Data is
|
||||||
|
## encrypted using 128-bit AES in CBC mode (with a null IV). The encrypted
|
||||||
|
## result is printed to STDOUT encoded with base64.
|
||||||
|
##
|
||||||
|
## @param KEY
|
||||||
|
## The key to encrypt STDIN with, as a 16-byte (32-digit) hexadecimal
|
||||||
|
## value.
|
||||||
|
##
|
||||||
|
encrypt() {
|
||||||
|
|
||||||
|
KEY="$1"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Encrypt STDIN
|
||||||
|
#
|
||||||
|
|
||||||
|
openssl enc -aes-128-cbc -K "$KEY" -iv "$NULL_IV" -nosalt -a
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sign and encrypt file using secret key
|
||||||
|
#
|
||||||
|
|
||||||
|
sign "$SECRET_KEY" "$JSON_FILENAME" | encrypt "$SECRET_KEY"
|
||||||
|
|
104
extensions/guacamole-auth-json/pom.xml
Normal file
104
extensions/guacamole-auth-json/pom.xml
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.glyptodon.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-auth-json</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<version>1.0.0-1</version>
|
||||||
|
<name>guacamole-auth-json</name>
|
||||||
|
<url>http://glyptodon.org/</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<!-- Written for 1.6 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.3</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Xlint:all</arg>
|
||||||
|
<arg>-Werror</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<fork>true</fork>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Copy dependencies prior to packaging -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>2.10</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>unpack-dependencies</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>unpack-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeScope>runtime</includeScope>
|
||||||
|
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Guacamole Extension API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.guacamole</groupId>
|
||||||
|
<artifactId>guacamole-ext</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Guice -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject</groupId>
|
||||||
|
<artifactId>guice</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject.extensions</groupId>
|
||||||
|
<artifactId>guice-multibindings</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jackson for JSON support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
<version>1.9.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Java servlet API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>servlet-api</artifactId>
|
||||||
|
<version>2.5</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring security library (required for IP address matching) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-web</artifactId>
|
||||||
|
<version>4.0.4.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
pom.xml
1
pom.xml
@@ -53,6 +53,7 @@
|
|||||||
<module>extensions/guacamole-auth-duo</module>
|
<module>extensions/guacamole-auth-duo</module>
|
||||||
<module>extensions/guacamole-auth-header</module>
|
<module>extensions/guacamole-auth-header</module>
|
||||||
<module>extensions/guacamole-auth-jdbc</module>
|
<module>extensions/guacamole-auth-jdbc</module>
|
||||||
|
<module>extensions/guacamole-auth-json</module>
|
||||||
<module>extensions/guacamole-auth-ldap</module>
|
<module>extensions/guacamole-auth-ldap</module>
|
||||||
<module>extensions/guacamole-auth-openid</module>
|
<module>extensions/guacamole-auth-openid</module>
|
||||||
<module>extensions/guacamole-auth-quickconnect</module>
|
<module>extensions/guacamole-auth-quickconnect</module>
|
||||||
|
Reference in New Issue
Block a user