Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
1
extensions/.ratignore
Normal file
1
extensions/.ratignore
Normal file
@@ -0,0 +1 @@
|
||||
guacamole-auth-radius/**/*
|
||||
0
extensions/guacamole-auth-ban/.ratignore
Normal file
0
extensions/guacamole-auth-ban/.ratignore
Normal file
81
extensions/guacamole-auth-ban/pom.xml
Normal file
81
extensions/guacamole-auth-ban/pom.xml
Normal 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>
|
||||
54
extensions/guacamole-auth-ban/src/main/assembly/dist.xml
Normal file
54
extensions/guacamole-auth-ban/src/main/assembly/dist.xml
Normal 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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"LOGIN": {
|
||||
"ERROR_TOO_MANY_ATTEMPTS" : "Too many failed authentication attempts. Please try again later."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"LOGIN": {
|
||||
"ERROR_TOO_MANY_ATTEMPTS" : "Zbyt wiele nieudanych prób logowania. Spróbuj ponownie później."
|
||||
}
|
||||
}
|
||||
3
extensions/guacamole-auth-duo/.gitignore
vendored
Normal file
3
extensions/guacamole-auth-duo/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
src/main/resources/generated/
|
||||
target/
|
||||
*~
|
||||
2
extensions/guacamole-auth-duo/.ratignore
Normal file
2
extensions/guacamole-auth-duo/.ratignore
Normal file
@@ -0,0 +1,2 @@
|
||||
src/main/resources/lib/DuoWeb/**/*
|
||||
src/main/java/com/duosecurity/duoweb/**/*
|
||||
142
extensions/guacamole-auth-duo/pom.xml
Normal file
142
extensions/guacamole-auth-duo/pom.xml
Normal 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>
|
||||
53
extensions/guacamole-auth-duo/src/main/assembly/dist.xml
Normal file
53
extensions/guacamole-auth-duo/src/main/assembly/dist.xml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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..."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。",
|
||||
"INFO_DUO_REDIRECT_PENDING" : "Duoへリダイレクトしています。"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo 유효성 검사 코드가 잘못되었습니다.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "계속하려면 Duo로 인증하세요."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Бэкенд Duo TFA"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Неверный код валидации Duo.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Пожалуйста, аутентифицируйтесь в Duo для продолжения."
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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进行身份验证。"
|
||||
}
|
||||
|
||||
}
|
||||
2
extensions/guacamole-auth-header/.gitignore
vendored
Normal file
2
extensions/guacamole-auth-header/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target/
|
||||
*~
|
||||
0
extensions/guacamole-auth-header/.ratignore
Normal file
0
extensions/guacamole-auth-header/.ratignore
Normal file
67
extensions/guacamole-auth-header/pom.xml
Normal file
67
extensions/guacamole-auth-header/pom.xml
Normal 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>
|
||||
53
extensions/guacamole-auth-header/src/main/assembly/dist.xml
Normal file
53
extensions/guacamole-auth-header/src/main/assembly/dist.xml
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"; }
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "1.6.0",
|
||||
|
||||
"name" : "HTTP Header Authentication Extension",
|
||||
"namespace" : "header",
|
||||
|
||||
"authProviders" : [
|
||||
"org.apache.guacamole.auth.header.HTTPHeaderAuthenticationProvider"
|
||||
]
|
||||
|
||||
}
|
||||
0
extensions/guacamole-auth-jdbc/.ratignore
Normal file
0
extensions/guacamole-auth-jdbc/.ratignore
Normal file
2
extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/.gitignore
vendored
Normal file
2
extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target/
|
||||
*~
|
||||
@@ -0,0 +1 @@
|
||||
src/main/resources/html/*.html
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {}
|
||||
@@ -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
Reference in New Issue
Block a user