Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

1
extensions/.ratignore Normal file
View File

@@ -0,0 +1 @@
guacamole-auth-radius/**/*

View File

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.apache.guacamole</groupId>
<artifactId>guacamole-auth-ban</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-ban</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.6.0</version>
<relativePath>../</relativePath>
</parent>
<dependencies>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
<!-- Exclude transitive dependencies that will be overridden by
newer versions required by Caffeine -->
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Guava Base Libraries -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0
http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<!-- Output tar.gz -->
<formats>
<format>tar.gz</format>
</formats>
<!-- Include licenses and extension .jar -->
<fileSets>
<!-- Include licenses -->
<fileSet>
<outputDirectory></outputDirectory>
<directory>target/licenses</directory>
</fileSet>
<!-- Include extension .jar -->
<fileSet>
<directory>target</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,200 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.ban;
import org.apache.guacamole.auth.ban.status.AuthenticationFailureTracker;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.ban.status.InMemoryAuthenticationFailureTracker;
import org.apache.guacamole.auth.ban.status.NullAuthenticationFailureTracker;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
import org.apache.guacamole.net.event.AuthenticationFailureEvent;
import org.apache.guacamole.net.event.AuthenticationSuccessEvent;
import org.apache.guacamole.net.event.listener.Listener;
import org.apache.guacamole.net.event.AuthenticationRequestReceivedEvent;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.LongGuacamoleProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener implementation which automatically tracks authentication failures
* such that further authentication attempts may be automatically blocked if
* they match configured criteria.
*/
public class BanningAuthenticationListener implements Listener {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(BanningAuthenticationListener.class);
/**
* The maximum number of failed authentication attempts allowed before an
* address is temporarily banned.
*/
private static final IntegerGuacamoleProperty MAX_ATTEMPTS = new IntegerGuacamoleProperty() {
@Override
public String getName() {
return "ban-max-invalid-attempts";
}
};
/**
* The length of time that each address should be banned after reaching the
* maximum number of failed authentication attempts, in seconds.
*/
private static final IntegerGuacamoleProperty IP_BAN_DURATION = new IntegerGuacamoleProperty() {
@Override
public String getName() {
return "ban-address-duration";
}
};
/**
* The maximum number of failed authentication attempts tracked at any
* given time. Once this number of addresses is exceeded, the oldest
* authentication attempts are rotated off on an LRU basis.
*/
private static final LongGuacamoleProperty MAX_ADDRESSES = new LongGuacamoleProperty() {
@Override
public String getName() {
return "ban-max-addresses";
}
};
/**
* The default maximum number of failed authentication attempts allowed
* before an address is temporarily banned.
*/
private static final int DEFAULT_MAX_ATTEMPTS = 5;
/**
* The default length of time that each address should be banned after
* reaching the maximum number of failed authentication attempts, in
* seconds.
*/
private static final int DEFAULT_IP_BAN_DURATION = 300;
/**
* The maximum number of failed authentication attempts tracked at any
* given time. Once this number of addresses is exceeded, the oldest
* authentication attempts are rotated off on an LRU basis.
*/
private static final long DEFAULT_MAX_ADDRESSES = 10485760;
/**
* Tracker of addresses that have repeatedly failed authentication.
*/
private final AuthenticationFailureTracker tracker;
/**
* Creates a new BanningAuthenticationListener which automatically bans
* further authentication attempts from addresses that have repeatedly
* failed to authenticate. The ban duration and maximum number of failed
* attempts allowed before banning are configured within
* guacamole.properties.
*
* @throws GuacamoleException
* If an error occurs parsing the configuration properties used by this
* extension.
*/
public BanningAuthenticationListener() throws GuacamoleException {
Environment environment = LocalEnvironment.getInstance();
int maxAttempts = environment.getProperty(MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
int banDuration = environment.getProperty(IP_BAN_DURATION, DEFAULT_IP_BAN_DURATION);
long maxAddresses = environment.getProperty(MAX_ADDRESSES, DEFAULT_MAX_ADDRESSES);
// Configure auth failure tracking behavior and inform administrator of
// ultimate result
if (maxAttempts <= 0) {
this.tracker = new NullAuthenticationFailureTracker();
logger.info("Maximum failed authentication attempts has been set "
+ "to {}. Automatic banning of brute-force authentication "
+ "attempts will be disabled.", maxAttempts);
}
else if (banDuration <= 0) {
this.tracker = new NullAuthenticationFailureTracker();
logger.info("Ban duration for addresses that repeatedly fail "
+ "authentication has been set to {}. Automatic banning "
+ "of brute-force authentication attempts will be "
+ "disabled.", banDuration);
}
else if (maxAddresses <= 0) {
this.tracker = new NullAuthenticationFailureTracker();
logger.info("Maximum number of tracked addresses has been set to "
+ "{}. Automatic banning of brute-force authentication "
+ "attempts will be disabled.", maxAddresses);
}
else {
this.tracker = new InMemoryAuthenticationFailureTracker(maxAttempts, banDuration, maxAddresses);
logger.info("Addresses will be automatically banned for {} "
+ "seconds after {} failed authentication attempts. Up "
+ "to {} unique addresses will be tracked/banned at any "
+ "given time.", banDuration, maxAttempts, maxAddresses);
}
}
@Override
public void handleEvent(Object event) throws GuacamoleException {
// Notify auth tracker of each request received BEFORE the request is
// processed ...
if (event instanceof AuthenticationRequestReceivedEvent) {
AuthenticationRequestReceivedEvent request = (AuthenticationRequestReceivedEvent) event;
tracker.notifyAuthenticationRequestReceived(request.getCredentials());
}
// ... as well as every explicit failure ...
else if (event instanceof AuthenticationFailureEvent) {
AuthenticationFailureEvent failure = (AuthenticationFailureEvent) event;
// Requests for additional credentials are not failures per se,
// but continuations of a multi-request authentication attempt that
// has not yet succeeded OR failed
if (failure.getFailure() instanceof GuacamoleInsufficientCredentialsException) {
tracker.notifyAuthenticationRequestReceived(failure.getCredentials());
return;
}
// Consider all other errors to be failed auth attempts
tracker.notifyAuthenticationFailed(failure.getCredentials());
}
// ... and explicit success.
else if (event instanceof AuthenticationSuccessEvent) {
AuthenticationSuccessEvent success = (AuthenticationSuccessEvent) event;
tracker.notifyAuthenticationSuccess(success.getCredentials());
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.ban.status;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The current status of an authentication failure, including the number of
* times the failure has occurred.
*/
public class AuthenticationFailureStatus {
/**
* The timestamp of the last authentication failure, as returned by
* System.nanoTime().
*/
private long lastFailure;
/**
* The number of failures that have occurred.
*/
private final AtomicInteger failureCount;
/**
* The maximum number of failures that may occur before the user/address
* causing the failures is blocked.
*/
private final int maxAttempts;
/**
* The amount of time that a user/address must remain blocked after they
* have reached the maximum number of failures. Unlike the value provided
* at construction time, this value is maintained in nanoseconds.
*/
private final long duration;
/**
* Creates an AuthenticationFailureStatus that is initialized to zero
* failures and is subject to the given restrictions. Additional failures
* may be flagged after creation with {@link #notifyFailed()}.
*
* @param maxAttempts
* The maximum number of failures that may occur before the
* user/address causing the failures is blocked.
*
* @param duration
* The amount of time, in seconds, that a user/address must remain
* blocked after they have reached the maximum number of failures.
*/
public AuthenticationFailureStatus(int maxAttempts, int duration) {
this.lastFailure = System.nanoTime();
this.failureCount = new AtomicInteger(0);
this.maxAttempts = maxAttempts;
this.duration = TimeUnit.SECONDS.toNanos(duration);
}
/**
* Updates this authentication failure, noting that the failure it
* represents has recurred.
*/
public void notifyFailed() {
lastFailure = System.nanoTime();
failureCount.incrementAndGet();
}
/**
* Returns whether this authentication failure is recent enough that it
* should still be tracked. This function will return false for
* authentication failures that have not recurred for at least the duration
* provided at construction time.
*
* @return
* true if this authentication failure is recent enough that it should
* still be tracked, false otherwise.
*/
public boolean isValid() {
return System.nanoTime() - lastFailure <= duration;
}
/**
* Returns whether the user/address causing this authentication failure
* should be blocked based on the restrictions provided at construction
* time.
*
* @return
* true if the user/address causing this failure should be blocked,
* false otherwise.
*/
public boolean isBlocked() {
return isValid() && failureCount.get() >= maxAttempts;
}
/**
* Returns the total number of authentication failures that have been
* recorded through creating this object and invoking
* {@link #notifyFailed()}.
*
* @return
* The total number of failures that have occurred.
*/
public int getFailures() {
return failureCount.get();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.ban.status;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Credentials;
/**
* Tracks past authentication results, automatically blocking the IP addresses
* of machines that repeatedly fail to authenticate.
*/
public interface AuthenticationFailureTracker {
/**
* Reports that an authentication request has been received, but it is
* either not yet known whether the request has succeeded or failed. If the
* associated address is currently being blocked, an exception will be
* thrown.
*
* @param credentials
* The credentials associated with the authentication request.
*
* @throws GuacamoleException
* If the authentication request is being blocked due to brute force
* prevention rules.
*/
void notifyAuthenticationRequestReceived(Credentials credentials)
throws GuacamoleException;
/**
* Reports that an authentication request has been received and has
* succeeded. If the associated address is currently being blocked, an
* exception will be thrown.
*
* @param credentials
* The credentials associated with the successful authentication
* request.
*
* @throws GuacamoleException
* If the authentication request is being blocked due to brute force
* prevention rules.
*/
void notifyAuthenticationSuccess(Credentials credentials)
throws GuacamoleException;
/**
* Reports that an authentication request has been received and has
* failed. If the associated address is currently being blocked, an
* exception will be thrown.
*
* @param credentials
* The credentials associated with the failed authentication request.
*
* @throws GuacamoleException
* If the authentication request is being blocked due to brute force
* prevention rules.
*/
void notifyAuthenticationFailed(Credentials credentials)
throws GuacamoleException;
}

View File

@@ -0,0 +1,196 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.ban.status;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.language.TranslatableGuacamoleClientTooManyException;
import org.apache.guacamole.net.auth.Credentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AuthenticationFailureTracker implementation that tracks the failure status
* of each IP address in memory. The maximum amount of memory consumed is
* bounded by the configured maximum number of addresses tracked.
*/
public class InMemoryAuthenticationFailureTracker implements AuthenticationFailureTracker {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(InMemoryAuthenticationFailureTracker.class);
/**
* All authentication failures currently being tracked, stored by the
* associated IP address.
*/
private final Cache<String, AuthenticationFailureStatus> failures;
/**
* The maximum number of failed authentication attempts allowed before an
* address is temporarily banned.
*/
private final int maxAttempts;
/**
* The length of time that each address should be banned after reaching the
* maximum number of failed authentication attempts, in seconds.
*/
private final int banDuration;
/**
* Creates a new AuthenticationFailureTracker that automatically blocks
* authentication attempts based on the provided blocking criteria.
*
* @param maxAttempts
* The maximum number of failed authentication attempts allowed before
* an address is temporarily banned.
*
* @param banDuration
* The length of time that each address should be banned after reaching
* the maximum number of failed authentication attempts, in seconds.
*
* @param maxAddresses
* The maximum number of unique IP addresses that should be tracked
* before discarding older tracked failures.
*/
public InMemoryAuthenticationFailureTracker(int maxAttempts, int banDuration,
long maxAddresses) {
this.maxAttempts = maxAttempts;
this.banDuration = banDuration;
// Limit maximum number of tracked addresses to configured upper bound
this.failures = Caffeine.newBuilder()
.maximumSize(maxAddresses)
.build();
}
/**
* Reports that the given address has just failed to authenticate and
* returns the AuthenticationFailureStatus that represents that failure. If
* the address isn't already being tracked, it will begin being tracked as
* of this call. If the address is already tracked, the returned
* AuthenticationFailureStatus will represent past authentication failures,
* as well.
*
* @param address
* The address that has failed to authenticate.
*
* @return
* An AuthenticationFailureStatus that represents this latest
* authentication failure for the given address, as well as any past
* failures.
*/
private AuthenticationFailureStatus getAuthenticationFailure(String address) {
AuthenticationFailureStatus status = failures.get(address,
(addr) -> new AuthenticationFailureStatus(maxAttempts, banDuration));
status.notifyFailed();
return status;
}
/**
* Reports that an authentication request has been received, as well as
* whether that request is known to have failed. If the associated address
* is currently being blocked, an exception will be thrown.
*
* @param credentials
* The credentials associated with the authentication request.
*
* @param failed
* Whether the request is known to have failed. If the status of the
* request is not yet known, this should be false.
*
* @throws GuacamoleException
* If the authentication request is being blocked due to brute force
* prevention rules.
*/
private void notifyAuthenticationStatus(Credentials credentials,
boolean failed) throws GuacamoleException {
// Ignore requests that do not contain explicit parameters of any kind
if (credentials.isEmpty())
return;
// Determine originating address of the authentication request
String address = credentials.getRemoteAddress();
if (address == null)
throw new GuacamoleServerException("Source address cannot be determined.");
// Get current failure status for the address associated with the
// authentication request, adding/updating that status if the request
// was itself a failure
AuthenticationFailureStatus status;
if (failed) {
status = getAuthenticationFailure(address);
logger.info("Authentication has failed for address \"{}\" (current total failures: {}/{}).",
address, status.getFailures(), maxAttempts);
}
else
status = failures.getIfPresent(address);
if (status != null) {
// Explicitly block further processing of authentication/authorization
// if too many failures have occurred
if (status.isBlocked()) {
logger.warn("Blocking authentication attempt from address \"{}\" due to number of authentication failures.", address);
throw new TranslatableGuacamoleClientTooManyException("Too "
+ "many failed authentication attempts.",
"LOGIN.ERROR_TOO_MANY_ATTEMPTS");
}
// Clean up tracking of failures if the address is no longer
// relevant (all failures are sufficiently old)
else if (!status.isValid()) {
logger.debug("Removing address \"{}\" from tracking as there are no recent authentication failures.", address);
failures.invalidate(address);
}
}
}
@Override
public void notifyAuthenticationRequestReceived(Credentials credentials)
throws GuacamoleException {
notifyAuthenticationStatus(credentials, false);
}
@Override
public void notifyAuthenticationSuccess(Credentials credentials)
throws GuacamoleException {
notifyAuthenticationStatus(credentials, false);
}
@Override
public void notifyAuthenticationFailed(Credentials credentials)
throws GuacamoleException {
notifyAuthenticationStatus(credentials, true);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.ban.status;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.Credentials;
/**
* AuthenticationFailureTracker implementation that does nothing. All requests
* are ignored, regardless of status, and no tracking is performed.
*/
public class NullAuthenticationFailureTracker implements AuthenticationFailureTracker {
@Override
public void notifyAuthenticationRequestReceived(Credentials credentials)
throws GuacamoleException {
// Do nothing
}
@Override
public void notifyAuthenticationSuccess(Credentials credentials)
throws GuacamoleException {
// Do nothing
}
@Override
public void notifyAuthenticationFailed(Credentials credentials)
throws GuacamoleException {
// Do nothing
}
}

View File

@@ -0,0 +1,17 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "Brute-force Authentication Detection/Prevention",
"namespace" : "ban",
"listeners" : [
"org.apache.guacamole.auth.ban.BanningAuthenticationListener"
],
"translations" : [
"translations/en.json",
"translations/pl.json"
]
}

View File

@@ -0,0 +1,5 @@
{
"LOGIN": {
"ERROR_TOO_MANY_ATTEMPTS" : "Too many failed authentication attempts. Please try again later."
}
}

View File

@@ -0,0 +1,5 @@
{
"LOGIN": {
"ERROR_TOO_MANY_ATTEMPTS" : "Zbyt wiele nieudanych prób logowania. Spróbuj ponownie później."
}
}

View File

@@ -0,0 +1,3 @@
src/main/resources/generated/
target/
*~

View File

@@ -0,0 +1,2 @@
src/main/resources/lib/DuoWeb/**/*
src/main/java/com/duosecurity/duoweb/**/*

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.apache.guacamole</groupId>
<artifactId>guacamole-auth-duo</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-duo</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.6.0</version>
<relativePath>../</relativePath>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<okhttp.version>4.12.0</okhttp.version>
<kotlin.version>1.9.25</kotlin.version>
</properties>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<!-- Guava - Utility Library -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Duo SDK -->
<dependency>
<groupId>com.duosecurity</groupId>
<artifactId>duo-universal-sdk</artifactId>
<version>1.2.0</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Force the use of a consistent version of "okhttp" -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
</exclusion>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp.version}</version>
<exclusions>
<exclusion>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Force the use of a consistent version of Kotlin standard library common -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<!-- Library for unified IPv4/6 parsing and validation -->
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<!-- Output tar.gz -->
<formats>
<format>tar.gz</format>
</formats>
<!-- Include licenses and extension .jar -->
<fileSets>
<!-- Include licenses -->
<fileSet>
<outputDirectory></outputDirectory>
<directory>target/licenses</directory>
</fileSet>
<!-- Include extension .jar -->
<fileSet>
<directory>target</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo;
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;
/**
* AuthenticationProvider implementation which uses Duo as an additional
* authentication factor for users which have already been authenticated by
* some other AuthenticationProvider.
*/
public class DuoAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* The unique identifier for this authentication provider. This is used in
* various parts of the Guacamole client to distinguish this provider from
* others, particularly when multiple authentication providers are used.
*/
public static String PROVIDER_IDENTIFER = "duo";
/**
* Service for verifying the identity of users that Guacamole has otherwise
* already authenticated.
*/
private final UserVerificationService verificationService;
/**
* Session manager for storing/retrieving the state of a user's
* authentication attempt while they are redirected to the Duo service.
*/
private final DuoAuthenticationSessionManager sessionManager;
/**
* Creates a new DuoAuthenticationProvider that verifies users
* using the Duo authentication service
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public DuoAuthenticationProvider() throws GuacamoleException {
// Set up Guice injector.
Injector injector = Guice.createInjector(
new DuoAuthenticationProviderModule(this)
);
sessionManager = injector.getInstance(DuoAuthenticationSessionManager.class);
verificationService = injector.getInstance(UserVerificationService.class);
}
@Override
public String getIdentifier() {
return PROVIDER_IDENTIFER;
}
@Override
public Credentials updateCredentials(Credentials credentials)
throws GuacamoleException {
// Ignore requests with no corresponding authentication session ID, as
// there are no credentials to reconstitute if the user has not yet
// attempted to authenticate
String duoState = credentials.getParameter(UserVerificationService.DUO_STATE_PARAMETER_NAME);
if (duoState == null)
return credentials;
// Ignore requests with invalid/expired authentication session IDs
DuoAuthenticationSession session = sessionManager.resume(duoState);
if (session == null)
return credentials;
// Reconstitute the originally-provided credentials from the users
// authentication attempt prior to being redirected to Duo
Credentials previousCredentials = session.getCredentials();
previousCredentials.setRequestDetails(credentials.getRequestDetails());
return previousCredentials;
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Verify user against Duo service
verificationService.verifyAuthenticatedUser(authenticatedUser);
// User has been verified, and authentication should be allowed to
// continue
return null;
}
@Override
public void shutdown() {
sessionManager.shutdown();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo;
import com.google.inject.AbstractModule;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.duo.conf.ConfigurationService;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.AuthenticationProvider;
/**
* Guice module which configures Duo-specific injections.
*/
public class DuoAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* A reference to the DuoAuthenticationProvider on behalf of which this
* module has configured injection.
*/
private final AuthenticationProvider authProvider;
/**
* Creates a new Duo authentication provider module which configures
* injection for the DuoAuthenticationProvider.
*
* @param authProvider
* The AuthenticationProvider for which injection is being configured.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public DuoAuthenticationProviderModule(AuthenticationProvider authProvider)
throws GuacamoleException {
// Get local environment
this.environment = LocalEnvironment.getInstance();
// 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 Duo-specific services
bind(ConfigurationService.class);
bind(UserVerificationService.class);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo;
import org.apache.guacamole.net.auth.AuthenticationSession;
import org.apache.guacamole.net.auth.Credentials;
/**
* Representation of an in-progress Duo authentication attempt.
*/
public class DuoAuthenticationSession extends AuthenticationSession {
/**
* The credentials that the user originally provided to Guacamole prior to
* being redirected to the Duo service.
*/
private final Credentials credentials;
/**
* Creates a new AuthenticationSession representing an in-progress Duo
* authentication attempt.
*
* @param credentials
* The credentials that the user originally provided to Guacamole prior
* to being redirected to the Duo service.
*
* @param expires
* The number of milliseconds that may elapse before this session must
* be considered invalid.
*/
public DuoAuthenticationSession(Credentials credentials, long expires) {
super(expires);
this.credentials = credentials;
}
/**
* Returns the credentials that the user originally provided to Guacamole
* prior to being redirected to the Duo service.
*
* @return
* The credentials that the user originally provided to Guacamole prior
* to being redirected to the Duo service.
*/
public Credentials getCredentials() {
return credentials;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo;
import com.google.inject.Singleton;
import org.apache.guacamole.net.auth.AuthenticationSessionManager;
/**
* Manager service that temporarily stores authentication attempts while
* the Duo authentication flow is underway.
*/
@Singleton
public class DuoAuthenticationSessionManager
extends AuthenticationSessionManager<DuoAuthenticationSession> {
// Intentionally empty (the default functions inherited from the
// AuthenticationSessionManager base class are sufficient for our needs)
}

View File

@@ -0,0 +1,276 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo;
import com.duosecurity.Client;
import com.duosecurity.exception.DuoException;
import com.duosecurity.model.Token;
import com.google.inject.Inject;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.duo.conf.ConfigurationService;
import org.apache.guacamole.form.RedirectField;
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
import org.apache.guacamole.language.TranslatableMessage;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.properties.IPAddressListProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service for verifying the identity of a user against Duo.
*/
public class UserVerificationService {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
/**
* The name of the HTTP parameter that Duo will use to communicate the
* result of the user's attempt to authenticate with their service. This
* parameter is provided in the GET request received when Duo redirects the
* user back to Guacamole.
*/
public static final String DUO_CODE_PARAMETER_NAME = "duo_code";
/**
* The name of the HTTP parameter that we will be using to hold the opaque
* authentication session ID. This session ID is transmitted to Duo during
* the initial redirect and received back from Duo via this parameter in
* the GET request received when Duo redirects the user back to Guacamole.
* The session ID is ultimately used to reconstitute the original
* credentials received from the user by Guacamole such that parameter
* tokens like GUAC_USERNAME and GUAC_PASSWORD can continue to work as
* expected.
*/
public static final String DUO_STATE_PARAMETER_NAME = "state";
/**
* The value that will be returned in the token if Duo authentication
* was successful.
*/
private static final String DUO_TOKEN_SUCCESS_VALUE = "allow";
/**
* Session manager for storing/retrieving the state of a user's
* authentication attempt while they are redirected to the Duo service.
*/
@Inject
private DuoAuthenticationSessionManager sessionManager;
/**
* Service for retrieving Duo configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Verifies the identity of the given user via the Duo multi-factor
* authentication service. If a signed response from Duo has not already
* been provided, a signed response from Duo is requested in the
* form of additional expected credentials. Any provided signed response
* is cryptographically verified. If no signed response is present, or the
* signed response is invalid, an exception is thrown.
*
* @param authenticatedUser
* The user whose identity should be verified against Duo.
*
* @throws GuacamoleException
* If required Duo-specific configuration options are missing or
* malformed, or if the user's identity cannot be verified.
*/
public void verifyAuthenticatedUser(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Pull the original HTTP request used to authenticate
Credentials credentials = authenticatedUser.getCredentials();
IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
// Ignore anonymous users
String username = authenticatedUser.getIdentifier();
if (username == null || username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
return;
// Pull address lists to check from configuration. Note that the enforce
// list will override the bypass list, which means that, if the client
// address happens to be in both lists, Duo MFA will be enforced.
List<IPAddress> bypassAddresses = confService.getBypassHosts();
List<IPAddress> enforceAddresses = confService.getEnforceHosts();
// Check if the bypass list contains the client address, and set the
// enforce flag to the opposite.
boolean enforceHost = !(IPAddressListProperty.addressListContains(bypassAddresses, clientAddr));
// Only continue processing if the list is not empty
if (!enforceAddresses.isEmpty()) {
// If client address is not available or invalid, MFA will
// be enforced.
if (clientAddr == null || !clientAddr.isIPAddress())
enforceHost = true;
// Check the enforce list for the client address and set enforcement flag.
else
enforceHost = IPAddressListProperty.addressListContains(enforceAddresses, clientAddr);
}
// If the enforce flag is not true, bypass Duo MFA.
if (!enforceHost)
return;
// Obtain a Duo client for redirecting the user to the Duo service and
// verifying any received authentication code
Client duoClient;
try {
duoClient = new Client.Builder(
confService.getClientId(),
confService.getClientSecret(),
confService.getAPIHostname(),
confService.getRedirectUri().toString())
.build();
}
catch (DuoException e) {
throw new GuacamoleServerException("Client for communicating with "
+ "the Duo authentication service could not be created.", e);
}
// Verify that the Duo service is healthy and available
try {
duoClient.healthCheck();
}
catch (DuoException e) {
throw new GuacamoleServerException("Duo authentication service is "
+ "not currently available (failed health check).", e);
}
// Retrieve signed Duo authentication code and session state from the
// request (these will be absent if this is an initial authentication
// attempt and not a redirect back from Duo)
String duoCode = credentials.getParameter(DUO_CODE_PARAMETER_NAME);
String duoState = credentials.getParameter(DUO_STATE_PARAMETER_NAME);
// Redirect to Duo to obtain an authentication code if that redirect
// has not yet occurred
if (duoCode != null && duoState != null) {
// Validate that the user has successfully verified their identify with
// the Duo service
try {
// Note unexpected behavior (Duo is expected to always return
// a token)
Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username);
if (token == null) {
logger.warn("Duo did not return an authentication result "
+ "at all for the authentication attempt by user "
+ "\"{}\". This is unexpected behavior and may be "
+ "a bug in the Duo service or the Duo SDK. "
+ "Guacamole will attempt to automatically work "
+ "around the issue by making a fresh Duo "
+ "authentication request.", username);
}
// Warn if Duo explicitly denies authentication
else if (token.getAuth_result() == null || !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus())) {
logger.warn("Duo did not return an explicitly successful "
+ "authentication result for the authentication "
+ "attempt by user \"{}\". The user will now be "
+ "redirected back to the Duo service to reattempt"
+ "authentication.", username);
}
// Allow user to continue authenticating with Guacamole only if
// Duo has validated their identity
else
return;
}
catch (DuoException e) {
logger.debug("The Duo client failed internally while "
+ "attempting to validate the identity of user "
+ "\"{}\". This is commonly caused by stale query "
+ "parameters from an older Duo request remaining "
+ "present in the Guacamole URL. The user will now be "
+ "redirected back to the Duo service to reattempt "
+ "authentication.", e);
}
}
// Store received credentials for later retrieval leveraging Duo's
// opaque session state identifier (we need to maintain these
// credentials so that things like the GUAC_USERNAME and
// GUAC_PASSWORD tokens continue to work as expected despite the
// redirect to/from the external Duo service)
duoState = duoClient.generateState();
long expiresAfter = TimeUnit.MINUTES.toMillis(confService.getAuthenticationTimeout());
sessionManager.defer(new DuoAuthenticationSession(credentials, expiresAfter), duoState);
// Obtain authentication URL from Duo client
String duoAuthUrlString;
try {
duoAuthUrlString = duoClient.createAuthUrl(username, duoState);
}
catch (DuoException e) {
throw new GuacamoleServerException("Duo client failed to "
+ "generate the authentication URL necessary to "
+ "redirect the authenticating user to the Duo "
+ "service.", e);
}
// Parse and validate URL obtained from Duo client
URI duoAuthUrl;
try {
duoAuthUrl = new URI(duoAuthUrlString);
}
catch (URISyntaxException e) {
throw new GuacamoleServerException("Authentication URL "
+ "generated by the Duo client is not actually a "
+ "valid URL and cannot be used to redirect the "
+ "authenticating user to the Duo service.", e);
}
// Request that user be redirected to the Duo service to obtain
// a Duo authentication code
throw new TranslatableGuacamoleInsufficientCredentialsException(
"Verification using Duo is required before authentication "
+ "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
new CredentialsInfo(Collections.singletonList(
new RedirectField(
DUO_CODE_PARAMETER_NAME, duoAuthUrl,
new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
)
))
);
}
}

View File

@@ -0,0 +1,268 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.duo.conf;
import com.google.inject.Inject;
import inet.ipaddr.IPAddress;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.IPAddressListProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.properties.URIGuacamoleProperty;
/**
* Service for retrieving configuration information regarding the Duo
* authentication extension.
*/
public class ConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* The property within guacamole.properties which defines the hostname
* of the Duo API endpoint to be used to verify user identities. This will
* usually be in the form "api-XXXXXXXX.duosecurity.com", where "XXXXXXXX"
* is some arbitrary alphanumeric value assigned by Duo and specific to
* your organization.
*/
private static final StringGuacamoleProperty DUO_API_HOSTNAME =
new StringGuacamoleProperty() {
@Override
public String getName() { return "duo-api-hostname"; }
};
/**
* The property within guacamole.properties which defines the client id
* received from Duo for verifying Guacamole users. This value MUST be
* exactly 20 characters.
*/
private static final StringGuacamoleProperty DUO_CLIENT_ID =
new StringGuacamoleProperty() {
@Override
public String getName() { return "duo-client-id"; }
};
/**
* The property within guacamole.properties which defines the secret key
* received from Duo for verifying Guacamole users. This value MUST be
* exactly 40 characters.
*/
private static final StringGuacamoleProperty DUO_CLIENT_SECRET =
new StringGuacamoleProperty() {
@Override
public String getName() { return "duo-client-secret"; }
};
/**
* The property within guacamole.properties which defines the redirect URI
* that Duo will call after the second factor has been completed. This
* should be the URI used to access Guacamole.
*/
private static final URIGuacamoleProperty DUO_REDIRECT_URI =
new URIGuacamoleProperty() {
@Override
public String getName() { return "duo-redirect-uri"; }
};
/**
* The property that configures the timeout, in minutes, of in-progress
* Duo authentication attempts. Authentication attempts that take longer
* than this period of time will be invalidated.
*/
private static final IntegerGuacamoleProperty DUO_AUTH_TIMEOUT =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "duo-auth-timeout"; }
};
/**
* The optional property that contains a comma-separated list of IP addresses
* or CIDRs for which the MFA requirement should be bypassed. If the Duo
* extension is installed, any/all users authenticating from clients that
* match this list will be able to successfully log in without fulfilling
* the MFA requirement. If this option is omitted or is empty, and the
* Duo module is installed, all users from all hosts will have Duo MFA
* enforced.
*/
private static final IPAddressListProperty DUO_BYPASS_HOSTS =
new IPAddressListProperty() {
@Override
public String getName() { return "duo-bypass-hosts"; }
};
/**
* The optional property that contains a comma-separated list of IP addresses
* or CIDRs for which the MFA requirement should be explicitly enforced. If
* the Duo module is enabled and this property is specified, users that log
* in from hosts that match the items in this list will have Duo MFA required,
* and all users from hosts that do not match this list will be able to log
* in without the MFA requirement. If this option is missing or empty and
* the Duo module is installed, MFA will be enforced for all users.
*/
private static final IPAddressListProperty DUO_ENFORCE_HOSTS =
new IPAddressListProperty() {
@Override
public String getName() { return "duo-enforce-hosts"; }
};
/**
* Returns the hostname of the Duo API endpoint to be used to verify user
* identities, as defined in guacamole.properties by the "duo-api-hostname"
* property. This will usually be in the form
* "api-XXXXXXXX.duosecurity.com", where "XXXXXXXX" is some arbitrary
* alphanumeric value assigned by Duo and specific to your organization.
*
* @return
* The hostname of the Duo API endpoint to be used to verify user
* identities.
*
* @throws GuacamoleException
* If the associated property within guacamole.properties is missing.
*/
public String getAPIHostname() throws GuacamoleException {
return environment.getRequiredProperty(DUO_API_HOSTNAME);
}
/**
* Returns the Duo client id received from Duo for verifying Guacamole
* users, as defined in guacamole.properties by the "duo-client-id"
* property. This value MUST be exactly 20 characters.
*
* @return
* The client id received from Duo for verifying Guacamole users.
*
* @throws GuacamoleException
* If the associated property within guacamole.properties is missing.
*/
public String getClientId() throws GuacamoleException {
return environment.getRequiredProperty(DUO_CLIENT_ID);
}
/**
* Returns the client secret received from Duo for verifying Guacamole users,
* as defined in guacamole.properties by the "duo-client-secret" property.
* This value MUST be exactly 20 characters.
*
* @return
* The client secret received from Duo for verifying Guacamole users.
*
* @throws GuacamoleException
* If the associated property within guacamole.properties is missing.
*/
public String getClientSecret() throws GuacamoleException {
return environment.getRequiredProperty(DUO_CLIENT_SECRET);
}
/**
* Return the callback URI that will be called by Duo after authentication
* with Duo has been completed. This should be the URI to return the user
* to the Guacamole interface, and will be a full URI.
*
* @return
* The URL for Duo to use to callback to the Guacamole interface after
* authentication has been completed.
*
* @throws GuacamoleException
* If guacamole.properties cannot be read, or if the property is not
* defined.
*/
public URI getRedirectUri() throws GuacamoleException {
return environment.getRequiredProperty(DUO_REDIRECT_URI);
}
/**
* Returns the maximum amount of time to allow for an in-progress Duo
* authentication attempt to be completed, in minutes. A user that takes
* longer than this amount of time to complete authentication with Duo
* will need to try again.
*
* @return
* The maximum amount of time to allow for an in-progress Duo
* authentication attempt to be completed, in minutes.
*
* @throws GuacamoleException
* If the authentication timeout cannot be parsed.
*/
public int getAuthenticationTimeout() throws GuacamoleException {
return environment.getProperty(DUO_AUTH_TIMEOUT, 5);
}
/**
* Returns the list of IP addresses and subnets defined in guacamole.properties
* for which Duo MFA should _not_ be enforced. Users logging in from hosts
* contained in this list will be logged in without the MFA requirement.
*
* @return
* A list of IP addresses and subnets for which Duo MFA should not be
* enforced.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or if an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getBypassHosts() throws GuacamoleException {
return environment.getProperty(DUO_BYPASS_HOSTS, Collections.emptyList());
}
/**
* Returns the list of IP addresses and subnets defined in guacamole.properties
* for which Duo MFA should explicitly be enforced, while logins from all
* other hosts should not enforce MFA. Users logging in from hosts
* contained in this list will be required to complete the Duo MFA authentication,
* while users from all other hosts will be logged in without the MFA requirement.
*
* @return
* A list of IP addresses and subnets for which Duo MFA should be
* explicitly enforced.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or if an invalid IP address
* or subnet is specified.
*/
public List<IPAddress> getEnforceHosts() throws GuacamoleException {
return environment.getProperty(DUO_ENFORCE_HOSTS, Collections.emptyList());
}
}

View File

@@ -0,0 +1,25 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "Duo TFA Authentication Backend",
"namespace" : "duo",
"authProviders" : [
"org.apache.guacamole.auth.duo.DuoAuthenticationProvider"
],
"translations" : [
"translations/ca.json",
"translations/de.json",
"translations/en.json",
"translations/fr.json",
"translations/ja.json",
"translations/ko.json",
"translations/pl.json",
"translations/pt.json",
"translations/ru.json",
"translations/zh.json"
]
}

View File

@@ -0,0 +1,13 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "El codi de validació duo és incorrecte.",
"INFO_DUO_AUTH_REQUIRED" : "Si us plau, autentiqueu amb Duo per continuar."
}
}

View File

@@ -0,0 +1,13 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo Validierungscode nicht korrekt.",
"INFO_DUO_AUTH_REQUIRED" : "Bitte melden Sie sich mit Duo an, um fortzufahren."
}
}

View File

@@ -0,0 +1,14 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo validation code incorrect.",
"INFO_DUO_AUTH_REQUIRED" : "Please authenticate with Duo to continue.",
"INFO_DUO_REDIRECT_PENDING" : "Please wait, redirecting to Duo..."
}
}

View File

@@ -0,0 +1,14 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Code de validation Duo incorrect.",
"INFO_DUO_AUTH_REQUIRED" : "Veuillez vous authentifier avec Duo pour continuer.",
"INFO_DUO_REDIRECT_PENDING" : "Veuillez patienter, redirection vers Duo..."
}
}

View File

@@ -0,0 +1,8 @@
{
"LOGIN" : {
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Codice di convalida Duo errato.",
"INFO_DUO_AUTH_REQUIRED" : "Si prega di autenticarsi con Duo per continuare."
}
}

View File

@@ -0,0 +1,9 @@
{
"LOGIN" : {
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。",
"INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。",
"INFO_DUO_REDIRECT_PENDING" : "Duoへリダイレクトしています。"
}
}

View File

@@ -0,0 +1,8 @@
{
"LOGIN" : {
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo 유효성 검사 코드가 잘못되었습니다.",
"INFO_DUO_AUTH_REQUIRED" : "계속하려면 Duo로 인증하세요."
}
}

View File

@@ -0,0 +1,13 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Nieprawidłowy kod weryfikacyjny Duo.",
"INFO_DUO_AUTH_REQUIRED" : "Aby kontynuować uwierzytelnij się w Duo."
}
}

View File

@@ -0,0 +1,13 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA Backend"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Código de validação Duo incorreto.",
"INFO_DUO_AUTH_REQUIRED" : "Por favor autentique com Duo para continuar."
}
}

View File

@@ -0,0 +1,12 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Бэкенд Duo TFA"
},
"LOGIN" : {
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Неверный код валидации Duo.",
"INFO_DUO_AUTH_REQUIRED" : "Пожалуйста, аутентифицируйтесь в Duo для продолжения."
}
}

View File

@@ -0,0 +1,13 @@
{
"DATA_SOURCE_DUO" : {
"NAME" : "Duo TFA后端"
},
"LOGIN" : {
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo验证码不正确。",
"INFO_DUO_AUTH_REQUIRED" : "请先使用Duo进行身份验证。"
}
}

View File

@@ -0,0 +1,2 @@
target/
*~

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.apache.guacamole</groupId>
<artifactId>guacamole-auth-header</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-header</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.6.0</version>
<relativePath>../</relativePath>
</parent>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<!-- Output tar.gz -->
<formats>
<format>tar.gz</format>
</formats>
<!-- Include licenses and extension .jar -->
<fileSets>
<!-- Include licenses -->
<fileSet>
<outputDirectory></outputDirectory>
<directory>target/licenses</directory>
</fileSet>
<!-- Include extension .jar -->
<fileSet>
<directory>target</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header;
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.apache.guacamole.auth.header.user.AuthenticatedUser;
/**
* Service providing convenience functions for the HTTP Header
* AuthenticationProvider implementation.
*/
public class AuthenticationProviderService {
/**
* Service for retrieving header configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<AuthenticatedUser> authenticatedUserProvider;
/**
* 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 {
// Get the username from the header configured in guacamole.properties
String username = credentials.getHeader(confService.getHttpAuthHeader());
if (username != null) {
// Update credentials with username provided via header for sake of
// ${GUAC_USERNAME} token
credentials.setUsername(username);
AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(username, credentials);
return authenticatedUser;
}
// Authentication not provided via header, yet, so we request it.
throw new GuacamoleInvalidCredentialsException("Invalid login.", CredentialsInfo.USERNAME_PASSWORD);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header;
import com.google.inject.Inject;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
/**
* Service for retrieving configuration information for HTTP header-based
* authentication.
*/
public class ConfigurationService {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Returns the header of the HTTP server as configured with
* guacamole.properties used for HTTP authentication.
* By default, this will be "REMOTE_USER".
*
* @return
* The header used for HTTP authentication, as configured with
* guacamole.properties.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public String getHttpAuthHeader() throws GuacamoleException {
return environment.getProperty(
HTTPHeaderGuacamoleProperties.HTTP_AUTH_HEADER,
"REMOTE_USER"
);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header;
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;
/**
* Guacamole authentication backend which authenticates users using an
* arbitrary external HTTP header. No storage for connections is
* provided - only authentication. Storage must be provided by some other
* extension.
*/
public class HTTPHeaderAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* Injector which will manage the object graph of this authentication
* provider.
*/
private final Injector injector;
/**
* Creates a new HTTPHeaderAuthenticationProvider that authenticates users
* using HTTP headers.
*
* @throws GuacamoleException
* If a required property is missing, or an error occurs while parsing
* a property.
*/
public HTTPHeaderAuthenticationProvider() throws GuacamoleException {
// Set up Guice injector.
injector = Guice.createInjector(
new HTTPHeaderAuthenticationProviderModule(this)
);
}
@Override
public String getIdentifier() {
return "header";
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Pass credentials to authentication service.
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
return authProviderService.authenticateUser(credentials);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header;
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;
/**
* Guice module which configures HTTP header-specific injections.
*/
public class HTTPHeaderAuthenticationProviderModule extends AbstractModule {
/**
* Guacamole server environment.
*/
private final Environment environment;
/**
* A reference to the HTTPHeaderAuthenticationProvider on behalf of which this
* module has configured injection.
*/
private final AuthenticationProvider authProvider;
/**
* Creates a new HTTP header authentication provider module which configures
* injection for the HTTPHeaderAuthenticationProvider.
*
* @param authProvider
* The AuthenticationProvider for which injection is being configured.
*
* @throws GuacamoleException
* If an error occurs while retrieving the Guacamole server
* environment.
*/
public HTTPHeaderAuthenticationProviderModule(AuthenticationProvider authProvider)
throws GuacamoleException {
// Get local environment
this.environment = LocalEnvironment.getInstance();
// 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 HTTPHeader-specific classes
bind(ConfigurationService.class);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header;
import org.apache.guacamole.properties.StringGuacamoleProperty;
/**
* Provides properties required for use of the HTTP header
* authentication provider. These properties will be read from
* guacamole.properties when the HTTP authentication provider is used.
*/
public class HTTPHeaderGuacamoleProperties {
/**
* This class should not be instantiated.
*/
private HTTPHeaderGuacamoleProperties() {}
/**
* A property used to configure the header used for HTTP header authentication.
*/
public static final StringGuacamoleProperty HTTP_AUTH_HEADER = new StringGuacamoleProperty() {
@Override
public String getName() { return "http-auth-header"; }
};
}

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.header.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 HTTP header implementation of AuthenticatedUser, associating a
* username and particular set of credentials with the HTTP authentication
* provider.
*/
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;
/**
* Initializes this AuthenticatedUser using the given username and
* credentials.
*
* @param username
* The username of the user that was authenticated.
*
* @param credentials
* The credentials provided when this user was authenticated.
*/
public void init(String username, Credentials credentials) {
this.credentials = credentials;
setIdentifier(username.toLowerCase());
}
@Override
public AuthenticationProvider getAuthenticationProvider() {
return authProvider;
}
@Override
public Credentials getCredentials() {
return credentials;
}
}

View File

@@ -0,0 +1,12 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "HTTP Header Authentication Extension",
"namespace" : "header",
"authProviders" : [
"org.apache.guacamole.auth.header.HTTPHeaderAuthenticationProvider"
]
}

View File

@@ -0,0 +1,2 @@
target/
*~

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.apache.guacamole</groupId>
<artifactId>guacamole-auth-jdbc-base</artifactId>
<packaging>jar</packaging>
<name>guacamole-auth-jdbc-base</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-jdbc</artifactId>
<version>1.6.0</version>
<relativePath>../../</relativePath>
</parent>
<dependencies>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
<scope>provided</scope>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.19</version>
</dependency>
<!-- MyBatis Guice -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-guice</artifactId>
<version>3.18</version>
<exclusions>
<!-- This dependency appears to be necessary only to provide an
SLF4J bridge for the commons-logging (JCL) system used by DBCP2
an optional dependency that is not being used here. See: https://github.com/mybatis/guice/commit/fbf655ed5bd7ecb00ec070ce20d4fe8aeb6fa9c1 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
<!-- This dependency is LGPL-licensed and is listed in the
mybatis-guice pom.xml as optional. Its only current use within
mybatis-guice is to provide the "Nullable" annotation for a
single member variable, and that annotation does not have
runtime retention. -->
<exclusion>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Guava - Utility Library -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,143 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
/**
* Service which authenticates users based on credentials and provides for
* the creation of corresponding, new UserContext objects for authenticated
* users.
*/
public interface AuthenticationProviderService {
/**
* Authenticates the user having the given credentials, returning a new
* AuthenticatedUser instance only if the credentials are valid. If the
* credentials are invalid or expired, an appropriate GuacamoleException
* will be thrown.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the user is being
* authenticated.
*
* @param credentials
* The credentials to use to produce the AuthenticatedUser.
*
* @return
* A new AuthenticatedUser instance for the user identified by the
* given credentials.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException;
/**
* Returning a new UserContext instance for the given already-authenticated
* user.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being produced.
*
* @param authenticatedUser
* The credentials to use to produce the UserContext.
*
* @return
* A new UserContext instance for the user identified by the given
* credentials, or null if no such user exists within the database.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public UserContext getUserContext(AuthenticationProvider authenticationProvider,
AuthenticatedUser authenticatedUser) throws GuacamoleException;
/**
* Returns an updated UserContext instance for the given
* already-authenticated user. If no changes need be made to the
* UserContext, the original UserContext will be returned.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being updated.
*
* @param context
* The UserContext to update.
*
* @param authenticatedUser
* The AuthenticatedUser associated with the UserContext being updated.
*
* @param credentials
* The credentials most recently submitted by the user. These
* credentials are not guaranteed to be the same as the credentials
* already associated with the AuthenticatedUser.
*
* @return
* A new UserContext instance for the user identified by the given
* credentials.
*
* @throws GuacamoleException
* If an error occurs during authentication, or if the given
* credentials are invalid or expired.
*/
public UserContext updateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException;
/**
* Decorates a UserContext instance for the given already-authenticated user.
* If no decoration is required, the original UserContext will be returned.
*
* @param authenticationProvider
* The AuthenticationProvider on behalf of which the UserContext is
* being decorated.
*
* @param context
* The UserContext to decorate.
*
* @param authenticatedUser
* The AuthenticatedUser associated with the UserContext being decorated.
*
* @param credentials
* The credentials most recently submitted by the user. These
* credentials are not guaranteed to be the same as the credentials
* already associated with the AuthenticatedUser.
*
* @return
* A decorated UserContext instance for the user identified by the given
* credentials, or the original user context if no decoration is required.
*
* @throws GuacamoleException
* If the an error occurs during decoration of the UserContext.
*/
public UserContext decorateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException;
}

View File

@@ -0,0 +1,156 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.guacamole.GuacamoleException;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Pooled DataSource implementation which dynamically retrieves the database
* username and password from the Guacamole server environment each time a
* new database connection is created.
*/
@Singleton
public class DynamicallyAuthenticatedDataSource extends PooledDataSource {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(DynamicallyAuthenticatedDataSource.class);
/**
* Creates a new DynamicallyAuthenticatedDataSource which dynamically
* retrieves database credentials from the given JDBCEnvironment each time
* a new database connection is needed.
*
* @param environment
* The JDBCEnvironment that should be used to retrieve database
* credentials.
*
* @param driverClassLoader
* @param driver
* @param url
*/
@Inject
public DynamicallyAuthenticatedDataSource(JDBCEnvironment environment,
@Named(value="JDBC.driverClassLoader") ClassLoader driverClassLoader,
@Named(value="JDBC.driver") String driver,
@Named(value="JDBC.url") String url) {
// Wrap unpooled DataSource, overriding the connection process such
// that credentials are dynamically retrieved from the JDBCEnvironment
super(new UnpooledDataSource(driverClassLoader, driver, url, null, null) {
@Override
public Connection getConnection() throws SQLException {
try {
logger.debug("Creating new database connection for pool.");
return super.getConnection(environment.getUsername(), environment.getPassword());
}
catch (GuacamoleException e) {
throw new SQLException("Retrieval of database credentials failed.", e);
}
}
});
// Force recalculation of expectedConnectionTypeCode. The
// PooledDataSource constructor accepting a single UnpooledDataSource
// will otherwise leave this value uninitialized, resulting in all
// connections failing to pass sanity checks and never being returned
// to the pool.
super.forceCloseAll();
}
@Override
@Inject(optional=true)
public void setPoolPingConnectionsNotUsedFor(
@Named("mybatis.pooled.pingConnectionsNotUsedFor") int milliseconds) {
super.setPoolPingConnectionsNotUsedFor(milliseconds);
}
@Override
@Inject(optional=true)
public void setPoolPingEnabled(@Named("mybatis.pooled.pingEnabled") boolean poolPingEnabled) {
super.setPoolPingEnabled(poolPingEnabled);
}
@Override
@Inject(optional=true)
public void setPoolPingQuery(@Named("mybatis.pooled.pingQuery") String poolPingQuery) {
super.setPoolPingQuery(poolPingQuery);
}
@Override
@Inject(optional=true)
public void setPoolTimeToWait(@Named("mybatis.pooled.timeToWait") int poolTimeToWait) {
super.setPoolTimeToWait(poolTimeToWait);
}
@Override
@Inject(optional=true)
public void setPoolMaximumCheckoutTime(
@Named("mybatis.pooled.maximumCheckoutTime") int poolMaximumCheckoutTime) {
super.setPoolMaximumCheckoutTime(poolMaximumCheckoutTime);
}
@Override
@Inject(optional=true)
public void setPoolMaximumIdleConnections(
@Named("mybatis.pooled.maximumIdleConnections") int poolMaximumIdleConnections) {
super.setPoolMaximumIdleConnections(poolMaximumIdleConnections);
}
@Override
@Inject(optional=true)
public void setPoolMaximumActiveConnections(
@Named("mybatis.pooled.maximumActiveConnections") int poolMaximumActiveConnections) {
super.setPoolMaximumActiveConnections(poolMaximumActiveConnections);
}
@Override
@Inject(optional=true)
public void setDriverProperties(@Named("JDBC.driverProperties") Properties driverProps) {
super.setDriverProperties(driverProps);
}
@Override
@Inject(optional=true)
public void setDefaultAutoCommit(@Named("JDBC.autoCommit") boolean defaultAutoCommit) {
super.setDefaultAutoCommit(defaultAutoCommit);
}
@Override
@Inject(optional=true)
public void setLoginTimeout(@Named("JDBC.loginTimeout") int loginTimeout) {
super.setLoginTimeout(loginTimeout);
}
}

View File

@@ -0,0 +1,132 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Inject;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnectionRecord;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DelegatingConnection;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* Connection implementation that creates a history record when the connection
* is established, and returns a HistoryTrackingTunnel to automatically set the
* end date when the connection is closed.
*/
public class HistoryTrackingConnection extends DelegatingConnection {
/**
* The current Guacamole user.
*/
private final User currentUser;
/**
* The remote host that the user connected from.
*/
private final String remoteHost;
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* The environment in which Guacamole is running.
*/
private final Environment environment = LocalEnvironment.getInstance();
/**
* Creates a new HistoryConnection that wraps the given connection,
* automatically creating a history record when the connection is
* established, and returning a HistoryTrackingTunnel to set the end
* date on the history entry when the connection is closed.
*
* @param currentUser
* The current Guacamole user.
*
* @param remoteHost
* The remote host that the user connected from.
*
* @param connection
* The connection to wrap.
*
* @param connectionRecordMapper
* The connection record mapper that will be used to write the connection history records.
*/
public HistoryTrackingConnection(User currentUser, String remoteHost, Connection connection, ConnectionRecordMapper connectionRecordMapper) {
super(connection);
this.currentUser = currentUser;
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Create a connection record model, starting at the current date/time
ConnectionRecordModel connectionRecordModel = new ConnectionRecordModel();
connectionRecordModel.setStartDate(new Date());
// Set the user information
connectionRecordModel.setUsername(this.currentUser.getIdentifier());
connectionRecordModel.setRemoteHost(this.remoteHost);
// Set the connection information
connectionRecordModel.setConnectionName(this.getDelegateConnection().getName());
// Insert the connection history record to mark the start of this connection
connectionRecordMapper.insert(connectionRecordModel,
environment.getCaseSensitivity());
// Include history record UUID as token
ModeledConnectionRecord modeledRecord = new ModeledConnectionRecord(connectionRecordModel);
Map<String, String> updatedTokens = new HashMap<>(tokens);
updatedTokens.put("HISTORY_UUID", modeledRecord.getUUID().toString());
// Connect, and wrap the tunnel for return
GuacamoleTunnel tunnel = super.connect(info, updatedTokens);
return new HistoryTrackingTunnel(
tunnel, this.connectionRecordMapper, connectionRecordModel);
}
/**
* Get the Connection wrapped by this HistoryTrackingConnection.
*
* @return
* The wrapped Connection.
*/
public Connection getWrappedConnection() {
return getDelegateConnection();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DecoratingDirectory;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
/**
* A connection directory that returns HistoryTrackingConnection-wrapped connections
* when queried.
*/
public class HistoryTrackingConnectionDirectory extends DecoratingDirectory<Connection> {
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* The user that directory operations are being performed for.
*/
private final User user;
/**
* The remote host that the user connected from.
*/
private final String remoteHost;
/**
* Create a new history tracking connection directory. Any connection retrieved from this
* directory will be wrapped in a HistoryTrackingConnection, enabling connection history
* records to be written with the provided connection record mapper.
*
* @param directory
* The connection directory to wrap.
*
* @param user
* The user associated with the connection directory.
*
* @param remoteHost
* The remote host that the user connected from.
*
* @param connectionRecordMapper
* The connection record mapper that will be used to write the connection history records.
*/
public HistoryTrackingConnectionDirectory(Directory<Connection> directory, User user, String remoteHost, ConnectionRecordMapper connectionRecordMapper) {
super(directory);
this.user = user;
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
protected Connection decorate(Connection connection) throws GuacamoleException {
// Wrap the connection in a history-tracking layer
return new HistoryTrackingConnection(
this.user, this.remoteHost, connection, this.connectionRecordMapper);
}
@Override
protected Connection undecorate(Connection connection) throws GuacamoleException {
// If the connection was wrapped, unwrap it
if (connection instanceof HistoryTrackingConnection) {
return ((HistoryTrackingConnection) connection).getWrappedConnection();
}
// Otherwise, return the unwrapped connection directly
return connection;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import java.util.Date;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordModel;
import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
import org.apache.guacamole.net.GuacamoleTunnel;
/**
* Tunnel implementation which automatically writes an end date for the
* provided connection history record model using the provided connection
* history mapper, when the tunnel is closed.
*/
public class HistoryTrackingTunnel extends DelegatingGuacamoleTunnel {
/**
* The connection for which this tunnel was established.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* The user for which this tunnel was established.
*/
private final ConnectionRecordModel connectionRecordModel;
/**
* Creates a new HistoryTrackingTunnel that wraps the given tunnel,
* automatically setting the end date for the provided connection history records,
* using the provided connection history record mapper.
*
* @param tunnel
* The tunnel to wrap.
*
* @param connectionRecordMapper
* The mapper to use when writing connection history records.
*
* @param connectionRecordModel
* The connection history record model representing the in-progress connection.
*/
public HistoryTrackingTunnel(GuacamoleTunnel tunnel,
ConnectionRecordMapper connectionRecordMapper, ConnectionRecordModel connectionRecordModel) {
super(tunnel);
// Store the connection record mapper and model for history tracking
this.connectionRecordMapper = connectionRecordMapper;
this.connectionRecordModel = connectionRecordModel;
}
@Override
public void close() throws GuacamoleException {
// Set the end date to complete the connection history record
this.connectionRecordModel.setEndDate(new Date());
this.connectionRecordMapper.updateEndDate(this.connectionRecordModel);
super.close();
}
}

View File

@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.DelegatingUserContext;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.UserContext;
/**
* DelegatingUserContext implementation which writes connection history records
* when connections are established and closed.
*/
public class HistoryTrackingUserContext extends DelegatingUserContext {
/**
* The remote host that the user associated with the user context
* connected from.
*/
private final String remoteHost;
/**
* The connection record mapper to use when writing history entries for
* established connections.
*/
private final ConnectionRecordMapper connectionRecordMapper;
/**
* Creates a new HistoryTrackingUserContext which wraps the given
* UserContext, allowing for tracking of connection history external to
* this authentication provider.
*
* @param userContext
* The UserContext to wrap.
*
* @param remoteHost
* The host that the user associated with the given user context connected from.
*
* @param connectionRecordMapper
* The mapper to use when writing connection history entries to the DB.
*/
public HistoryTrackingUserContext(UserContext userContext, String remoteHost, ConnectionRecordMapper connectionRecordMapper) {
super(userContext);
this.remoteHost = remoteHost;
this.connectionRecordMapper = connectionRecordMapper;
}
@Override
public Directory<Connection> getConnectionDirectory() throws GuacamoleException {
return new HistoryTrackingConnectionDirectory(
super.getConnectionDirectory(), self(),
this.remoteHost, this.connectionRecordMapper);
}
}

View File

@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.AuthenticatedUser;
/**
* Provides a base implementation of an AuthenticationProvider which delegates
* the various function calls to an underlying AuthenticationProviderService
* implementation. As such a service is injectable by Guice, this provides a
* means for Guice to (effectively) apply dependency injection to an
* AuthenticationProvider, even though it is the AuthenticationProvider that
* serves as the entry point.
*/
public abstract class InjectedAuthenticationProvider extends AbstractAuthenticationProvider {
/**
* The AuthenticationProviderService to which all AuthenticationProvider
* calls will be delegated.
*/
private final AuthenticationProviderService authProviderService;
/**
* Creates a new AuthenticationProvider that delegates all calls to an
* underlying AuthenticationProviderService. The behavior of the
* AuthenticationProvider is defined by the given
* AuthenticationProviderService implementation, which will be injected by
* the Guice Injector provided by the given JDBCInjectorProvider.
*
* @param injectorProvider
* A JDBCInjectorProvider instance which provides singleton instances
* of a Guice Injector, pre-configured to set up all injections and
* access to the underlying database via MyBatis.
*
* @param authProviderServiceClass
* The AuthenticationProviderService implementation which defines the
* behavior of this AuthenticationProvider.
*
* @throws GuacamoleException
* If the Injector cannot be created due to an error.
*/
public InjectedAuthenticationProvider(JDBCInjectorProvider injectorProvider,
Class<? extends AuthenticationProviderService> authProviderServiceClass)
throws GuacamoleException {
Injector injector = injectorProvider.get();
authProviderService = injector.getInstance(authProviderServiceClass);
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
return authProviderService.authenticateUser(this, credentials);
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
return authProviderService.getUserContext(this, authenticatedUser);
}
@Override
public UserContext updateUserContext(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return authProviderService.updateUserContext(this, context,
authenticatedUser, credentials);
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
return authProviderService.decorateUserContext(this, context,
authenticatedUser, credentials);
}
}

View File

@@ -0,0 +1,209 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Scopes;
import javax.sql.DataSource;
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
import org.apache.guacamole.auth.jdbc.connectiongroup.ModeledConnectionGroup;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupDirectory;
import org.apache.guacamole.auth.jdbc.connection.ConnectionDirectory;
import org.apache.guacamole.auth.jdbc.connection.ModeledGuacamoleConfiguration;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionSet;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.auth.jdbc.user.UserDirectory;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionMapper;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionMapper;
import org.apache.guacamole.auth.jdbc.user.UserMapper;
import org.apache.guacamole.auth.jdbc.connectiongroup.ConnectionGroupService;
import org.apache.guacamole.auth.jdbc.connection.ConnectionService;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.security.PasswordEncryptionService;
import org.apache.guacamole.auth.jdbc.security.SHA256PasswordEncryptionService;
import org.apache.guacamole.auth.jdbc.security.SaltService;
import org.apache.guacamole.auth.jdbc.security.SecureRandomSaltService;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionService;
import org.apache.guacamole.auth.jdbc.user.UserService;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionSet;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionSet;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionSet;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionDirectory;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionSet;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionService;
import org.apache.guacamole.auth.jdbc.activeconnection.TrackedActiveConnection;
import org.apache.guacamole.auth.jdbc.base.EntityMapper;
import org.apache.guacamole.auth.jdbc.base.EntityService;
import org.apache.guacamole.auth.jdbc.connection.ConnectionParameterMapper;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionService;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionSet;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionSet;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService;
import org.apache.guacamole.auth.jdbc.sharing.HashSharedConnectionMap;
import org.apache.guacamole.auth.jdbc.sharing.SecureRandomShareKeyGenerator;
import org.apache.guacamole.auth.jdbc.sharing.ShareKeyGenerator;
import org.apache.guacamole.auth.jdbc.sharing.SharedConnectionMap;
import org.apache.guacamole.auth.jdbc.sharingprofile.ModeledSharingProfile;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileDirectory;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileMapper;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileParameterMapper;
import org.apache.guacamole.auth.jdbc.sharingprofile.SharingProfileService;
import org.apache.guacamole.auth.jdbc.tunnel.RestrictedGuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.PasswordRecordMapper;
import org.apache.guacamole.auth.jdbc.user.UserRecordMapper;
import org.apache.guacamole.auth.jdbc.usergroup.ModeledUserGroup;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupDirectory;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMemberUserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupMemberUserMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupParentUserGroupMapper;
import org.apache.guacamole.auth.jdbc.usergroup.UserGroupService;
import org.mybatis.guice.MyBatisModule;
import org.apache.guacamole.auth.jdbc.user.UserParentUserGroupMapper;
/**
* Guice module which configures the injections used by the JDBC authentication
* provider base. This module MUST be included in the Guice injector, or
* authentication providers based on JDBC will not function.
*/
public class JDBCAuthenticationProviderModule extends MyBatisModule {
/**
* The environment of the Guacamole server.
*/
private final JDBCEnvironment environment;
/**
* Creates a new JDBC authentication provider module that configures the
* various injected base classes using the given environment, and provides
* connections using the given socket service.
*
* @param environment
* The environment to use to configure injected classes.
*/
public JDBCAuthenticationProviderModule(JDBCEnvironment environment) {
this.environment = environment;
}
@Override
protected void initialize() {
// Datasource
bind(DataSource.class).to(DynamicallyAuthenticatedDataSource.class);
// Transaction factory
bindTransactionFactoryType(JdbcTransactionFactory.class);
// Set the JDBC Auth provider to use batch execution if enabled
if (environment.shouldUseBatchExecutor())
bindConfigurationSetting(configuration -> {
configuration.setDefaultExecutorType(ExecutorType.BATCH);
});
// Add MyBatis mappers
addMapperClass(ConnectionMapper.class);
addMapperClass(ConnectionGroupMapper.class);
addMapperClass(ConnectionGroupPermissionMapper.class);
addMapperClass(ConnectionPermissionMapper.class);
addMapperClass(ConnectionRecordMapper.class);
addMapperClass(ConnectionParameterMapper.class);
addMapperClass(EntityMapper.class);
addMapperClass(PasswordRecordMapper.class);
addMapperClass(SystemPermissionMapper.class);
addMapperClass(SharingProfileMapper.class);
addMapperClass(SharingProfileParameterMapper.class);
addMapperClass(SharingProfilePermissionMapper.class);
addMapperClass(UserGroupMapper.class);
addMapperClass(UserGroupMemberUserGroupMapper.class);
addMapperClass(UserGroupMemberUserMapper.class);
addMapperClass(UserGroupParentUserGroupMapper.class);
addMapperClass(UserGroupPermissionMapper.class);
addMapperClass(UserMapper.class);
addMapperClass(UserParentUserGroupMapper.class);
addMapperClass(UserPermissionMapper.class);
addMapperClass(UserRecordMapper.class);
// Bind core implementations of guacamole-ext classes
bind(ActiveConnectionDirectory.class);
bind(ActiveConnectionPermissionSet.class);
bind(JDBCEnvironment.class).toInstance(environment);
bind(ConnectionDirectory.class);
bind(ConnectionGroupDirectory.class);
bind(ConnectionGroupPermissionSet.class);
bind(ConnectionPermissionSet.class);
bind(ModeledConnection.class);
bind(ModeledConnectionGroup.class);
bind(ModeledGuacamoleConfiguration.class);
bind(ModeledSharingProfile.class);
bind(ModeledUser.class);
bind(ModeledUserContext.class);
bind(ModeledUserGroup.class);
bind(RootConnectionGroup.class);
bind(SharingProfileDirectory.class);
bind(SharingProfilePermissionSet.class);
bind(SystemPermissionSet.class);
bind(TrackedActiveConnection.class);
bind(UserDirectory.class);
bind(UserGroupDirectory.class);
bind(UserGroupPermissionSet.class);
bind(UserPermissionSet.class);
// Bind services
bind(ActiveConnectionService.class);
bind(ActiveConnectionPermissionService.class);
bind(ConnectionGroupPermissionService.class);
bind(ConnectionGroupService.class);
bind(ConnectionPermissionService.class);
bind(ConnectionSharingService.class);
bind(ConnectionService.class);
bind(EntityService.class);
bind(GuacamoleTunnelService.class).to(RestrictedGuacamoleTunnelService.class);
bind(PasswordEncryptionService.class).to(SHA256PasswordEncryptionService.class);
bind(PasswordPolicyService.class);
bind(SaltService.class).to(SecureRandomSaltService.class);
bind(SharedConnectionMap.class).to(HashSharedConnectionMap.class).in(Scopes.SINGLETON);
bind(ShareKeyGenerator.class).to(SecureRandomShareKeyGenerator.class).in(Scopes.SINGLETON);
bind(SharingProfilePermissionService.class);
bind(SharingProfileService.class);
bind(SystemPermissionService.class);
bind(UserGroupService.class);
bind(UserGroupPermissionService.class);
bind(UserPermissionService.class);
bind(UserService.class);
}
}

View File

@@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.connection.ConnectionRecordMapper;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
import org.apache.guacamole.auth.jdbc.sharing.user.SharedAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.user.ModeledUser;
import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
import org.apache.guacamole.auth.jdbc.user.PrivilegedModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.user.UserService;
import org.apache.guacamole.language.TranslatableGuacamoleClientException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
/**
* AuthenticationProviderService implementation which authenticates users with
* a username/password pair, producing new UserContext objects which are backed
* by an underlying, arbitrary database.
*/
public class JDBCAuthenticationProviderService implements AuthenticationProviderService {
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Service for accessing users.
*/
@Inject
private UserService userService;
/**
* Service for enforcing password complexity policies.
*/
@Inject
private PasswordPolicyService passwordPolicyService;
/**
* Provider for retrieving UserContext instances.
*/
@Inject
private Provider<ModeledUserContext> userContextProvider;
/**
* Mapper for writing connection history.
*/
@Inject
private ConnectionRecordMapper connectionRecordMapper;
@Override
public AuthenticatedUser authenticateUser(AuthenticationProvider authenticationProvider,
Credentials credentials) throws GuacamoleException {
// Authenticate user
AuthenticatedUser user = userService.retrieveAuthenticatedUser(authenticationProvider, credentials);
if (user != null)
return user;
// Otherwise, unauthorized
throw new GuacamoleInvalidCredentialsException("Invalid login", CredentialsInfo.USERNAME_PASSWORD);
}
@Override
public ModeledUserContext getUserContext(AuthenticationProvider authenticationProvider,
AuthenticatedUser authenticatedUser) throws GuacamoleException {
// Always allow but provide no data for users authenticated via our own
// connection sharing links
if (authenticatedUser instanceof SharedAuthenticatedUser)
return null;
// Set semantic flags based on context
boolean databaseCredentialsUsed = (authenticatedUser instanceof ModeledAuthenticatedUser);
boolean databaseRestrictionsApplicable = (databaseCredentialsUsed || environment.isUserRequired());
// Retrieve user account for already-authenticated user
ModeledUser user = userService.retrieveUser(authenticationProvider, authenticatedUser);
ModeledUserContext context = userContextProvider.get();
if (user != null && !user.isDisabled()) {
// Enforce applicable account restrictions
if (databaseRestrictionsApplicable) {
// Verify user account is still valid as of today
if (!user.isAccountValid())
throw new TranslatableGuacamoleClientException("User "
+ "account is no longer valid.",
"LOGIN.ERROR_NOT_VALID");
// Verify user account is allowed to be used at the current time
if (!user.isAccountAccessible())
throw new TranslatableGuacamoleClientException("User "
+ "account may not be used at this time.",
"LOGIN.ERROR_NOT_ACCESSIBLE");
}
// Update password if password is expired AND the password was
// actually involved in the authentication process
if (databaseCredentialsUsed) {
if (user.isExpired() || passwordPolicyService.isPasswordExpired(user))
userService.resetExpiredPassword(user, authenticatedUser.getCredentials());
}
}
// If no user account is found, and database-specific account
// restrictions do not apply, get a skeleton user.
else if (!databaseRestrictionsApplicable) {
user = userService.retrieveSkeletonUser(authenticationProvider, authenticatedUser);
// If auto account creation is enabled, add user to DB.
if (environment.autoCreateAbsentAccounts()) {
ModeledUser createdUser = userService.createObject(new PrivilegedModeledAuthenticatedUser(user.getCurrentUser()), user);
user.setModel(createdUser.getModel());
}
}
// Veto authentication result only if database-specific account
// restrictions apply in this situation
else
throw new GuacamoleInvalidCredentialsException("Invalid login",
CredentialsInfo.USERNAME_PASSWORD);
// Initialize the UserContext with the user account and return it.
context.init(user.getCurrentUser());
context.recordUserLogin();
return context;
}
@Override
public UserContext updateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Refresh the user context
return getUserContext(authenticationProvider, authenticatedUser);
}
@Override
public UserContext decorateUserContext(AuthenticationProvider authenticationProvider,
UserContext context, AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Track connection history only for external connections, and only if enabled in the config
if (environment.trackExternalConnectionHistory() && context.getAuthenticationProvider() != authenticationProvider) {
return new HistoryTrackingUserContext(context, credentials.getRemoteHostname(), connectionRecordMapper);
}
return context;
}
}

View File

@@ -0,0 +1,275 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.security.PasswordPolicy;
import org.apache.guacamole.environment.DelegatingEnvironment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.ibatis.session.SqlSession;
/**
* A JDBC-specific implementation of Environment that defines generic properties
* intended for use within JDBC based authentication providers.
*/
public abstract class JDBCEnvironment extends DelegatingEnvironment {
/**
* Constructs a new JDBCEnvironment using an underlying LocalEnviroment to
* read properties from the file system.
*/
public JDBCEnvironment() {
super(LocalEnvironment.getInstance());
}
/**
* Returns whether a database user account is required for authentication to
* succeed, even if another authentication provider has already
* authenticated the user.
*
* @return
* true if database user accounts are required for absolutely all
* authentication attempts, false otherwise.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract boolean isUserRequired() throws GuacamoleException;
/**
* Returns the maximum number of concurrent connections to allow overall.
* As this limit applies globally (independent of which connection is in
* use or which user is using it), this setting cannot be overridden at the
* connection level. Zero denotes unlimited.
*
* @return
* The maximum allowable number of concurrent connections.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getAbsoluteMaxConnections() throws GuacamoleException;
/**
* Returns the maximum number of identifiers/parameters to be
* included in a single batch when executing SQL statements.
*
* @return
* The maximum number of identifiers/parameters to be included
* in a single batch.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getBatchSize() throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection, unless specified differently on an individual
* connection. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections
* to any connection.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxConnections() throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection group, unless specified differently on an individual
* connection group. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections
* to any connection group.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxGroupConnections()
throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection by an individual user, unless specified differently on
* an individual connection. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections to
* any connection by an individual user.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxConnectionsPerUser()
throws GuacamoleException;
/**
* Returns the default maximum number of concurrent connections to allow to
* any one connection group by an individual user, unless specified
* differently on an individual connection group. Zero denotes unlimited.
*
* @return
* The default maximum allowable number of concurrent connections to
* any connection group by an individual user.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property.
*/
public abstract int getDefaultMaxGroupConnectionsPerUser()
throws GuacamoleException;
/**
* Returns the policy which applies to newly-set passwords. Passwords which
* apply to Guacamole user accounts will be required to conform to this
* policy.
*
* @return
* The password policy which applies to Guacamole user accounts.
*/
public abstract PasswordPolicy getPasswordPolicy();
/**
* Returns whether the database supports recursive queries. Many database
* engines support recursive queries through CTEs. If recursive queries are
* not supported, queries that are intended to be recursive may need to be
* invoked multiple times to retrieve the same data.
*
* @param session
* The SqlSession provided by MyBatis for the current transaction.
*
* @return
* true if the database supports recursive queries, false otherwise.
*/
public abstract boolean isRecursiveQuerySupported(SqlSession session);
/**
* Returns a boolean value representing whether or not the JDBC module
* should automatically create accounts within the database for users that
* are successfully authenticated via other extensions. Returns true if
* accounts should be auto-created, otherwise returns false.
*
* @return
* true if user accounts should be automatically created within the
* database when authentication succeeds from another extension;
* otherwise false.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean autoCreateAbsentAccounts() throws GuacamoleException;
/**
* Returns the username that should be used when authenticating with the
* database containing the Guacamole authentication tables.
*
* @return
* The username for the database.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property value, or if the
* value was not set, as this property is required.
*/
public abstract String getUsername() throws GuacamoleException;
/**
* Returns the password that should be used authenticating with the
* database containing the Guacamole authentication tables.
*
* @return
* The password for the database.
*
* @throws GuacamoleException
* If an error occurs while retrieving the property value, or if the
* value was not set, as this property is required.
*/
public abstract String getPassword() throws GuacamoleException;
/**
* Returns whether the given Java class is defined within the classpath.
*
* @param classname
* The name of the Java class to check.
*
* @return
* true if the given Java class is present within the classpath, false
* otherwise.
*/
public static boolean isClassDefined(String classname) {
try {
Class.forName(classname, false, JDBCEnvironment.class.getClassLoader());
return true;
}
catch (ClassNotFoundException e) {
return false;
}
}
/**
* Returns a boolean value representing whether or not the JDBC module
* should automatically track connection history for external connections,
* i.e. connections not originated from within the JDBC auth provider
* itself.
*
* @return
* true if connection history should be tracked for connections that
* do not originate from within this JDBC auth provider, false otherwise.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean trackExternalConnectionHistory() throws GuacamoleException;
/**
* Returns a boolean value representing whether access time windows should
* be enforced for active connections - i.e. whether a currently-connected
* user should be disconnected upon the closure of an access window.
*
* @return
* true if a connected user should be disconnected upon an access time
* window closing, false otherwise.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
public abstract boolean enforceAccessWindowsForActiveSessions() throws GuacamoleException;
/**
* Returns true if the JDBC batch executor should be used by default, false
* otherwise. The batch executor allows repeated updates to be batched
* together for improved performance.
* See https://mybatis.org/mybatis-3/java-api.html#sqlSessions
*
* @return
* true if the batch executor should be used by default, false otherwise.
*/
public boolean shouldUseBatchExecutor() {
// Unless otherwise overwritten due to implementation-specific problems,
// all JDBC extensions should use the batch executor if possible to
// ensure the best performance for repetitive queries
return true;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc;
import com.google.inject.Injector;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.guacamole.GuacamoleException;
/**
* A caching provider of singleton Guice Injector instances. The first call to
* get() will return a new instance of the Guice Injector, while all subsequent
* calls will return that same instance. It is up to implementations of this
* class to define how the Guice Injector will be created through defining the
* create() function.
*
* IMPORTANT: Because the Injector returned by get() is cached statically, only
* ONE implementation of this class may be used within any individual
* classloader. Within the context of the JDBC extension, as long as each built
* extension only provides one subclass of this class, things should work
* properly, as each extension is given its own classloader by Guacamole.
*/
public abstract class JDBCInjectorProvider {
/**
* An AtomicReference wrapping the cached Guice Injector. If the Injector
* has not yet been created, null will be wrapped instead.
*/
private static final AtomicReference<Injector> injector = new AtomicReference<Injector>(null);
/**
* Creates a new instance of the Guice Injector which should be used
* across the entire JDBC authentication extension. This function will
* generally only be called once, but multiple invocations are possible if
* get() is invoked several times concurrently prior to the Injector being
* cached.
*
* @return
* @throws GuacamoleException
*/
protected abstract Injector create() throws GuacamoleException;
/**
* Returns a common, singleton instance of a Guice Injector, configured for
* the injections required by the JDBC authentication extension. The result
* of the first call to this function will be cached statically within this
* class, and will be returned for all subsequent calls.
*
* @return
* A singleton instance of the Guice Injector used across the entire
* JDBC authentication extension.
*
* @throws GuacamoleException
* If the Injector cannot be created due to an error.
*/
public Injector get() throws GuacamoleException {
// Return existing Injector if already created
Injector value = injector.get();
if (value != null)
return value;
// Explicitly create and store new Injector only if necessary
injector.compareAndSet(null, create());
// Consistently return the same Injector, even if two create operations
// happen concurrently
return injector.get();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.JDBCDirectory;
import org.apache.guacamole.net.auth.ActiveConnection;
/**
* Implementation of a Directory which contains all currently-active
* connections.
*/
public class ActiveConnectionDirectory extends JDBCDirectory<ActiveConnection> {
/**
* Service for retrieving and manipulating active connections.
*/
@Inject
private ActiveConnectionService activeConnectionService;
@Override
public ActiveConnection get(String identifier) throws GuacamoleException {
return activeConnectionService.retrieveObject(getCurrentUser(), identifier);
}
@Override
public Collection<ActiveConnection> getAll(Collection<String> identifiers)
throws GuacamoleException {
Collection<TrackedActiveConnection> objects = activeConnectionService.retrieveObjects(getCurrentUser(), identifiers);
return Collections.<ActiveConnection>unmodifiableCollection(objects);
}
@Override
public Set<String> getIdentifiers() throws GuacamoleException {
return activeConnectionService.getIdentifiers(getCurrentUser());
}
@Override
public void add(ActiveConnection object) throws GuacamoleException {
activeConnectionService.createObject(getCurrentUser(), object);
}
@Override
public void update(ActiveConnection object) throws GuacamoleException {
TrackedActiveConnection connection = (TrackedActiveConnection) object;
activeConnectionService.updateObject(getCurrentUser(), connection);
}
@Override
public void remove(String identifier) throws GuacamoleException {
activeConnectionService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.EntityModel;
import org.apache.guacamole.auth.jdbc.base.ModeledPermissions;
import org.apache.guacamole.auth.jdbc.permission.AbstractPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionService;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating active connections.
*/
public class ActiveConnectionPermissionService
extends AbstractPermissionService<ObjectPermissionSet, ObjectPermission>
implements ObjectPermissionService {
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Provider for active connection permission sets.
*/
@Inject
private Provider<ActiveConnectionPermissionSet> activeConnectionPermissionSetProvider;
@Override
public boolean hasPermission(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
ObjectPermission.Type type, String identifier,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions
Set<ObjectPermission> permissions = retrievePermissions(user,
targetEntity, effectiveGroups);
// Permission is granted if retrieved permissions contains the
// requested permission
ObjectPermission permission = new ObjectPermission(type, identifier);
return permissions.contains(permission);
}
@Override
public Set<ObjectPermission> retrievePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Retrieve permissions only if allowed
if (canReadPermissions(user, targetEntity)) {
// Privileged accounts (such as administrators or UserContexts
// returned by getPrivileged()) may always access active connections
boolean isPrivileged = targetEntity.isPrivileged();
// Get all active connections
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// We have READ, and possibly DELETE, on all active connections
Set<ObjectPermission> permissions = new HashSet<>();
for (ActiveConnectionRecord record : records) {
// Add implicit READ
String identifier = record.getUUID().toString();
permissions.add(new ObjectPermission(ObjectPermission.Type.READ, identifier));
// If the target user is privileged, or the connection belongs
// to the target user, then they can DELETE
if (isPrivileged || targetEntity.isUser(record.getUsername()))
permissions.add(new ObjectPermission(ObjectPermission.Type.DELETE, identifier));
}
return permissions;
}
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public Collection<String> retrieveAccessibleIdentifiers(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission.Type> permissionTypes,
Collection<String> identifiers, Set<String> effectiveGroups)
throws GuacamoleException {
Set<ObjectPermission> permissions = retrievePermissions(user, targetEntity, effectiveGroups);
Collection<String> accessibleObjects = new ArrayList<String>(permissions.size());
// For each identifier/permission combination
for (String identifier : identifiers) {
for (ObjectPermission.Type permissionType : permissionTypes) {
// Add identifier if at least one requested permission is granted
ObjectPermission permission = new ObjectPermission(permissionType, identifier);
if (permissions.contains(permission)) {
accessibleObjects.add(identifier);
break;
}
}
}
return accessibleObjects;
}
@Override
public ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Set<String> effectiveGroups) throws GuacamoleException {
// Create permission set for requested entity
ActiveConnectionPermissionSet permissionSet = activeConnectionPermissionSetProvider.get();
permissionSet.init(user, targetEntity, effectiveGroups);
return permissionSet;
}
@Override
public void createPermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Creating active connection permissions is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void deletePermissions(ModeledAuthenticatedUser user,
ModeledPermissions<? extends EntityModel> targetEntity,
Collection<ObjectPermission> permissions)
throws GuacamoleException {
// Deleting active connection permissions is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;
import com.google.inject.Inject;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionSet;
/**
* An implementation of ObjectPermissionSet which uses an injected service to
* query and manipulate the permissions associated with active connections.
*/
public class ActiveConnectionPermissionSet extends ObjectPermissionSet {
/**
* Service for querying and manipulating active connection permissions.
*/
@Inject
private ActiveConnectionPermissionService activeConnectionPermissionService;
@Override
protected ObjectPermissionService getObjectPermissionService() {
return activeConnectionPermissionService;
}
}

View File

@@ -0,0 +1,219 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.DirectoryObjectService;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating active connections.
*/
public class ActiveConnectionService
implements DirectoryObjectService<TrackedActiveConnection, ActiveConnection> {
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* Provider for active connections.
*/
@Inject
private Provider<TrackedActiveConnection> trackedActiveConnectionProvider;
@Override
public TrackedActiveConnection retrieveObject(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Pull objects having given identifier
Collection<TrackedActiveConnection> objects = retrieveObjects(user, Collections.singleton(identifier));
// If no such object, return null
if (objects.isEmpty())
return null;
// The object collection will have exactly one element unless the
// database has seriously lost integrity
assert(objects.size() == 1);
// Return first and only object
return objects.iterator().next();
}
@Override
public Collection<TrackedActiveConnection> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException {
String username = user.getIdentifier();
boolean isPrivileged = user.isPrivileged();
Set<String> identifierSet = new HashSet<String>(identifiers);
// Retrieve all visible connections (permissions enforced by tunnel service)
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// Restrict to subset of records which match given identifiers
Collection<TrackedActiveConnection> activeConnections = new ArrayList<TrackedActiveConnection>(identifiers.size());
for (ActiveConnectionRecord record : records) {
// The current user should have access to sensitive information and
// be able to connect to (join) the active connection if they are
// the user that started the connection OR the user is an admin
boolean hasPrivilegedAccess =
isPrivileged || username.equals(record.getUsername());
// Add connection if within requested identifiers
if (identifierSet.contains(record.getUUID().toString())) {
TrackedActiveConnection activeConnection = trackedActiveConnectionProvider.get();
activeConnection.init(user, record, hasPrivilegedAccess, hasPrivilegedAccess);
activeConnections.add(activeConnection);
}
}
return activeConnections;
}
@Override
public void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException {
// Close connection, if it exists and we have permission
ActiveConnection activeConnection = retrieveObject(user, identifier);
if (activeConnection == null)
return;
if (hasObjectPermissions(user, identifier, ObjectPermission.Type.DELETE)) {
// Close connection if not already closed
GuacamoleTunnel tunnel = activeConnection.getTunnel();
if (tunnel != null && tunnel.isOpen())
tunnel.close();
}
else
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public Set<String> getIdentifiers(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Retrieve all visible connections (permissions enforced by tunnel service)
Collection<ActiveConnectionRecord> records = tunnelService.getActiveConnections(user);
// Build list of identifiers
Set<String> identifiers = new HashSet<String>(records.size());
for (ActiveConnectionRecord record : records)
identifiers.add(record.getUUID().toString());
return identifiers;
}
@Override
public TrackedActiveConnection createObject(ModeledAuthenticatedUser user,
ActiveConnection object) throws GuacamoleException {
// Updating active connections is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void updateObject(ModeledAuthenticatedUser user, TrackedActiveConnection object)
throws GuacamoleException {
// Updating active connections is not implemented
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Retrieve the permission set for the specified user that relates
* to access to active connections.
*
* @param user
* The user for which to retrieve the permission set.
*
* @return
* A permission set associated with the given user that specifies
* the permissions available for active connection objects.
*
* @throws GuacamoleException
* If permission to read permissions for the user is denied.
*/
private ObjectPermissionSet getPermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException {
return user.getUser().getActiveConnectionPermissions();
}
/**
* Return a boolean value representing whether or not a user has the given
* permission available to them on the active connection with the given
* identifier.
*
* @param user
* The user for which the permissions are being queried.
*
* @param identifier
* The identifier of the active connection we are wondering about.
*
* @param type
* The type of permission being requested.
*
* @return
* True if the user has the necessary permission; otherwise false.
*
* @throws GuacamoleException
* If the user does not have access to read permissions.
*/
private boolean hasObjectPermissions(ModeledAuthenticatedUser user,
String identifier, ObjectPermission.Type type)
throws GuacamoleException {
ObjectPermissionSet permissionSet = getPermissionSet(user);
return user.isPrivileged()
|| permissionSet.hasPermission(type, identifier);
}
}

View File

@@ -0,0 +1,284 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;
import com.google.inject.Inject;
import java.util.Date;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.base.RestrictedObject;
import org.apache.guacamole.auth.jdbc.connection.ModeledConnection;
import org.apache.guacamole.auth.jdbc.sharing.ConnectionSharingService;
import org.apache.guacamole.auth.jdbc.sharing.connection.SharedConnectionDefinition;
import org.apache.guacamole.auth.jdbc.tunnel.ActiveConnectionRecord;
import org.apache.guacamole.auth.jdbc.tunnel.GuacamoleTunnelService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.auth.ActiveConnection;
import org.apache.guacamole.net.auth.credentials.UserCredentials;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
/**
* An implementation of the ActiveConnection object which has an associated
* ActiveConnectionRecord.
*/
public class TrackedActiveConnection extends RestrictedObject implements ActiveConnection {
/**
* Service for managing shared connections.
*/
@Inject
private ConnectionSharingService sharingService;
/**
* Service for creating and tracking tunnels.
*/
@Inject
private GuacamoleTunnelService tunnelService;
/**
* The identifier of this active connection.
*/
private String identifier;
/**
* The actual connection record from which this ActiveConnection derives its
* data.
*/
private ActiveConnectionRecord connectionRecord;
/**
* The connection being actively used or shared.
*/
private ModeledConnection connection;
/**
* The identifier of the associated sharing profile.
*/
private String sharingProfileIdentifier;
/**
* The date and time this active connection began.
*/
private Date startDate;
/**
* The remote host that initiated this connection.
*/
private String remoteHost;
/**
* The username of the user that initiated this connection.
*/
private String username;
/**
* The underlying GuacamoleTunnel.
*/
private GuacamoleTunnel tunnel;
/**
* Whether connections to this TrackedActiveConnection are allowed.
*/
private boolean connectable;
/**
* Initializes this TrackedActiveConnection, copying the data associated
* with the given active connection record. At a minimum, the identifier
* of this active connection will be set, the start date, and the
* identifier of the associated connection will be copied. If requested,
* sensitive information like the associated username will be copied, as
* well.
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param activeConnectionRecord
* The active connection record to copy.
*
* @param includeSensitiveInformation
* Whether sensitive data should be copied from the connection record
* as well. This includes the remote host, associated tunnel, and
* username.
*
* @param connectable
* Whether the user that retrieved this object should be allowed to
* join the active connection.
*/
public void init(ModeledAuthenticatedUser currentUser,
ActiveConnectionRecord activeConnectionRecord,
boolean includeSensitiveInformation,
boolean connectable) {
super.init(currentUser);
this.connectionRecord = activeConnectionRecord;
this.connectable = connectable;
// Copy all non-sensitive data from given record
this.connection = activeConnectionRecord.getConnection();
this.sharingProfileIdentifier = activeConnectionRecord.getSharingProfileIdentifier();
this.identifier = activeConnectionRecord.getUUID().toString();
this.startDate = activeConnectionRecord.getStartDate();
// Include sensitive data, too, if requested
if (includeSensitiveInformation) {
this.remoteHost = activeConnectionRecord.getRemoteHost();
this.tunnel = activeConnectionRecord.getTunnel();
this.username = activeConnectionRecord.getUsername();
}
}
@Override
public String getIdentifier() {
return identifier;
}
@Override
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/**
* Returns the connection being actively used. If this active connection is
* not the primary connection, this will be the connection being actively
* shared.
*
* @return
* The connection being actively used.
*/
public ModeledConnection getConnection() {
return connection;
}
@Override
public String getConnectionIdentifier() {
return connection.getIdentifier();
}
@Override
public void setConnectionIdentifier(String connnectionIdentifier) {
throw new UnsupportedOperationException("The connection identifier of "
+ "TrackedActiveConnection is inherited from the underlying "
+ "connection.");
}
@Override
public String getSharingProfileIdentifier() {
return sharingProfileIdentifier;
}
@Override
public void setSharingProfileIdentifier(String sharingProfileIdentifier) {
this.sharingProfileIdentifier = sharingProfileIdentifier;
}
/**
* Shares this active connection with the user that retrieved it, returning
* a SharedConnectionDefinition that can be used to establish a tunnel to
* the shared connection. If provided, access within the shared connection
* will be restricted by the sharing profile with the given identifier.
*
* @param identifier
* The identifier of the sharing profile that defines the restrictions
* applying to the shared connection, or null if no such restrictions
* apply.
*
* @return
* A new SharedConnectionDefinition which can be used to establish a
* tunnel to the shared connection.
*
* @throws GuacamoleException
* If permission to share this active connection is denied.
*/
private SharedConnectionDefinition share(String identifier) throws GuacamoleException {
return sharingService.shareConnection(getCurrentUser(), connectionRecord, identifier);
}
@Override
public UserCredentials getSharingCredentials(String identifier)
throws GuacamoleException {
return sharingService.getSharingCredentials(share(identifier));
}
@Override
public Date getStartDate() {
return startDate;
}
@Override
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
@Override
public String getRemoteHost() {
return remoteHost;
}
@Override
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
@Override
public GuacamoleTunnel getTunnel() {
return tunnel;
}
@Override
public void setTunnel(GuacamoleTunnel tunnel) {
this.tunnel = tunnel;
}
@Override
public boolean isConnectable() {
return connectable;
}
@Override
public GuacamoleTunnel connect(GuacamoleClientInformation info,
Map<String, String> tokens) throws GuacamoleException {
// Establish connection only if connecting is allowed
if (isConnectable())
return tunnelService.getGuacamoleTunnel(getCurrentUser(), share(null), info, tokens);
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public int getActiveConnections() {
return 0;
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* Classes related to currently-active connections.
*/
package org.apache.guacamole.auth.jdbc.activeconnection;

View File

@@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.List;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Common interface for mapping activity records.
*
* @param <ModelType>
* The type of model object representing the activity records mapped by
* this mapper.
*/
public interface ActivityRecordMapper<ModelType> {
/**
* Inserts the given activity record.
*
* @param record
* The activity record to insert.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("record") ModelType record,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Updates the given activity record in the database, assigning an end
* date. No column of the existing activity record is updated except for
* the end date. If the record does not actually exist, this operation has
* no effect.
*
* @param record
* The activity record to update.
*
* @return
* The number of rows updated.
*/
int updateEndDate(@Param("record") ModelType record);
/**
* Searches for up to <code>limit</code> activity records that contain
* the given terms, sorted by the given predicates, regardless of whether
* the data they are associated with is readable by any particular user.
* This should only be called on behalf of a system administrator. If
* records are needed by a non-administrative user who must have explicit
* read rights, use {@link searchReadable()} instead.
*
* @param identifier
* The optional identifier of the object whose history is being
* retrieved, or null if records related to any such object should be
* retrieved.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param terms
* The search terms that must match the returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The results of the search performed with the given parameters.
*/
List<ModelType> search(@Param("identifier") String identifier,
@Param("recordIdentifier") String recordIdentifier,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Searches for up to <code>limit</code> activity records that contain
* the given terms, sorted by the given predicates. Only records that are
* associated with data explicitly readable by the given user will be
* returned. If records are needed by a system administrator (who, by
* definition, does not need explicit read rights), use {@link search()}
* instead.
*
* @param identifier
* The optional identifier of the object whose history is being
* retrieved, or null if records related to any such object should be
* retrieved.
*
* @param user
* The user whose permissions should determine whether a record is
* returned.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param terms
* The search terms that must match the returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The results of the search performed with the given parameters.
*/
List<ModelType> searchReadable(@Param("identifier") String identifier,
@Param("user") UserModel user,
@Param("recordIdentifier") String recordIdentifier,
@Param("terms") Collection<ActivityRecordSearchTerm> terms,
@Param("sortPredicates") List<ActivityRecordSortPredicate> sortPredicates,
@Param("limit") int limit,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,193 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Date;
/**
* A single activity record representing an arbitrary activity performed by a
* user.
*/
public class ActivityRecordModel {
/**
* The ID of this object in the database, if any.
*/
private Integer recordID;
/**
* The database ID of the user associated with this activity record.
*/
private Integer userID;
/**
* The username of the user that performed the activity.
*/
private String username;
/**
* The remote host associated with the user that performed the activity.
*/
private String remoteHost;
/**
* The time the activity was initiated by the associated user.
*/
private Date startDate;
/**
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
private Date endDate;
/**
* Returns the ID of this record in the database, if it exists.
*
* @return
* The ID of this record in the database, or null if this record was
* not retrieved from the database.
*/
public Integer getRecordID() {
return recordID;
}
/**
* Sets the database ID of this record to the given value.
*
* @param recordID
* The ID to assign to this object.
*/
public void setRecordID(Integer recordID) {
this.recordID = recordID;
}
/**
* Returns the database ID of the user associated with this activity
* record.
*
* @return
* The database ID of the user associated with this activity record.
*/
public Integer getUserID() {
return userID;
}
/**
* Sets the database ID of the user associated with this activity record.
*
* @param userID
* The database ID of the user to associate with this activity
* record.
*/
public void setUserID(Integer userID) {
this.userID = userID;
}
/**
* Returns the username of the user that performed the activity associated
* with this record.
*
* @return
* The username of the user that performed the activity associated with
* this record.
*/
public String getUsername() {
return username;
}
/**
* Sets the username of the user that performed the activity associated
* with this record.
*
* @param username
* The username of the user that performed the activity associated with
* this record.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the remote host associated with the user that performed the
* activity.
*
* @return
* The remote host associated with the user that performed the activity.
*/
public String getRemoteHost() {
return remoteHost;
}
/**
* Sets the remote host associated with the user that performed the
* activity.
*
* @param remoteHost
* The remote host associated with the user that performed the activity.
*/
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
/**
* Returns the time the activity was initiated by the associated user.
*
* @return
* The time the activity was initiated by the associated user.
*/
public Date getStartDate() {
return startDate;
}
/**
* Sets the time the activity was initiated by the associated user.
*
* @param startDate
* The time the activity was initiated by the associated user.
*/
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
/**
* Returns the time the activity ended, or null if the end time is not
* known or the activity is still in progress.
*
* @return
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public Date getEndDate() {
return endDate;
}
/**
* Sets the time the activity ended, if known.
*
* @param endDate
* The time the activity ended, or null if the end time is not known or
* the activity is still in progress.
*/
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
}

View File

@@ -0,0 +1,291 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A search term for querying historical records of arbitrary activities. This
* will contain a the search term in string form and, if that string appears to
* be a date. a corresponding date range.
*/
public class ActivityRecordSearchTerm {
/**
* A pattern that can match a year, year and month, or year and month and
* day.
*/
private static final Pattern DATE_PATTERN =
Pattern.compile("(\\d+)(?:-(\\d+)?(?:-(\\d+)?)?)?");
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* year number.
*/
private static final int YEAR_GROUP = 1;
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* month number, if any.
*/
private static final int MONTH_GROUP = 2;
/**
* The index of the group within <code>DATE_PATTERN</code> containing the
* day number, if any.
*/
private static final int DAY_GROUP = 3;
/**
* The start of the date range for records that should be retrieved, if the
* provided search term appears to be a date.
*/
private final Date startDate;
/**
* The end of the date range for records that should be retrieved, if the
* provided search term appears to be a date.
*/
private final Date endDate;
/**
* The string that should be searched for.
*/
private final String term;
/**
* Parse the given string as an integer, returning the provided default
* value if the string is null.
*
* @param str
* The string to parse as an integer.
*
* @param defaultValue
* The value to return if <code>str</code> is null.
*
* @return
* The parsed value, or the provided default value if <code>str</code>
* is null.
*/
private static int parseInt(String str, int defaultValue) {
if (str == null)
return defaultValue;
return Integer.parseInt(str);
}
/**
* Returns a new calendar representing the last millisecond of the same
* year as <code>calendar</code>.
*
* @param calendar
* The calendar defining the year whose end (last millisecond) is to be
* returned.
*
* @return
* A new calendar representing the last millisecond of the same year as
* <code>calendar</code>.
*/
private static Calendar getEndOfYear(Calendar calendar) {
// Get first day of next year
Calendar endOfYear = Calendar.getInstance();
endOfYear.clear();
endOfYear.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1);
// Transform into the last millisecond of the given year
endOfYear.add(Calendar.MILLISECOND, -1);
return endOfYear;
}
/**
* Returns a new calendar representing the last millisecond of the same
* month and year as <code>calendar</code>.
*
* @param calendar
* The calendar defining the month and year whose end (last millisecond)
* is to be returned.
*
* @return
* A new calendar representing the last millisecond of the same month
* and year as <code>calendar</code>.
*/
private static Calendar getEndOfMonth(Calendar calendar) {
// Copy given calender only up to given month
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.clear();
endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
// Advance to the last millisecond of the given month
endOfMonth.add(Calendar.MONTH, 1);
endOfMonth.add(Calendar.MILLISECOND, -1);
return endOfMonth;
}
/**
* Returns a new calendar representing the last millisecond of the same
* year, month, and day as <code>calendar</code>.
*
* @param calendar
* The calendar defining the year, month, and day whose end
* (last millisecond) is to be returned.
*
* @return
* A new calendar representing the last millisecond of the same year,
* month, and day as <code>calendar</code>.
*/
private static Calendar getEndOfDay(Calendar calendar) {
// Copy given calender only up to given month
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.clear();
endOfMonth.set(Calendar.YEAR, calendar.get(Calendar.YEAR));
endOfMonth.set(Calendar.MONTH, calendar.get(Calendar.MONTH));
endOfMonth.set(Calendar.DAY_OF_MONTH, calendar.get(Calendar.DAY_OF_MONTH));
// Advance to the last millisecond of the given day
endOfMonth.add(Calendar.DAY_OF_MONTH, 1);
endOfMonth.add(Calendar.MILLISECOND, -1);
return endOfMonth;
}
/**
* Creates a new ActivityRecordSearchTerm representing the given string.
* If the given string appears to be a date, the start and end dates of the
* implied date range will be automatically determined and made available
* via getStartDate() and getEndDate() respectively.
*
* @param term
* The string that should be searched for.
*/
public ActivityRecordSearchTerm(String term) {
// Search terms absolutely must not be null
if (term == null)
throw new NullPointerException("Search terms may not be null");
this.term = term;
// Parse start/end of date range if term appears to be a date
Matcher matcher = DATE_PATTERN.matcher(term);
if (matcher.matches()) {
// Retrieve date components from term
String year = matcher.group(YEAR_GROUP);
String month = matcher.group(MONTH_GROUP);
String day = matcher.group(DAY_GROUP);
// Parse start date from term
Calendar startCalendar = Calendar.getInstance();
startCalendar.clear();
startCalendar.set(
Integer.parseInt(year),
parseInt(month, 1) - 1,
parseInt(day, 1)
);
Calendar endCalendar;
// Derive end date from start date
if (month == null) {
endCalendar = getEndOfYear(startCalendar);
}
else if (day == null) {
endCalendar = getEndOfMonth(startCalendar);
}
else {
endCalendar = getEndOfDay(startCalendar);
}
// Convert results back into dates
this.startDate = startCalendar.getTime();
this.endDate = endCalendar.getTime();
}
// The search term doesn't look like a date
else {
this.startDate = null;
this.endDate = null;
}
}
/**
* Returns the start of the date range for records that should be retrieved,
* if the provided search term appears to be a date.
*
* @return
* The start of the date range.
*/
public Date getStartDate() {
return startDate;
}
/**
* Returns the end of the date range for records that should be retrieved,
* if the provided search term appears to be a date.
*
* @return
* The end of the date range.
*/
public Date getEndDate() {
return endDate;
}
/**
* Returns the string that should be searched for.
*
* @return
* The search term.
*/
public String getTerm() {
return term;
}
@Override
public int hashCode() {
return term.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ActivityRecordSearchTerm))
return false;
return ((ActivityRecordSearchTerm) obj).getTerm().equals(getTerm());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.net.auth.ActivityRecordSet;
/**
* A sort predicate which species the property to use when sorting activity
* records, along with the sort order.
*/
public class ActivityRecordSortPredicate {
/**
* The property to use when sorting ActivityRecords.
*/
private final ActivityRecordSet.SortableProperty property;
/**
* Whether the sort order is descending (true) or ascending (false).
*/
private final boolean descending;
/**
* Creates a new ActivityRecordSortPredicate with the given sort property
* and sort order.
*
* @param property
* The property to use when sorting ActivityRecords.
*
* @param descending
* Whether the sort order is descending (true) or ascending (false).
*/
public ActivityRecordSortPredicate(ActivityRecordSet.SortableProperty property,
boolean descending) {
this.property = property;
this.descending = descending;
}
/**
* Returns the property that should be used when sorting ActivityRecords.
*
* @return
* The property that should be used when sorting ActivityRecords.
*/
public ActivityRecordSet.SortableProperty getProperty() {
return property;
}
/**
* Returns whether the sort order is descending.
*
* @return
* true if the sort order is descending, false if the sort order is
* ascending.
*/
public boolean isDescending() {
return descending;
}
}

View File

@@ -0,0 +1,180 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Map of arbitrary attribute name/value pairs which can alternatively be
* exposed as a collection of model objects.
*/
public class ArbitraryAttributeMap extends HashMap<String, String> {
/**
* Creates a new ArbitraryAttributeMap containing the name/value pairs
* within the given collection of model objects.
*
* @param models
* The model objects of all attributes which should be stored in the
* new map as name/value pairs.
*
* @return
* A new ArbitraryAttributeMap containing the name/value pairs within
* the given collection of model objects.
*/
public static ArbitraryAttributeMap fromModelCollection(Collection<ArbitraryAttributeModel> models) {
// Add all name/value pairs from the given collection to the map
ArbitraryAttributeMap map = new ArbitraryAttributeMap();
for (ArbitraryAttributeModel model : models)
map.put(model.getName(), model.getValue());
return map;
}
/**
* Returns a collection of model objects which mirrors the contents of this
* ArbitraryAttributeMap. Each name/value pair within the map is reflected
* by a corresponding model object within the returned collection. Removing
* a model object from the collection removes the corresponding name/value
* pair from the map. Adding a new model object to the collection adds a
* corresponding name/value pair to the map. Changes to a model object
* within the collection are NOT reflected on the map, however.
*
* @return
* A collection of model objects which mirrors the contents of this
* ArbitraryAttributeMap.
*/
public Collection<ArbitraryAttributeModel> toModelCollection() {
return new AbstractCollection<ArbitraryAttributeModel>() {
@Override
public void clear() {
ArbitraryAttributeMap.this.clear();
}
@Override
public boolean remove(Object o) {
// The Collection view of an ArbitraryAttributeMap can contain
// only ArbitraryAttributeModel objects
if (!(o instanceof ArbitraryAttributeModel))
return false;
// Remove only if key is actually present
ArbitraryAttributeModel model = (ArbitraryAttributeModel) o;
if (!ArbitraryAttributeMap.this.containsKey(model.getName()))
return false;
// The attribute should be removed only if the value matches
String currentValue = ArbitraryAttributeMap.this.get(model.getName());
if (currentValue == null) {
if (model.getValue() != null)
return false;
}
else if (!currentValue.equals(model.getValue()))
return false;
ArbitraryAttributeMap.this.remove(model.getName());
return true;
}
@Override
public boolean add(ArbitraryAttributeModel e) {
String newValue = e.getValue();
String oldValue = put(e.getName(), newValue);
// If null value is being added, collection changed only if
// old value was non-null
if (newValue == null)
return oldValue != null;
// Collection changed if value changed
return !newValue.equals(oldValue);
}
@Override
public boolean contains(Object o) {
// The Collection view of an ArbitraryAttributeMap can contain
// only ArbitraryAttributeModel objects
if (!(o instanceof ArbitraryAttributeModel))
return false;
// No need to check the value of the attribute if the attribute
// is not even present
ArbitraryAttributeModel model = (ArbitraryAttributeModel) o;
String value = get(model.getName());
if (value == null)
return false;
// The name/value pair is present only if the value matches
return value.equals(model.getValue());
}
@Override
public Iterator<ArbitraryAttributeModel> iterator() {
// Get iterator over all string name/value entries
final Iterator<Map.Entry<String, String>> iterator = entrySet().iterator();
// Dynamically translate each string name/value entry into a
// corresponding attribute model object as iteration continues
return new Iterator<ArbitraryAttributeModel>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public ArbitraryAttributeModel next() {
Map.Entry<String, String> entry = iterator.next();
return new ArbitraryAttributeModel(entry.getKey(),
entry.getValue());
}
@Override
public void remove() {
iterator.remove();
}
};
}
@Override
public int size() {
return ArbitraryAttributeMap.this.size();
}
};
}
}

View File

@@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
/**
* A single attribute name/value pair belonging to a object which implements
* the Attributes interface, such as a Connection or User. Attributes stored
* as raw name/value pairs are the attributes which are given to the database
* authentication extension for storage by other extensions. Attributes which
* are directly supported by the database authentication extension have defined
* columns and properties with proper types, constraints, etc.
*/
public class ArbitraryAttributeModel {
/**
* The name of the attribute.
*/
private String name;
/**
* The value the attribute is set to.
*/
private String value;
/**
* Creates a new ArbitraryAttributeModel with its name and value both set
* to null.
*/
public ArbitraryAttributeModel() {
}
/**
* Creates a new ArbitraryAttributeModel with its name and value
* initialized to the given values.
*
* @param name
* The name of the attribute.
*
* @param value
* The value the attribute is set to.
*/
public ArbitraryAttributeModel(String name, String value) {
this.name = name;
this.value = value;
}
/**
* Returns the name of this attribute.
*
* @return
* The name of this attribute.
*/
public String getName() {
return name;
}
/**
* Sets the name of this attribute.
*
* @param name
* The name of this attribute.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the value of this attribute.
*
* @return
* The value of this attribute.
*/
public String getValue() {
return value;
}
/**
* Sets the value of this attribute.
*
* @param value
* The value of this attribute.
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
/**
* Object representation of a Guacamole object which can be the child of another
* object, such as a connection or sharing profile, as represented in the
* database.
*/
public abstract class ChildObjectModel extends ObjectModel {
/**
* The unique identifier which identifies the parent of this object.
*/
private String parentIdentifier;
/**
* Creates a new, empty object.
*/
public ChildObjectModel() {
}
/**
* Returns the identifier of the parent connection group, or null if the
* parent connection group is the root connection group.
*
* @return
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public String getParentIdentifier() {
return parentIdentifier;
}
/**
* Sets the identifier of the parent connection group.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the parent
* connection group is the root connection group.
*/
public void setParentIdentifier(String parentIdentifier) {
this.parentIdentifier = parentIdentifier;
}
}

View File

@@ -0,0 +1,151 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects that have unique identifiers, such as the objects
* within directories. This service will automatically enforce the permissions
* of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*/
public interface DirectoryObjectService<InternalType, ExternalType> {
/**
* Retrieves the single object that has the given identifier, if it exists
* and the user has permission to read it.
*
* @param user
* The user retrieving the object.
*
* @param identifier
* The identifier of the object to retrieve.
*
* @return
* The object having the given identifier, or null if no such object
* exists.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested object.
*/
InternalType retrieveObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException;
/**
* Retrieves all objects that have the identifiers in the given collection.
* Only objects that the user has permission to read will be returned.
*
* @param user
* The user retrieving the objects.
*
* @param identifiers
* The identifiers of the objects to retrieve.
*
* @return
* The objects having the given identifiers.
*
* @throws GuacamoleException
* If an error occurs while retrieving the requested objects.
*/
Collection<InternalType> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException;
/**
* Creates the given object. If the object already exists, an error will be
* thrown.
*
* @param user
* The user creating the object.
*
* @param object
* The object to create.
*
* @return
* The newly-created object.
*
* @throws GuacamoleException
* If the user lacks permission to create the object, or an error
* occurs while creating the object.
*/
InternalType createObject(ModeledAuthenticatedUser user, ExternalType object)
throws GuacamoleException;
/**
* Deletes the object having the given identifier. If no such object
* exists, this function has no effect.
*
* @param user
* The user deleting the object.
*
* @param identifier
* The identifier of the object to delete.
*
* @throws GuacamoleException
* If the user lacks permission to delete the object, or an error
* occurs while deleting the object.
*/
void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException;
/**
* Updates the given object, applying any changes that have been made. If
* no such object exists, this function has no effect.
*
* @param user
* The user updating the object.
*
* @param object
* The object to update.
*
* @throws GuacamoleException
* If the user lacks permission to update the object, or an error
* occurs while updating the object.
*/
void updateObject(ModeledAuthenticatedUser user, InternalType object)
throws GuacamoleException;
/**
* Returns the set of all identifiers for all objects that the user has
* read access to.
*
* @param user
* The user retrieving the identifiers.
*
* @return
* The set of all identifiers for all objects.
*
* @throws GuacamoleException
* If an error occurs while reading identifiers.
*/
Set<String> getIdentifiers(ModeledAuthenticatedUser user) throws GuacamoleException;
}

View File

@@ -0,0 +1,86 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for entities. An entity is the base concept behind a user or user
* group, and serves as a common point for granting permissions and defining
* group membership.
*/
public interface EntityMapper {
/**
* Inserts the given entity into the database. If the entity already
* exists, this will result in an error.
*
* @param entity
* The entity to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("entity") EntityModel entity);
/**
* Returns the set of all group identifiers of which the given entity is a
* member, taking into account the given collection of known group
* memberships which are not necessarily defined within the database.
*
* NOTE: This query is expected to handle recursion through the membership
* graph on its own. If the database engine does not support recursive
* queries (isRecursiveQuerySupported() of JDBCEnvironment returns false),
* then this query will only return one level of depth past the effective
* groups given and will need to be invoked multiple times.
*
* @param entity
* The entity whose effective groups should be returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param recursive
* Whether the query should leverage database engine features to return
* absolutely all effective groups, including those inherited through
* group membership. If false, this query will return only one level of
* depth and may need to be executed multiple times. If it is known
* that the database engine in question will always support (or always
* not support) recursive queries, this parameter may be ignored.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* The set of identifiers of all groups that the given entity is a
* member of, including those where membership is inherited through
* membership in other groups.
*/
Set<String> selectEffectiveGroupIdentifiers(@Param("entity") EntityModel entity,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("recursive") boolean recursive,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
}

View File

@@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
/**
* Base representation of a Guacamole object that can be granted permissions
* (an "entity"), such as a user or user group, as represented in the database.
* Each entity has three base properties:
*
* 1. The "entityID", which points to the common entry in the
* guacamole_entity table and is common to any type of entity.
*
* 2. The "objectID", which points to the type-specific entry for the object
* in question (ie: an entry in guacamole_user or guacamole_user_group).
*
* 3. The "identifier", which contains the unique "name" value defined for
* the entity within the guacamole_entity table.
*/
public abstract class EntityModel extends ObjectModel {
/**
* The ID of the entity entry which corresponds to this object in the
* database, if any. Note that this is distinct from the objectID,
* inherited from ObjectModel, which is specific to the actual type of
* object represented by the entity.
*/
private Integer entityID;
/**
* The type of object represented by the entity (user or user group).
*/
private EntityType type;
/**
* Creates a new, empty entity.
*/
public EntityModel() {
}
/**
* Creates a new entity of the given type which is otherwise empty.
*
* @param type
* The type to assign to the new entity.
*/
public EntityModel(EntityType type) {
this.type = type;
}
/**
* Returns the ID of the entity entry which corresponds to this object in
* the database, if it exists. Note that this is distinct from the objectID,
* inherited from ObjectModel, which is specific to the actual type of
* object represented by the entity.
*
* @return
* The ID of this entity in the database, or null if this entity was
* not retrieved from the database.
*/
public Integer getEntityID() {
return entityID;
}
/**
* Sets the ID of this entity to the given value.
*
* @param entityID
* The ID to assign to this entity.
*/
public void setEntityID(Integer entityID) {
this.entityID = entityID;
}
/**
* Returns the type of object represented by the entity. Each entity may be
* either a user or a user group.
*
* @return
* The type of object represented by the entity.
*/
public EntityType getEntityType() {
return type;
}
/**
* Sets the type of object represented by the entity. Each entity may be
* either a user or a user group.
*
* @param type
* The type of object represented by the entity.
*/
public void setEntityType(EntityType type) {
this.type = type;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.guice.transactional.Transactional;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating entities.
*/
public class EntityService {
/**
* The Guacamole server environment.
*/
@Inject
private JDBCEnvironment environment;
/**
* Mapper for Entity model objects.
*/
@Inject
private EntityMapper entityMapper;
/**
* The current SQL session used by MyBatis.
*/
@Inject
private SqlSession sqlSession;
/**
* Returns the set of all group identifiers of which the given entity is a
* member, taking into account the given collection of known group
* memberships which are not necessarily defined within the database.
*
* Note that group visibility with respect to the queried entity is NOT
* taken into account. If the entity is a member of a group, the identifier
* of that group will be included in the returned set even if the current
* user lacks "READ" permission for that group.
*
* @param entity
* The entity whose effective groups should be returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @return
* The set of identifiers of all groups that the given entity is a
* member of, including those where membership is inherited through
* membership in other groups.
*/
@Transactional
public Set<String> retrieveEffectiveGroups(ModeledPermissions<? extends EntityModel> entity,
Collection<String> effectiveGroups) {
CaseSensitivity caseSensitivity = environment.getCaseSensitivity();
// Retrieve the effective user groups of the given entity, recursively if possible
boolean recursive = environment.isRecursiveQuerySupported(sqlSession);
Set<String> identifiers = entityMapper.selectEffectiveGroupIdentifiers(
entity.getModel(), effectiveGroups, recursive, caseSensitivity);
// If the set of user groups retrieved was not produced recursively,
// manually repeat the query to expand the set until all effective
// groups have been found
if (!recursive && !identifiers.isEmpty()) {
Set<String> previousIdentifiers;
do {
previousIdentifiers = identifiers;
identifiers = entityMapper.selectEffectiveGroupIdentifiers(
entity.getModel(), previousIdentifiers, false,
caseSensitivity);
} while (identifiers.size() > previousIdentifiers.size());
}
return identifiers;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
/**
* The type of object represented by an entity. Each entity may represent
* either a user or a user group.
*/
public enum EntityType {
/**
* An individual user.
*/
USER,
/**
* A group of users and/or other groups.
*/
USER_GROUP
}

View File

@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AtomicDirectoryOperation;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.Identifiable;
import org.mybatis.guice.transactional.Transactional;
/**
* An implementation of Directory that uses database transactions to guarantee
* atomicity for any operations supplied to tryAtomically().
*/
public abstract class JDBCDirectory<ObjectType extends Identifiable>
extends RestrictedObject implements Directory<ObjectType> {
@Override
@Transactional
public void tryAtomically(AtomicDirectoryOperation<ObjectType> operation)
throws GuacamoleException {
// Execute the operation atomically - the @Transactional annotation
// specifies that the entire operation will be performed in a transaction
operation.executeOperation(true, this);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.UUID;
import org.apache.guacamole.net.auth.ActivityRecord;
/**
* An ActivityRecord which is backed by a database model.
*/
public class ModeledActivityRecord implements ActivityRecord {
/**
* The model object backing this activity record.
*/
private final ActivityRecordModel model;
/**
* The UUID namespace of the type 3 name UUID to generate for the record.
* This namespace should correspond to the source of IDs for the model such
* that the combination of this namespace with the numeric record ID will
* always be unique and deterministic across all activity records,
* regardless of record type.
*/
private final UUID namespace;
/**
* Creates a new ModeledActivityRecord backed by the given model object.
* Changes to this record will affect the backing model object, and changes
* to the backing model object will affect this record.
*
* @param namespace
* The UUID namespace of the type 3 name UUID to generate for the
* record. This namespace should correspond to the source of IDs for
* the model such that the combination of this namespace with the
* numeric record ID will always be unique and deterministic across all
* activity records, regardless of record type.
*
* @param model
* The model object to use to back this activity record.
*/
public ModeledActivityRecord(UUID namespace, ActivityRecordModel model) {
this.model = model;
this.namespace = namespace;
}
/**
* Returns the backing model object. Changes to this record will affect the
* backing model object, and changes to the backing model object will
* affect this record.
*
* @return
* The backing model object.
*/
public ActivityRecordModel getModel() {
return model;
}
@Override
public Date getStartDate() {
return model.getStartDate();
}
@Override
public Date getEndDate() {
return model.getEndDate();
}
@Override
public String getRemoteHost() {
return model.getRemoteHost();
}
@Override
public String getUsername() {
return model.getUsername();
}
@Override
public boolean isActive() {
return false;
}
@Override
public String getIdentifier() {
Integer id = model.getRecordID();
if (id == null)
return null;
return id.toString();
}
@Override
public UUID getUUID() {
Integer id = model.getRecordID();
if (id == null)
return null;
// Convert record ID to a name UUID in the given namespace
return UUID.nameUUIDFromBytes(ByteBuffer.allocate(24)
.putLong(namespace.getMostSignificantBits())
.putLong(namespace.getLeastSignificantBits())
.putLong(id)
.array());
}
}

View File

@@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.ActivityRecord;
import org.apache.guacamole.net.auth.ActivityRecordSet;
import org.apache.guacamole.net.auth.ActivityRecordSet.SortableProperty;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A JDBC implementation of ActivityRecordSet. Calls to asCollection() will
* query history records using an implementation-specific mechanism. Which
* records are returned will be determined by the values passed in earlier.
*
* @param <RecordType>
* The type of ActivityRecord contained within this set.
*/
public abstract class ModeledActivityRecordSet<RecordType extends ActivityRecord>
extends RestrictedObject implements ActivityRecordSet<RecordType> {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(ModeledActivityRecordSet.class);
/**
* The set of strings that each must occur somewhere within the returned
* records, whether within the associated username, an associated date, or
* other related data. If non-empty, any record not matching each of the
* strings within the collection will be excluded from the results.
*/
private final Set<ActivityRecordSearchTerm> requiredContents =
new HashSet<>();
/**
* The maximum number of history records that should be returned by a call
* to asCollection().
*/
private int limit = Integer.MAX_VALUE;
/**
* A list of predicates to apply while sorting the resulting records,
* describing the properties involved and the sort order for those
* properties.
*/
private final List<ActivityRecordSortPredicate> sortPredicates =
new ArrayList<>();
/**
* Retrieves the history records matching the given criteria. Retrieves up
* to <code>limit</code> history records matching the given terms and sorted
* by the given predicates. Only history records associated with data that
* the given user can read are returned.
*
* @param user
* The user retrieving the history.
*
* @param recordIdentifier
* The identifier of the specific history record to retrieve, if not
* all matching records. Search terms, etc. will still be applied to
* the single record.
*
* @param requiredContents
* The search terms that must be contained somewhere within each of the
* returned records.
*
* @param sortPredicates
* A list of predicates to sort the returned records by, in order of
* priority.
*
* @param limit
* The maximum number of records that should be returned.
*
* @return
* A collection of all history records matching the given criteria.
*
* @throws GuacamoleException
* If permission to read the history records is denied.
*/
protected abstract List<RecordType> retrieveHistory(
AuthenticatedUser user, String recordIdentifier,
Set<ActivityRecordSearchTerm> requiredContents,
List<ActivityRecordSortPredicate> sortPredicates,
int limit) throws GuacamoleException;
@Override
public RecordType get(String identifier) throws GuacamoleException {
List<RecordType> records = retrieveHistory(getCurrentUser(),
identifier, requiredContents, sortPredicates, limit);
if (records.isEmpty())
return null;
if (records.size() == 1)
return records.get(0);
logger.warn("Multiple history records match ID \"{}\"! This should "
+ "not be possible and may indicate a bug or database "
+ "corruption.", identifier);
return null;
}
@Override
public Collection<RecordType> asCollection()
throws GuacamoleException {
return retrieveHistory(getCurrentUser(), null, requiredContents,
sortPredicates, limit);
}
@Override
public ModeledActivityRecordSet<RecordType> contains(String value)
throws GuacamoleException {
requiredContents.add(new ActivityRecordSearchTerm(value));
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> limit(int limit) throws GuacamoleException {
this.limit = Math.min(this.limit, limit);
return this;
}
@Override
public ModeledActivityRecordSet<RecordType> sort(SortableProperty property, boolean desc)
throws GuacamoleException {
sortPredicates.add(new ActivityRecordSortPredicate(
property,
desc
));
return this;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.auth.jdbc.connectiongroup.RootConnectionGroup;
/**
* Common base class for objects that will ultimately be made available through
* the Directory class. All such objects will need the same base set of queries
* to fulfill the needs of the Directory class.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledChildDirectoryObject<ModelType extends ChildObjectModel>
extends ModeledDirectoryObject<ModelType> {
/**
* Returns the identifier of the parent connection group, which cannot be
* null. If the parent is the root connection group, this will be
* RootConnectionGroup.IDENTIFIER.
*
* @return
* The identifier of the parent connection group.
*/
public String getParentIdentifier() {
// Translate null parent to proper identifier
String parentIdentifier = getModel().getParentIdentifier();
if (parentIdentifier == null)
return RootConnectionGroup.IDENTIFIER;
return parentIdentifier;
}
/**
* Sets the identifier of the associated parent connection group. If the
* parent is the root connection group, this should be
* RootConnectionGroup.IDENTIFIER.
*
* @param parentIdentifier
* The identifier of the connection group to associate as this object's
* parent.
*/
public void setParentIdentifier(String parentIdentifier) {
// Translate root identifier back into null
if (parentIdentifier != null
&& parentIdentifier.equals(RootConnectionGroup.IDENTIFIER))
parentIdentifier = null;
getModel().setParentIdentifier(parentIdentifier);
}
}

View File

@@ -0,0 +1,209 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.Identifiable;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects that can be children of other objects. This service will
* automatically enforce the permissions of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*
* @param <ModelType>
* The underlying model object used to represent InternalType in the
* database.
*/
public abstract class ModeledChildDirectoryObjectService<InternalType extends ModeledChildDirectoryObject<ModelType>,
ExternalType extends Identifiable, ModelType extends ChildObjectModel>
extends ModeledDirectoryObjectService<InternalType, ExternalType, ModelType> {
/**
* Returns the permission set associated with the given user and related
* to the type of objects which can be parents of the child objects handled
* by this directory object service, taking into account permission
* inheritance via user groups.
*
* @param user
* The user whose permissions are being retrieved.
*
* @return
* A permission set which contains the permissions associated with the
* given user and related to the type of objects which can be parents
* of the child objects handled by this directory object service.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract ObjectPermissionSet getParentEffectivePermissionSet(
ModeledAuthenticatedUser user) throws GuacamoleException;
/**
* Returns the set of parent objects that are modified by the given model
* object (by virtue of the object changing parents). If the model is not
* changing parents, the resulting collection will be empty.
*
* @param user
* The user making the given changes to the model.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* A collection of the identifiers of all parents that will be affected
* (updated) by the change.
*
* @throws GuacamoleException
* If an error occurs while determining which parents are affected.
*/
protected Collection<String> getModifiedParents(ModeledAuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// Get old parent identifier
String oldParentIdentifier = null;
if (identifier != null) {
ModelType current = retrieveObject(user, identifier).getModel();
oldParentIdentifier = current.getParentIdentifier();
}
// Get new parent identifier
String parentIdentifier = null;
if (model != null) {
parentIdentifier = model.getParentIdentifier();
// If both parents have the same identifier, nothing has changed
if (parentIdentifier != null && parentIdentifier.equals(oldParentIdentifier))
return Collections.<String>emptyList();
}
// Return collection of all non-root parents involved
Collection<String> parents = new ArrayList<String>(2);
if (oldParentIdentifier != null) parents.add(oldParentIdentifier);
if (parentIdentifier != null) parents.add(parentIdentifier);
return parents;
}
/**
* Returns whether the given user has permission to modify the parents
* affected by the modifications made to the given model object.
*
* @param user
* The user who changed the model object.
*
* @param identifier
* The identifier of the object that has been modified, if it exists.
* If the object is being created, this will be null.
*
* @param model
* The model that has been modified, if any. If the object is being
* deleted, this will be null.
*
* @return
* true if the user has update permission for all modified parents,
* false otherwise.
*
* @throws GuacamoleException
* If an error occurs while determining which parents are affected.
*/
protected boolean canUpdateModifiedParents(ModeledAuthenticatedUser user,
String identifier, ModelType model) throws GuacamoleException {
// If user is privileged, no need to check
if (user.isPrivileged())
return true;
// Verify that we have permission to modify any modified parents
Collection<String> modifiedParents = getModifiedParents(user, identifier, model);
if (!modifiedParents.isEmpty()) {
ObjectPermissionSet permissionSet = getParentEffectivePermissionSet(user);
Collection<String> updateableParents = permissionSet.getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.UPDATE),
modifiedParents
);
return updateableParents.size() == modifiedParents.size();
}
return true;
}
@Override
protected void beforeCreate(ModeledAuthenticatedUser user,
ExternalType object, ModelType model) throws GuacamoleException {
super.beforeCreate(user, object, model);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, null, model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeUpdate(ModeledAuthenticatedUser user,
InternalType object, ModelType model) throws GuacamoleException {
super.beforeUpdate(user, object, model);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, model.getIdentifier(), model))
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
protected void beforeDelete(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
super.beforeDelete(user, identifier);
// Validate that we can update all applicable parents
if (!canUpdateModifiedParents(user, identifier, null))
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.guacamole.net.auth.Attributes;
import org.apache.guacamole.net.auth.Identifiable;
/**
* Common base class for objects that will ultimately be made available through
* the Directory class and are persisted to an underlying database model. All
* such objects will need the same base set of queries to fulfill the needs of
* the Directory class.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledDirectoryObject<ModelType extends ObjectModel>
extends ModeledObject<ModelType> implements Identifiable, Attributes {
@Override
public String getIdentifier() {
return getModel().getIdentifier();
}
@Override
public void setIdentifier(String identifier) {
getModel().setIdentifier(identifier);
}
/**
* Returns the names of all attributes explicitly supported by this object.
* Attributes named here have associated mappings within the backing model
* object, and thus should not be included in the arbitrary attribute
* storage. Any attributes set which do not match these names, such as those
* set via other extensions, will be added to arbitrary attribute storage.
*
* @return
* A read-only Set of the names of all attributes explicitly supported
* (mapped to a property of the backing model) by this object.
*/
public Set<String> getSupportedAttributeNames() {
return Collections.<String>emptySet();
}
@Override
public Map<String, String> getAttributes() {
return new HashMap<String, String>(getModel().getArbitraryAttributeMap());
}
@Override
public void setAttributes(Map<String, String> attributes) {
ArbitraryAttributeMap arbitraryAttributes = getModel().getArbitraryAttributeMap();
// Get set of all supported attribute names
Set<String> supportedAttributes = getSupportedAttributeNames();
// Store remaining attributes only if not directly mapped to model
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
String name = attribute.getKey();
String value = attribute.getValue();
// Handle null attributes as explicit removal of that attribute,
// as the underlying model cannot store null attribute values
if (!supportedAttributes.contains(name)) {
if (value == null)
arbitraryAttributes.remove(name);
else
arbitraryAttributes.put(name, value);
}
}
}
}

View File

@@ -0,0 +1,190 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Common interface for objects that will ultimately be made available through
* the Directory class. All such objects will need the same base set of queries
* to fulfill the needs of the Directory class.
*
* @param <ModelType>
* The type of object contained within the directory whose objects are
* mapped by this mapper.
*/
public interface ModeledDirectoryObjectMapper<ModelType> {
/**
* Selects the identifiers of all objects, regardless of whether they
* are readable by any particular user. This should only be called on
* behalf of a system administrator. If identifiers are needed by a non-
* administrative user who must have explicit read rights, use
* selectReadableIdentifiers() instead.
*
* @return
* A Set containing all identifiers of all objects.
*/
Set<String> selectIdentifiers();
/**
* Selects the identifiers of all objects that are explicitly readable by
* the given user. If identifiers are needed by a system administrator
* (who, by definition, does not need explicit read rights), use
* selectIdentifiers() instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Set containing all identifiers of all readable objects.
*/
Set<String> selectReadableIdentifiers(@Param("user") UserModel user,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects all objects which have the given identifiers. If an identifier
* has no corresponding object, it will be ignored. This should only be
* called on behalf of a system administrator. If objects are needed by a
* non-administrative user who must have explicit read rights, use
* selectReadable() instead.
*
* @param identifiers
* The identifiers of the objects to return.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Collection of all objects having the given identifiers.
*/
Collection<ModelType> select(@Param("identifiers") Collection<String> identifiers,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects all objects which have the given identifiers and are explicitly
* readably by the given user. If an identifier has no corresponding
* object, or the corresponding object is unreadable, it will be ignored.
* If objects are needed by a system administrator (who, by definition,
* does not need explicit read rights), use select() instead.
*
* @param user
* The user whose permissions should determine whether an object
* is returned.
*
* @param identifiers
* The identifiers of the objects to return.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Collection of all objects having the given identifiers.
*/
Collection<ModelType> selectReadable(@Param("user") UserModel user,
@Param("identifiers") Collection<String> identifiers,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Inserts the given object into the database. If the object already
* exists, this will result in an error.
*
* @param object
* The object to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("object") ModelType object);
/**
* Deletes the given object into the database. If the object does not
* exist, this operation has no effect.
*
* @param identifier
* The identifier of the object to delete.
*
* @param caseSensitivity
* The case sensitivity configuration that contains information on
* whether usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("identifier") String identifier,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Updates the given existing object in the database. If the object does
* not actually exist, this operation has no effect.
*
* @param object
* The object to update.
*
* @return
* The number of rows updated.
*/
int update(@Param("object") ModelType object);
/**
* Deletes any arbitrary attributes currently associated with the given
* object in the database.
*
* @param object
* The object whose arbitrary attributes should be deleted.
*
* @return
* The number of rows deleted.
*/
int deleteAttributes(@Param("object") ModelType object);
/**
* Inserts all arbitrary attributes associated with the given object.
*
* @param object
* The object whose arbitrary attributes should be inserted.
*
* @return
* The number of rows inserted.
*/
int insertAttributes(@Param("object") ModelType object);
}

View File

@@ -0,0 +1,574 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.auth.jdbc.JDBCEnvironment;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper;
import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionModel;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.net.auth.Identifiable;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
import org.mybatis.guice.transactional.Transactional;
/**
* Service which provides convenience methods for creating, retrieving, and
* manipulating objects within directories. This service will automatically
* enforce the permissions of the current user.
*
* @param <InternalType>
* The specific internal implementation of the type of object this service
* provides access to.
*
* @param <ExternalType>
* The external interface or implementation of the type of object this
* service provides access to, as defined by the guacamole-ext API.
*
* @param <ModelType>
* The underlying model object used to represent InternalType in the
* database.
*/
public abstract class ModeledDirectoryObjectService<InternalType extends ModeledDirectoryObject<ModelType>,
ExternalType extends Identifiable, ModelType extends ObjectModel>
implements DirectoryObjectService<InternalType, ExternalType> {
/**
* All object permissions which are implicitly granted upon creation to the
* creator of the object.
*/
private static final ObjectPermission.Type[] IMPLICIT_OBJECT_PERMISSIONS = {
ObjectPermission.Type.READ,
ObjectPermission.Type.UPDATE,
ObjectPermission.Type.DELETE,
ObjectPermission.Type.ADMINISTER
};
/**
* The environment of the Guacamole server.
*/
@Inject
private JDBCEnvironment environment;
/**
* Returns an instance of a mapper for the type of object used by this
* service.
*
* @return
* A mapper which provides access to the model objects associated with
* the objects used by this service.
*/
protected abstract ModeledDirectoryObjectMapper<ModelType> getObjectMapper();
/**
* Returns an instance of a mapper for the type of permissions that affect
* the type of object used by this service.
*
* @return
* A mapper which provides access to the model objects associated with
* the permissions that affect the objects used by this service.
*/
protected abstract ObjectPermissionMapper getPermissionMapper();
/**
* Returns an instance of an object which is backed by the given model
* object.
*
* @param currentUser
* The user for whom this object is being created.
*
* @param model
* The model object to use to back the returned object.
*
* @return
* An object which is backed by the given model object.
*
* @throws GuacamoleException
* If the object instance cannot be created.
*/
protected abstract InternalType getObjectInstance(ModeledAuthenticatedUser currentUser,
ModelType model) throws GuacamoleException;
/**
* Returns the case sensitivity configuration for this service, which will
* be used to determine whether usernames and/or group names will be treated
* as case-sensitive.
*
* @return
* The case sensitivity configuration for this service.
*
* @throws GuacamoleException
* If an error occurs retrieving relevant configuration information.
*/
protected CaseSensitivity getCaseSensitivity() throws GuacamoleException {
// Retrieve the Guacamole setting.
return environment.getCaseSensitivity();
}
/**
* Returns an instance of a model object which is based on the given
* object.
*
* @param currentUser
* The user for whom this model object is being created.
*
* @param object
* The object to use to produce the returned model object.
*
* @return
* A model object which is based on the given object.
*
* @throws GuacamoleException
* If the model object instance cannot be created.
*/
protected abstract ModelType getModelInstance(ModeledAuthenticatedUser currentUser,
ExternalType object) throws GuacamoleException;
/**
* Returns whether the given user has permission to create the type of
* objects that this directory object service manages, taking into account
* permission inheritance through user groups.
*
* @param user
* The user being checked.
*
* @return
* true if the user has object creation permission relevant to this
* directory object service, false otherwise.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract boolean hasCreatePermission(ModeledAuthenticatedUser user)
throws GuacamoleException;
/**
* Returns whether the given user has permission to perform a certain
* action on a specific object managed by this directory object service,
* taking into account permission inheritance through user groups.
*
* @param user
* The user being checked.
*
* @param identifier
* The identifier of the object to check.
*
* @param type
* The type of action that will be performed.
*
* @return
* true if the user has object permission relevant described, false
* otherwise.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected boolean hasObjectPermission(ModeledAuthenticatedUser user,
String identifier, ObjectPermission.Type type)
throws GuacamoleException {
// Get object permissions
ObjectPermissionSet permissionSet = getEffectivePermissionSet(user);
// Return whether permission is granted
return user.isPrivileged()
|| permissionSet.hasPermission(type, identifier);
}
/**
* Returns the permission set associated with the given user and related
* to the type of objects handled by this directory object service, taking
* into account permission inheritance via user groups.
*
* @param user
* The user whose permissions are being retrieved.
*
* @return
* A permission set which contains the permissions associated with the
* given user and related to the type of objects handled by this
* directory object service.
*
* @throws GuacamoleException
* If permission to read the user's permissions is denied.
*/
protected abstract ObjectPermissionSet getEffectivePermissionSet(ModeledAuthenticatedUser user)
throws GuacamoleException;
/**
* Returns a collection of objects which are backed by the models in the
* given collection.
*
* @param currentUser
* The user for whom these objects are being created.
*
* @param models
* The model objects to use to back the objects within the returned
* collection.
*
* @return
* A collection of objects which are backed by the models in the given
* collection.
*
* @throws GuacamoleException
* If any of the object instances cannot be created.
*/
protected Collection<InternalType> getObjectInstances(ModeledAuthenticatedUser currentUser,
Collection<ModelType> models) throws GuacamoleException {
// Create new collection of objects by manually converting each model
Collection<InternalType> objects = new ArrayList<>(models.size());
for (ModelType model : models)
objects.add(getObjectInstance(currentUser, model));
return objects;
}
/**
* Called before any object is created through this directory object
* service. This function serves as a final point of validation before
* the create operation occurs. In its default implementation,
* beforeCreate() performs basic permissions checks.
*
* @param user
* The user creating the object.
*
* @param object
* The object being created.
*
* @param model
* The model of the object being created.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeCreate(ModeledAuthenticatedUser user,
ExternalType object, ModelType model) throws GuacamoleException {
// Verify permission to create objects
if (!user.isPrivileged() && !hasCreatePermission(user))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Called before any object is updated through this directory object
* service. This function serves as a final point of validation before
* the update operation occurs. In its default implementation,
* beforeUpdate() performs basic permissions checks.
*
* @param user
* The user updating the existing object.
*
* @param object
* The object being updated.
*
* @param model
* The model of the object being updated.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeUpdate(ModeledAuthenticatedUser user,
InternalType object, ModelType model) throws GuacamoleException {
// By default, do nothing.
if (!hasObjectPermission(user, model.getIdentifier(), ObjectPermission.Type.UPDATE))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Called before any object is deleted through this directory object
* service. This function serves as a final point of validation before
* the delete operation occurs. In its default implementation,
* beforeDelete() performs basic permissions checks.
*
* @param user
* The user deleting the existing object.
*
* @param identifier
* The identifier of the object being deleted.
*
* @throws GuacamoleException
* If the object is invalid, or an error prevents validating the given
* object.
*/
protected void beforeDelete(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Verify permission to delete objects
if (!hasObjectPermission(user, identifier, ObjectPermission.Type.DELETE))
throw new GuacamoleSecurityException("Permission denied.");
}
/**
* Returns whether the given string is a valid identifier within the JDBC
* authentication extension. Invalid identifiers may result in SQL errors
* from the underlying database when used in queries.
*
* @param identifier
* The string to check for validity.
*
* @return
* true if the given string is a valid identifier, false otherwise.
*/
protected boolean isValidIdentifier(String identifier) {
// Empty identifiers are invalid
if (identifier.isEmpty())
return false;
// Identifier is invalid if any non-numeric characters are present
for (int i = 0; i < identifier.length(); i++) {
if (!Character.isDigit(identifier.charAt(i)))
return false;
}
// Identifier is valid - contains only numeric characters
return true;
}
/**
* Filters the given collection of strings, returning a new collection
* containing only those strings which are valid identifiers. If no strings
* within the collection are valid identifiers, the returned collection will
* simply be empty.
*
* @param identifiers
* The collection of strings to filter.
*
* @return
* A new collection containing only the strings within the provided
* collection which are valid identifiers.
*/
protected List<String> filterIdentifiers(Collection<String> identifiers) {
// Obtain enough space for a full copy of the given identifiers
List<String> validIdentifiers = new ArrayList<>(identifiers.size());
// Add only valid identifiers to the copy
for (String identifier : identifiers) {
if (isValidIdentifier(identifier))
validIdentifiers.add(identifier);
}
return validIdentifiers;
}
@Override
public InternalType retrieveObject(ModeledAuthenticatedUser user,
String identifier) throws GuacamoleException {
// Pull objects having given identifier
Collection<InternalType> objects = retrieveObjects(user, Collections.singleton(identifier));
// If no such object, return null
if (objects.isEmpty())
return null;
// The object collection will have exactly one element unless the
// database has seriously lost integrity
assert(objects.size() == 1);
// Return first and only object
return objects.iterator().next();
}
@Override
public Collection<InternalType> retrieveObjects(ModeledAuthenticatedUser user,
Collection<String> identifiers) throws GuacamoleException {
// Ignore invalid identifiers
List<String> filteredIdentifiers = filterIdentifiers(identifiers);
// Do not query if no identifiers given
if (filteredIdentifiers.isEmpty())
return Collections.<InternalType>emptyList();
int batchSize = environment.getBatchSize();
boolean userIsPrivileged = user.isPrivileged();
CaseSensitivity caseSensitivity = getCaseSensitivity();
// Process the filteredIdentifiers in batches using Lists.partition() and flatMap
Collection<ModelType> allObjects = Lists.partition(filteredIdentifiers, batchSize).stream()
.flatMap(chunk -> {
Collection<ModelType> objects;
// Bypass permission checks if the user is privileged
if (userIsPrivileged)
objects = getObjectMapper().select(chunk, caseSensitivity);
// Otherwise only return explicitly readable identifiers
else
objects = getObjectMapper().selectReadable(user.getUser().getModel(),
chunk, user.getEffectiveUserGroups(), caseSensitivity);
return objects.stream();
})
.collect(Collectors.toList());
// Return collection of requested objects
return getObjectInstances(user, allObjects);
}
/**
* Returns an immutable collection of permissions that should be granted due
* to the creation of the given object. These permissions need not be
* granted solely to the user creating the object.
*
* @param user
* The user creating the object.
*
* @param model
* The object being created.
*
* @return
* The collection of implicit permissions that should be granted due to
* the creation of the given object.
*/
protected Collection<ObjectPermissionModel> getImplicitPermissions(ModeledAuthenticatedUser user,
ModelType model) {
// Check to see if the user granting permissions is a skeleton user,
// thus lacking database backing.
if (user.getUser().isSkeleton())
return Collections.emptyList();
// Build list of implicit permissions
Collection<ObjectPermissionModel> implicitPermissions =
new ArrayList<>(IMPLICIT_OBJECT_PERMISSIONS.length);
UserModel userModel = user.getUser().getModel();
for (ObjectPermission.Type permission : IMPLICIT_OBJECT_PERMISSIONS) {
// Create model which grants this permission to the current user
ObjectPermissionModel permissionModel = new ObjectPermissionModel();
permissionModel.setEntityID(userModel.getEntityID());
permissionModel.setType(permission);
permissionModel.setObjectIdentifier(model.getIdentifier());
// Add permission
implicitPermissions.add(permissionModel);
}
return Collections.unmodifiableCollection(implicitPermissions);
}
@Override
@Transactional
public InternalType createObject(ModeledAuthenticatedUser user, ExternalType object)
throws GuacamoleException {
ModelType model = getModelInstance(user, object);
beforeCreate(user, object, model);
// Create object
getObjectMapper().insert(model);
// Set identifier on original object
object.setIdentifier(model.getIdentifier());
// Add implicit permissions
Collection<ObjectPermissionModel> implicitPermissions = getImplicitPermissions(user, model);
if (!implicitPermissions.isEmpty())
getPermissionMapper().insert(implicitPermissions, getCaseSensitivity());
// Add any arbitrary attributes
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
return getObjectInstance(user, model);
}
@Override
public void deleteObject(ModeledAuthenticatedUser user, String identifier)
throws GuacamoleException {
beforeDelete(user, identifier);
// Delete object
getObjectMapper().delete(identifier, getCaseSensitivity());
}
@Override
@Transactional
public void updateObject(ModeledAuthenticatedUser user, InternalType object)
throws GuacamoleException {
ModelType model = object.getModel();
beforeUpdate(user, object, model);
// Update object
getObjectMapper().update(model);
// Replace any existing arbitrary attributes
getObjectMapper().deleteAttributes(model);
if (model.hasArbitraryAttributes())
getObjectMapper().insertAttributes(model);
}
@Override
public Set<String> getIdentifiers(ModeledAuthenticatedUser user)
throws GuacamoleException {
// Bypass permission checks if the user is privileged
if (user.isPrivileged())
return getObjectMapper().selectIdentifiers();
// Otherwise only return explicitly readable identifiers
else
return getObjectMapper().selectReadableIdentifiers(
user.getUser().getModel(),
user.getEffectiveUserGroups(),
getCaseSensitivity()
);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
/**
* Common base class for objects have an underlying model. For the purposes of
* JDBC-driven authentication providers, all modeled objects are also
* restricted.
*
* @param <ModelType>
* The type of model object which corresponds to this object.
*/
public abstract class ModeledObject<ModelType> extends RestrictedObject {
/**
* The internal model object containing the values which represent this
* object in the database.
*/
private ModelType model;
/**
* Initializes this object, associating it with the current authenticated
* user and populating it with data from the given model object
*
* @param currentUser
* The user that created or retrieved this object.
*
* @param model
* The backing model object.
*/
public void init(ModeledAuthenticatedUser currentUser, ModelType model) {
super.init(currentUser);
setModel(model);
}
/**
* Returns the backing model object. Changes to the model object will
* affect this object, and changes to this object will affect the model
* object.
*
* @return
* The backing model object.
*/
public ModelType getModel() {
return model;
}
/**
* Sets the backing model object. This will effectively replace all data
* contained within this object.
*
* @param model
* The backing model object.
*/
public void setModel(ModelType model) {
this.model = model;
}
}

View File

@@ -0,0 +1,288 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.permission.SystemPermissionService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.activeconnection.ActiveConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.ConnectionPermissionService;
import org.apache.guacamole.auth.jdbc.permission.SharingProfilePermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserGroupPermissionService;
import org.apache.guacamole.auth.jdbc.permission.UserPermissionService;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.Permissions;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermission;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
/**
* An implementation of the base Permissions interface which is common to both
* Users and UserGroups, backed by a database model.
*
* @param <ModelType>
* The type of model object that corresponds to this object.
*/
public abstract class ModeledPermissions<ModelType extends EntityModel>
extends ModeledDirectoryObject<ModelType> implements Permissions {
/**
* Service for retrieving entity details.
*/
@Inject
private EntityService entityService;
/**
* Service for retrieving system permissions.
*/
@Inject
private SystemPermissionService systemPermissionService;
/**
* Service for retrieving connection permissions.
*/
@Inject
private ConnectionPermissionService connectionPermissionService;
/**
* Service for retrieving connection group permissions.
*/
@Inject
private ConnectionGroupPermissionService connectionGroupPermissionService;
/**
* Service for retrieving sharing profile permissions.
*/
@Inject
private SharingProfilePermissionService sharingProfilePermissionService;
/**
* Service for retrieving active connection permissions.
*/
@Inject
private ActiveConnectionPermissionService activeConnectionPermissionService;
/**
* Service for retrieving user permissions.
*/
@Inject
private UserPermissionService userPermissionService;
/**
* Service for retrieving user group permissions.
*/
@Inject
private UserGroupPermissionService userGroupPermissionService;
/**
* Returns whether the underlying entity is a user. Entities may be either
* users or user groups.
*
* @return
* true if the underlying entity is a user, false otherwise.
*/
public boolean isUser() {
return getModel().getEntityType() == EntityType.USER;
}
/**
* Returns whether the underlying entity represents a specific user having
* the given username.
*
* @param username
* The username of a user.
*
* @return
* true if the underlying entity is a user that has the given username,
* false otherwise.
*/
public boolean isUser(String username) {
return isUser() && getIdentifier().equals(username);
}
/**
* Returns whether the underlying entity is a user group. Entities may be
* either users or user groups.
*
* @return
* true if the underlying entity is a user group, false otherwise.
*/
public boolean isUserGroup() {
return getModel().getEntityType() == EntityType.USER_GROUP;
}
/**
* Returns whether this entity is effectively unrestricted by permissions,
* such as a system administrator or an internal user operating via a
* privileged UserContext. Permission inheritance via user groups is taken
* into account.
*
* @return
* true if this entity should be unrestricted by permissions, false
* otherwise.
*
* @throws GuacamoleException
* If an error occurs while determining whether permission restrictions
* apply to the entity.
*/
public boolean isPrivileged() throws GuacamoleException {
SystemPermissionSet systemPermissionSet = getEffective().getSystemPermissions();
return systemPermissionSet.hasPermission(SystemPermission.Type.ADMINISTER);
}
@Override
public SystemPermissionSet getSystemPermissions()
throws GuacamoleException {
return systemPermissionService.getPermissionSet(getCurrentUser(), this,
Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getConnectionPermissions()
throws GuacamoleException {
return connectionPermissionService.getPermissionSet(getCurrentUser(),
this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getConnectionGroupPermissions()
throws GuacamoleException {
return connectionGroupPermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getSharingProfilePermissions()
throws GuacamoleException {
return sharingProfilePermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getActiveConnectionPermissions()
throws GuacamoleException {
return activeConnectionPermissionService.getPermissionSet(
getCurrentUser(), this, Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getUserPermissions()
throws GuacamoleException {
return userPermissionService.getPermissionSet(getCurrentUser(), this,
Collections.<String>emptySet());
}
@Override
public ObjectPermissionSet getUserGroupPermissions() throws GuacamoleException {
return userGroupPermissionService.getPermissionSet(getCurrentUser(),
this, Collections.<String>emptySet());
}
/**
* Returns the identifiers of all user groups defined within the database
* which apply to this user, including any groups inherited through
* membership in yet more groups.
*
* @return
* The identifiers of all user groups defined within the database which
* apply to this user.
*/
public Set<String> getEffectiveUserGroups() {
return entityService.retrieveEffectiveGroups(this,
Collections.<String>emptySet());
}
/**
* Returns a Permissions object which represents all permissions granted to
* this entity, including any permissions inherited through group
* membership.
*
* @return
* A Permissions object which represents all permissions granted to
* this entity.
*/
public Permissions getEffective() {
final ModeledAuthenticatedUser authenticatedUser = getCurrentUser();
final Set<String> effectiveGroups;
// If this user is the currently-authenticated user, include any
// additional effective groups declared by the authentication system
if (authenticatedUser.getIdentifier().equals(getIdentifier()))
effectiveGroups = entityService.retrieveEffectiveGroups(this,
authenticatedUser.getEffectiveUserGroups());
// Otherwise, just include effective groups from the database
else
effectiveGroups = getEffectiveUserGroups();
// Return a permissions object which describes all effective
// permissions, including any permissions inherited via user groups
return new Permissions() {
@Override
public ObjectPermissionSet getActiveConnectionPermissions()
throws GuacamoleException {
return activeConnectionPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getConnectionGroupPermissions()
throws GuacamoleException {
return connectionGroupPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getConnectionPermissions()
throws GuacamoleException {
return connectionPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getSharingProfilePermissions()
throws GuacamoleException {
return sharingProfilePermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public SystemPermissionSet getSystemPermissions()
throws GuacamoleException {
return systemPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getUserPermissions()
throws GuacamoleException {
return userPermissionService.getPermissionSet(authenticatedUser, ModeledPermissions.this, effectiveGroups);
}
@Override
public ObjectPermissionSet getUserGroupPermissions()
throws GuacamoleException {
return userGroupPermissionService.getPermissionSet(getCurrentUser(), ModeledPermissions.this, effectiveGroups);
}
};
}
}

View File

@@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
/**
* Object representation of a Guacamole object, such as a user or connection,
* as represented in the database.
*/
public abstract class ObjectModel {
/**
* The ID of this object in the database, if any.
*/
private Integer objectID;
/**
* The unique identifier which identifies this object.
*/
private String identifier;
/**
* Map of all arbitrary attributes associated with this object but not
* directly mapped to a particular column.
*/
private ArbitraryAttributeMap arbitraryAttributes =
new ArbitraryAttributeMap();
/**
* Creates a new, empty object.
*/
public ObjectModel() {
}
/**
* Returns the identifier that uniquely identifies this object.
*
* @return
* The identifier that uniquely identifies this object.
*/
public String getIdentifier() {
return identifier;
}
/**
* Sets the identifier that uniquely identifies this object.
*
* @param identifier
* The identifier that uniquely identifies this object.
*/
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
/**
* Returns the ID of this object in the database, if it exists.
*
* @return
* The ID of this object in the database, or null if this object was
* not retrieved from the database.
*/
public Integer getObjectID() {
return objectID;
}
/**
* Sets the ID of this object to the given value.
*
* @param objectID
* The ID to assign to this object.
*/
public void setObjectID(Integer objectID) {
this.objectID = objectID;
}
/**
* Returns a map of attribute name/value pairs for all attributes associated
* with this model which do not have explicit mappings to actual model
* properties. All other attributes (those which are explicitly supported
* by the model) should instead be mapped to properties with corresponding
* and properly-typed columns.
*
* @return
* A map of attribute name/value pairs for all attributes associated
* with this model which do not otherwise have explicit mappings to
* properties.
*/
public ArbitraryAttributeMap getArbitraryAttributeMap() {
return arbitraryAttributes;
}
/**
* Returns whether at least one arbitrary attribute name/value pair has
* been associated with this object.
*
* @return
* true if this object has at least one arbitrary attribute set, false
* otherwise.
*/
public boolean hasArbitraryAttributes() {
return !arbitraryAttributes.isEmpty();
}
/**
* Returns a Collection view of the equivalent attribute model objects
* which make up the map of arbitrary attribute name/value pairs returned
* by getArbitraryAttributeMap(). Additions and removals on the returned
* Collection directly affect the attribute map.
*
* @return
* A Collection view of the map returned by
* getArbitraryAttributeMap().
*/
public Collection<ArbitraryAttributeModel> getArbitraryAttributes() {
return arbitraryAttributes.toModelCollection();
}
/**
* Replaces all arbitrary attributes associated with this object with the
* attribute name/value pairs within the given collection of model objects.
*
* @param arbitraryAttributes
* The Collection of model objects containing the attribute name/value
* pairs which should replace all currently-stored arbitrary attributes,
* if any.
*/
public void setArbitraryAttributes(Collection<ArbitraryAttributeModel> arbitraryAttributes) {
this.arbitraryAttributes = ArbitraryAttributeMap.fromModelCollection(arbitraryAttributes);
}
}

View File

@@ -0,0 +1,142 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for the relations represented by a particular RelatedObjectSet
* implementation.
*
* @param <ParentModelType>
* The underlying database model of the object on the parent side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper.
*/
public interface ObjectRelationMapper<ParentModelType extends ObjectModel> {
/**
* Inserts rows as necessary to establish the one-to-many relationship
* represented by the RelatedObjectSet between the given parent and
* children. If the relation for any parent/child pair is already present,
* no attempt is made to insert a new row for that relation.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param children
* The identifiers of the objects on the child side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param caseSensitivity
* The case sensitivity configuration, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("parent") ParentModelType parent,
@Param("children") Collection<String> children,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Deletes rows as necessary to modify the one-to-many relationship
* represented by the RelatedObjectSet between the given parent and
* children. If the relation for any parent/child pair does not exist,
* that specific relation is ignored, and deletion proceeds with the
* remaining relations.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param children
* The identifiers of the objects on the child side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @param caseSensitivity
* The case sensitivity configuration, used to determine whether
* usernames and/or group names will be treated as case-sensitive.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("parent") ParentModelType parent,
@Param("children") Collection<String> children,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Retrieves the identifiers of all objects on the child side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper. This should only be called on behalf of a
* system administrator. If identifiers are needed by a non-administrative
* user who must have explicit read rights, use
* selectReadableChildIdentifiers() instead.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @return
* A Set containing the identifiers of all objects on the child side
* of the one-to-many relationship.
*/
Set<String> selectChildIdentifiers(@Param("parent") ParentModelType parent);
/**
* Retrieves the identifiers of all objects on the child side of the
* one-to-many relationship represented by the RelatedObjectSet mapped by
* this ObjectRelationMapper, including only those objects which are
* explicitly readable by the given user. If identifiers are needed by a
* system administrator (who, by definition, does not need explicit read
* rights), use selectChildIdentifiers() instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param effectiveGroups
* The identifiers of any known effective groups that should be taken
* into account, such as those defined externally to the database.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @param parent
* The model of the object on the parent side of the one-to-many
* relationship represented by the RelatedObjectSet.
*
* @return
* A Set containing the identifiers of all readable objects on the
* child side of the one-to-many relationship.
*/
Set<String> selectReadableChildIdentifiers(@Param("user") UserModel user,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity,
@Param("parent") ParentModelType parent);
}

View File

@@ -0,0 +1,234 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
import org.apache.guacamole.net.auth.permission.ObjectPermission;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* A database implementation of RelatedObjectSet which provides access to a
* parent object and corresponding set of objects related to the parent, subject
* to object-level permissions. Though the parent and child objects have
* specific types, only the parent object's type is enforced through type
* parameters, as child objects are represented by identifiers only.
*
* @param <ParentObjectType>
* The type of object that represents the parent side of the relation.
*
* @param <ParentModelType>
* The underlying database model of the parent object.
*/
public abstract class RelatedObjectSet<ParentObjectType extends ModeledDirectoryObject<ParentModelType>, ParentModelType extends ObjectModel>
extends RestrictedObject implements org.apache.guacamole.net.auth.RelatedObjectSet {
/**
* The parent object which shares some arbitrary relation with the objects
* within this set.
*/
private ParentObjectType parent;
/**
* Creates a new RelatedObjectSet. The resulting object set must still be
* initialized by a call to init().
*/
public RelatedObjectSet() {
}
/**
* Initializes this RelatedObjectSet with the current user and the single
* object on the parent side of the one-to-many relation represented by the
* set.
*
* @param currentUser
* The user who queried this RelatedObjectSet, and whose permissions
* dictate the access level of all operations performed on this set.
*
* @param parent
* The parent object which shares some arbitrary relation with the
* objects within this set.
*/
public void init(ModeledAuthenticatedUser currentUser, ParentObjectType parent) {
super.init(currentUser);
this.parent = parent;
}
/**
* Return the current case sensitivity setting, which can be used to
* determine whether or not certain identifiers should be treated as
* case-sensitive.
*
* @return
* The current case sensitivity setting.
*
* @throws GuacamoleException
* If an error occurs retrieving configuration information on
* case sensitivity.
*/
protected CaseSensitivity getCaseSensitivity() throws GuacamoleException {
// Identifiers are not case-sensitive by default.
return CaseSensitivity.DISABLED;
}
/**
* Returns the mapper which provides low-level access to the database
* models which drive the relation represented by this RelatedObjectSet.
*
* @return
* The mapper which provides low-level access to the database
* models which drive the relation represented by this
* RelatedObjectSet.
*/
protected abstract ObjectRelationMapper<ParentModelType> getObjectRelationMapper();
/**
* Returns the permission set which exposes the effective permissions
* available to the current user regarding the objects on the parent side
* of the one-to-many relationship represented by this RelatedObjectSet.
* Permission inheritance through user groups is taken into account.
*
* @return
* The permission set which exposes the effective permissions
* available to the current user regarding the objects on the parent
* side of the one-to-many relationship represented by this
* RelatedObjectSet.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
protected abstract ObjectPermissionSet getParentObjectEffectivePermissionSet()
throws GuacamoleException;
/**
* Returns the permission set which exposes the effective permissions
* available to the current user regarding the objects on the child side
* of the one-to-many relationship represented by this RelatedObjectSet.
* Permission inheritance through user groups is taken into account.
*
* @return
* The permission set which exposes the effective permissions
* available to the current user regarding the objects on the child
* side of the one-to-many relationship represented by this
* RelatedObjectSet.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
protected abstract ObjectPermissionSet getChildObjectEffectivePermissionSet()
throws GuacamoleException;
/**
* Returns whether the current user has permission to alter the status of
* the relation between the parent object and the given child objects.
*
* @param identifiers
* The identifiers of all objects on the child side of the one-to-many
* relation being changed.
*
* @return
* true if the user has permission to make the described changes,
* false otherwise.
*
* @throws GuacamoleException
* If permission to query permission status is denied.
*/
private boolean canAlterRelation(Collection<String> identifiers)
throws GuacamoleException {
// Privileged users (such as system administrators) may alter any
// relations
if (getCurrentUser().isPrivileged())
return true;
// Non-admin users require UPDATE permission on the parent object ...
if (!getParentObjectEffectivePermissionSet().hasPermission(
ObjectPermission.Type.UPDATE, parent.getIdentifier()))
return false;
// ... as well as UPDATE permission on all child objects being changed
Collection<String> accessibleIdentifiers =
getChildObjectEffectivePermissionSet().getAccessibleObjects(
Collections.singleton(ObjectPermission.Type.UPDATE),
identifiers);
return accessibleIdentifiers.size() == identifiers.size();
}
@Override
public Set<String> getObjects() throws GuacamoleException {
// Bypass permission checks if the user is a privileged
ModeledAuthenticatedUser user = getCurrentUser();
if (user.isPrivileged())
return getObjectRelationMapper().selectChildIdentifiers(parent.getModel());
// Otherwise only return explicitly readable identifiers
return getObjectRelationMapper().selectReadableChildIdentifiers(
user.getUser().getModel(), user.getEffectiveUserGroups(),
getCaseSensitivity(),
parent.getModel());
}
@Override
public void addObjects(Set<String> identifiers) throws GuacamoleException {
// Nothing to do if nothing provided
if (identifiers.isEmpty())
return;
// Create relations only if permission is granted
if (canAlterRelation(identifiers))
getObjectRelationMapper().insert(parent.getModel(), identifiers,
getCaseSensitivity());
// User lacks permission to add user groups
else
throw new GuacamoleSecurityException("Permission denied.");
}
@Override
public void removeObjects(Set<String> identifiers) throws GuacamoleException {
// Nothing to do if nothing provided
if (identifiers.isEmpty())
return;
// Delete relations only if permission is granted
if (canAlterRelation(identifiers))
getObjectRelationMapper().delete(parent.getModel(), identifiers,
getCaseSensitivity());
// User lacks permission to remove user groups
else
throw new GuacamoleSecurityException("Permission denied.");
}
}

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.base;
import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser;
/**
* Common base class for objects that are associated with the users that
* obtain them.
*/
public abstract class RestrictedObject {
/**
* The user this object belongs to. Access is based on his/her permission
* settings.
*/
private ModeledAuthenticatedUser currentUser;
/**
* Initializes this object, associating it with the current authenticated
* user and populating it with data from the given model object
*
* @param currentUser
* The user that created or retrieved this object.
*/
public void init(ModeledAuthenticatedUser currentUser) {
setCurrentUser(currentUser);
}
/**
* Returns the user that created or queried this object. This user's
* permissions dictate what operations can be performed on or through this
* object.
*
* @return
* The user that created or queried this object.
*/
public ModeledAuthenticatedUser getCurrentUser() {
return currentUser;
}
/**
* Sets the user that created or queried this object. This user's
* permissions dictate what operations can be performed on or through this
* object.
*
* @param currentUser
* The user that created or queried this object.
*/
public void setCurrentUser(ModeledAuthenticatedUser currentUser) {
this.currentUser = currentUser;
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
/**
* Base classes supporting JDBC-driven authentication providers and defining
* the relationships between the model and the implementations of guacamole-ext
* classes.
*/
package org.apache.guacamole.auth.jdbc.base;

View File

@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.jdbc.base.JDBCDirectory;
import org.apache.guacamole.net.auth.Connection;
import org.mybatis.guice.transactional.Transactional;
/**
* Implementation of the Connection Directory which is driven by an underlying,
* arbitrary database.
*/
public class ConnectionDirectory extends JDBCDirectory<Connection> {
/**
* Service for managing connection objects.
*/
@Inject
private ConnectionService connectionService;
@Override
public Connection get(String identifier) throws GuacamoleException {
return connectionService.retrieveObject(getCurrentUser(), identifier);
}
@Override
@Transactional
public Collection<Connection> getAll(Collection<String> identifiers) throws GuacamoleException {
Collection<ModeledConnection> objects = connectionService.retrieveObjects(getCurrentUser(), identifiers);
return Collections.<Connection>unmodifiableCollection(objects);
}
@Override
@Transactional
public Set<String> getIdentifiers() throws GuacamoleException {
return connectionService.getIdentifiers(getCurrentUser());
}
@Override
@Transactional
public void add(Connection object) throws GuacamoleException {
connectionService.createObject(getCurrentUser(), object);
}
@Override
@Transactional
public void update(Connection object) throws GuacamoleException {
ModeledConnection connection = (ModeledConnection) object;
connectionService.updateObject(getCurrentUser(), connection);
}
@Override
@Transactional
public void remove(String identifier) throws GuacamoleException {
connectionService.deleteObject(getCurrentUser(), identifier);
}
}

View File

@@ -0,0 +1,101 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Collection;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ModeledDirectoryObjectMapper;
import org.apache.guacamole.auth.jdbc.user.UserModel;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for connection objects.
*/
public interface ConnectionMapper extends ModeledDirectoryObjectMapper<ConnectionModel> {
/**
* Selects the identifiers of all connections within the given parent
* connection group, regardless of whether they are readable by any
* particular user. This should only be called on behalf of a system
* administrator. If identifiers are needed by a non-administrative user
* who must have explicit read rights, use
* selectReadableIdentifiersWithin() instead.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @return
* A Set containing all identifiers of all objects.
*/
Set<String> selectIdentifiersWithin(@Param("parentIdentifier") String parentIdentifier);
/**
* Selects the identifiers of all connections within the given parent
* connection group that are explicitly readable by the given user. If
* identifiers are needed by a system administrator (who, by definition,
* does not need explicit read rights), use selectIdentifiersWithin()
* instead.
*
* @param user
* The user whose permissions should determine whether an identifier
* is returned.
*
* @param parentIdentifier
* The identifier of the parent connection group, or null if the root
* connection group is to be queried.
*
* @param effectiveGroups
* The identifiers of all groups that should be taken into account
* when determining the permissions effectively granted to the user. If
* no groups are given, only permissions directly granted to the user
* will be used.
*
* @param caseSensitivity
* The object that contains current configuration for case sensitivity
* for usernames and group names.
*
* @return
* A Set containing all identifiers of all readable objects.
*/
Set<String> selectReadableIdentifiersWithin(@Param("user") UserModel user,
@Param("parentIdentifier") String parentIdentifier,
@Param("effectiveGroups") Collection<String> effectiveGroups,
@Param("caseSensitivity") CaseSensitivity caseSensitivity);
/**
* Selects the connection within the given parent group and having the
* given name. If no such connection exists, null is returned.
*
* @param parentIdentifier
* The identifier of the parent group to search within.
*
* @param name
* The name of the connection to find.
*
* @return
* The connection having the given name within the given parent group,
* or null if no such connection exists.
*/
ConnectionModel selectOneByName(@Param("parentIdentifier") String parentIdentifier,
@Param("name") String name);
}

View File

@@ -0,0 +1,395 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.guacamole.auth.jdbc.base.ChildObjectModel;
import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration.EncryptionMethod;
/**
* Object representation of a Guacamole connection, as represented in the
* database.
*/
public class ConnectionModel extends ChildObjectModel {
/**
* The human-readable name associated with this connection.
*/
private String name;
/**
* The name of the protocol to use when connecting to this connection.
*/
private String protocol;
/**
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if the
* default restrictions should be applied.
*/
private Integer maxConnections;
/**
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction applies,
* or null if the default restrictions should be applied.
*/
private Integer maxConnectionsPerUser;
/**
* The weight of the connection for the purposes of calculating
* WLC algorithm. null indicates nothing has been set, and anything less
* than 1 eliminates the system from being used for connections.
*/
private Integer connectionWeight;
/**
* Whether this connection should be reserved for failover. Failover-only
* connections within a balancing group are only used when all non-failover
* connections are unavailable.
*/
private boolean failoverOnly;
/**
* The identifiers of all readable sharing profiles associated with this
* connection.
*/
private Set<String> sharingProfileIdentifiers = new HashSet<String>();
/**
* The hostname of the guacd instance to use, or null if the hostname of the
* default guacd instance should be used.
*/
private String proxyHostname;
/**
* The port of the guacd instance to use, or null if the port of the default
* guacd instance should be used.
*/
private Integer proxyPort;
/**
* The encryption method required by the desired guacd instance, or null if
* the encryption method of the default guacd instance should be used.
*/
private EncryptionMethod proxyEncryptionMethod;
/**
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
private Date lastActive;
/**
* Creates a new, empty connection.
*/
public ConnectionModel() {
}
/**
* Returns the name associated with this connection.
*
* @return
* The name associated with this connection.
*/
public String getName() {
return name;
}
/**
* Sets the name associated with this connection.
*
* @param name
* The name to associate with this connection.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the name of the protocol to use when connecting to this
* connection.
*
* @return
* The name of the protocol to use when connecting to this connection.
*/
public String getProtocol() {
return protocol;
}
/**
* Sets the name of the protocol to use when connecting to this connection.
*
* @param protocol
* The name of the protocol to use when connecting to this connection.
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
/**
* Returns the maximum number of connections that can be established to
* this connection concurrently.
*
* @return
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if
* the default restrictions should be applied.
*/
public Integer getMaxConnections() {
return maxConnections;
}
/**
* Sets the maximum number of connections that can be established to this
* connection concurrently.
*
* @param maxConnections
* The maximum number of connections that can be established to this
* connection concurrently, zero if no restriction applies, or null if
* the default restrictions should be applied.
*/
public void setMaxConnections(Integer maxConnections) {
this.maxConnections = maxConnections;
}
/**
* Returns the maximum number of connections that can be established to
* this connection concurrently by any one user.
*
* @return
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction
* applies, or null if the default restrictions should be applied.
*/
public Integer getMaxConnectionsPerUser() {
return maxConnectionsPerUser;
}
/**
* Sets the connection weight for load balancing.
*
* @param connectionWeight
* The weight of the connection used in load balancing.
* The value is not required for the connection (null), and
* values less than 1 will prevent the connection from being
* used.
*/
public void setConnectionWeight(Integer connectionWeight) {
this.connectionWeight = connectionWeight;
}
/**
* Returns the connection weight used in applying weighted
* load balancing algorithms.
*
* @return
* The connection weight used in applying weighted
* load balancing aglorithms.
*/
public Integer getConnectionWeight() {
return connectionWeight;
}
/**
* Returns whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @return
* true if this connection should be reserved for failover, false
* otherwise.
*/
public boolean isFailoverOnly() {
return failoverOnly;
}
/**
* Sets whether this connection should be reserved for failover.
* Failover-only connections within a balancing group are only used when
* all non-failover connections are unavailable.
*
* @param failoverOnly
* true if this connection should be reserved for failover, false
* otherwise.
*/
public void setFailoverOnly(boolean failoverOnly) {
this.failoverOnly = failoverOnly;
}
/**
* Sets the maximum number of connections that can be established to this
* connection concurrently by any one user.
*
* @param maxConnectionsPerUser
* The maximum number of connections that can be established to this
* connection concurrently by any one user, zero if no restriction
* applies, or null if the default restrictions should be applied.
*/
public void setMaxConnectionsPerUser(Integer maxConnectionsPerUser) {
this.maxConnectionsPerUser = maxConnectionsPerUser;
}
/**
* Returns the hostname of the guacd instance to use. If the hostname of the
* default guacd instance should be used instead, null is returned.
*
* @return
* The hostname of the guacd instance to use, or null if the hostname
* of the default guacd instance should be used.
*/
public String getProxyHostname() {
return proxyHostname;
}
/**
* Sets the hostname of the guacd instance to use.
*
* @param proxyHostname
* The hostname of the guacd instance to use, or null if the hostname
* of the default guacd instance should be used.
*/
public void setProxyHostname(String proxyHostname) {
this.proxyHostname = proxyHostname;
}
/**
* Returns the port of the guacd instance to use. If the port of the default
* guacd instance should be used instead, null is returned.
*
* @return
* The port of the guacd instance to use, or null if the port of the
* default guacd instance should be used.
*/
public Integer getProxyPort() {
return proxyPort;
}
/**
* Sets the port of the guacd instance to use.
*
* @param proxyPort
* The port of the guacd instance to use, or null if the port of the
* default guacd instance should be used.
*/
public void setProxyPort(Integer proxyPort) {
this.proxyPort = proxyPort;
}
/**
* Returns the type of encryption required by the desired guacd instance.
* If the encryption method of the default guacd instance should be used
* instead, null is returned.
*
* @return
* The type of encryption required by the desired guacd instance, or
* null if the encryption method of the default guacd instance should
* be used.
*/
public EncryptionMethod getProxyEncryptionMethod() {
return proxyEncryptionMethod;
}
/**
* Sets the type of encryption which should be used when connecting to
* guacd, if any.
*
* @param proxyEncryptionMethod
* The type of encryption required by the desired guacd instance, or
* null if the encryption method of the default guacd instance should
* be used.
*/
public void setProxyEncryptionMethod(EncryptionMethod proxyEncryptionMethod) {
this.proxyEncryptionMethod = proxyEncryptionMethod;
}
/**
* Returns the identifiers of all readable sharing profiles associated with
* this connection. This is set only when the connection is queried, and has
* no effect when a connection is inserted, updated, or deleted.
*
* @return
* The identifiers of all readable sharing profiles associated with
* this connection.
*/
public Set<String> getSharingProfileIdentifiers() {
return sharingProfileIdentifiers;
}
/**
* Sets the identifiers of all readable sharing profiles associated with
* this connection. This should be set only when the connection is queried,
* as it has no effect when a connection is inserted, updated, or deleted.
*
* @param sharingProfileIdentifiers
* The identifiers of all readable sharing profiles associated with
* this connection.
*/
public void setSharingProfileIdentifiers(Set<String> sharingProfileIdentifiers) {
this.sharingProfileIdentifiers = sharingProfileIdentifiers;
}
/**
* Returns the date and time that this connection was last used, or null if
* this connection has never been used.
*
* @return
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public Date getLastActive() {
return lastActive;
}
/**
* Sets the date and time that this connection was last used. This value is
* expected to be set automatically via queries, derived from connection
* history records. It does NOT correspond to an actual column, and values
* set manually through invoking this function will not persist.
*
* @param lastActive
* The date and time that this connection was last used, or null if this
* connection has never been used.
*/
public void setLastActive(Date lastActive) {
this.lastActive = lastActive;
}
@Override
public String getIdentifier() {
// If no associated ID, then no associated identifier
Integer id = getObjectID();
if (id == null)
return null;
// Otherwise, the identifier is the ID as a string
return id.toString();
}
@Override
public void setIdentifier(String identifier) {
throw new UnsupportedOperationException("Connection identifiers are derived from IDs. They cannot be set.");
}
}

View File

@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import java.util.Collection;
import org.apache.ibatis.annotations.Param;
/**
* Mapper for connection parameter objects.
*/
public interface ConnectionParameterMapper {
/**
* Returns a collection of all parameters associated with the connection
* having the given identifier.
*
* @param identifier
* The identifier of the connection whose parameters are to be
* retrieved.
*
* @return
* A collection of all parameters associated with the connection
* having the given identifier. This collection will be empty if no
* such connection exists.
*/
Collection<ConnectionParameterModel> select(@Param("identifier") String identifier);
/**
* Inserts each of the parameter model objects in the given collection as
* new connection parameters.
*
* @param parameters
* The connection parameters to insert.
*
* @return
* The number of rows inserted.
*/
int insert(@Param("parameters") Collection<ConnectionParameterModel> parameters);
/**
* Deletes all parameters associated with the connection having the given
* identifier.
*
* @param identifier
* The identifier of the connection whose parameters should be
* deleted.
*
* @return
* The number of rows deleted.
*/
int delete(@Param("identifier") String identifier);
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
/**
* A single parameter name/value pair belonging to a connection.
*/
public class ConnectionParameterModel {
/**
* The identifier of the connection associated with this parameter.
*/
private String connectionIdentifier;
/**
* The name of the parameter.
*/
private String name;
/**
* The value the parameter is set to.
*/
private String value;
/**
* Returns the identifier of the connection associated with this parameter.
*
* @return
* The identifier of the connection associated with this parameter.
*/
public String getConnectionIdentifier() {
return connectionIdentifier;
}
/**
* Sets the identifier of the connection associated with this parameter.
*
* @param connectionIdentifier
* The identifier of the connection to associate with this parameter.
*/
public void setConnectionIdentifier(String connectionIdentifier) {
this.connectionIdentifier = connectionIdentifier;
}
/**
* Returns the name of this parameter.
*
* @return
* The name of this parameter.
*/
public String getName() {
return name;
}
/**
* Sets the name of this parameter.
*
* @param name
* The name of this parameter.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the value of this parameter.
*
* @return
* The value of this parameter.
*/
public String getValue() {
return value;
}
/**
* Sets the value of this parameter.
*
* @param value
* The value of this parameter.
*/
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordMapper;
/**
* Mapper for connection record objects.
*/
public interface ConnectionRecordMapper extends ActivityRecordMapper<ConnectionRecordModel> {}

View File

@@ -0,0 +1,156 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.auth.jdbc.connection;
import org.apache.guacamole.auth.jdbc.base.ActivityRecordModel;
/**
* A single connection record representing a past usage of a particular
* connection. If the connection was being shared, the sharing profile used to
* join the connection is included in the record.
*/
public class ConnectionRecordModel extends ActivityRecordModel {
/**
* The identifier of the connection associated with this connection record.
*/
private String connectionIdentifier;
/**
* The name of the connection associated with this connection record.
*/
private String connectionName;
/**
* The identifier of the sharing profile associated with this connection
* record. If no sharing profile was used, or the sharing profile that was
* used was deleted, this will be null.
*/
private String sharingProfileIdentifier;
/**
* The name of the sharing profile associated with this connection record.
* If no sharing profile was used, this will be null. If the sharing profile
* that was used was deleted, this will still contain the name of the
* sharing profile at the time that the connection was used.
*/
private String sharingProfileName;
/**
* Returns the identifier of the connection associated with this connection
* record.
*
* @return
* The identifier of the connection associated with this connection
* record.
*/
public String getConnectionIdentifier() {
return connectionIdentifier;
}
/**
* Sets the identifier of the connection associated with this connection
* record.
*
* @param connectionIdentifier
* The identifier of the connection to associate with this connection
* record.
*/
public void setConnectionIdentifier(String connectionIdentifier) {
this.connectionIdentifier = connectionIdentifier;
}
/**
* Returns the name of the connection associated with this connection
* record.
*
* @return
* The name of the connection associated with this connection
* record.
*/
public String getConnectionName() {
return connectionName;
}
/**
* Sets the name of the connection associated with this connection
* record.
*
* @param connectionName
* The name of the connection to associate with this connection
* record.
*/
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
/**
* Returns the identifier of the sharing profile associated with this
* connection record. If no sharing profile was used, or the sharing profile
* that was used was deleted, this will be null.
*
* @return
* The identifier of the sharing profile associated with this connection
* record, or null if no sharing profile was used or if the sharing
* profile that was used was deleted.
*/
public String getSharingProfileIdentifier() {
return sharingProfileIdentifier;
}
/**
* Sets the identifier of the sharing profile associated with this
* connection record. If no sharing profile was used, this should be null.
*
* @param sharingProfileIdentifier
* The identifier of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public void setSharingProfileIdentifier(String sharingProfileIdentifier) {
this.sharingProfileIdentifier = sharingProfileIdentifier;
}
/**
* Returns the human-readable name of the sharing profile associated with this
* connection record. If no sharing profile was used, this will be null.
*
* @return
* The human-readable name of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public String getSharingProfileName() {
return sharingProfileName;
}
/**
* Sets the human-readable name of the sharing profile associated with this
* connection record. If no sharing profile was used, this should be null.
*
* @param sharingProfileName
* The human-readable name of the sharing profile associated with this
* connection record, or null if no sharing profile was used.
*/
public void setSharingProfileName(String sharingProfileName) {
this.sharingProfileName = sharingProfileName;
}
}

Some files were not shown because too many files have changed in this diff Show More