mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-990: Merge new guacamole-auth-ban extension to block brute-force auth attempts.
This commit is contained in:
8
doc/licenses/caffeine-2.9.3/README
Normal file
8
doc/licenses/caffeine-2.9.3/README
Normal file
@@ -0,0 +1,8 @@
|
||||
Caffeine (https://github.com/ben-manes/caffeine)
|
||||
------------------------------------------------
|
||||
|
||||
Version: 2.9.3
|
||||
From: 'Ben Manes' (https://github.com/ben-manes)
|
||||
License(s):
|
||||
Apache v2.0
|
||||
|
1
doc/licenses/caffeine-2.9.3/dep-coordinates.txt
Normal file
1
doc/licenses/caffeine-2.9.3/dep-coordinates.txt
Normal file
@@ -0,0 +1 @@
|
||||
com.github.ben-manes.caffeine:caffeine:jar:2.9.3
|
22
doc/licenses/checker-qual-3.19.0/LICENSE.txt
Normal file
22
doc/licenses/checker-qual-3.19.0/LICENSE.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
Checker Framework qualifiers
|
||||
Copyright 2004-present by the Checker Framework developers
|
||||
|
||||
MIT License:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
8
doc/licenses/checker-qual-3.19.0/README
Normal file
8
doc/licenses/checker-qual-3.19.0/README
Normal file
@@ -0,0 +1,8 @@
|
||||
Checker Framework qualifiers (https://checkerframework.org/)
|
||||
------------------------------------------------------------
|
||||
|
||||
Version: 3.19.0
|
||||
From: 'Checker Framework developers' (https://checkerframework.org/)
|
||||
License(s):
|
||||
MIT (bundled/checker-qual-3.19.0/LICENSE.txt)
|
||||
|
1
doc/licenses/checker-qual-3.19.0/dep-coordinates.txt
Normal file
1
doc/licenses/checker-qual-3.19.0/dep-coordinates.txt
Normal file
@@ -0,0 +1 @@
|
||||
org.checkerframework:checker-qual:jar:3.19.0
|
8
doc/licenses/error-prone-2.10.0/README
Normal file
8
doc/licenses/error-prone-2.10.0/README
Normal file
@@ -0,0 +1,8 @@
|
||||
Error Prone (https://errorprone.info/)
|
||||
--------------------------------------
|
||||
|
||||
Version: 2.10.0
|
||||
From: 'Google Inc.' (http://www.google.com/)
|
||||
License(s):
|
||||
Apache v2.0
|
||||
|
1
doc/licenses/error-prone-2.10.0/dep-coordinates.txt
Normal file
1
doc/licenses/error-prone-2.10.0/dep-coordinates.txt
Normal file
@@ -0,0 +1 @@
|
||||
com.google.errorprone:error_prone_annotations:jar:2.10.0
|
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.4.0</version>
|
||||
<name>guacamole-auth-ban</name>
|
||||
<url>http://guacamole.apache.org/</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>extensions</artifactId>
|
||||
<version>1.4.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.4.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,82 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* Listener implementation which automatically tracks authentication failures
|
||||
* such that further authentication attempts may be automatically blocked by
|
||||
* {@link BanningAuthenticationProvider} if they match configured criteria.
|
||||
*/
|
||||
public class BanningAuthenticationListener implements Listener {
|
||||
|
||||
/**
|
||||
* Shared tracker of addresses that have repeatedly failed authentication.
|
||||
*/
|
||||
private static AuthenticationFailureTracker tracker;
|
||||
|
||||
/**
|
||||
* Assigns the shared tracker instance used by both the {@link BanningAuthenticationProvider}
|
||||
* and this listener. This function MUST be invoked with the tracker
|
||||
* created for BanningAuthenticationProvider as soon as possible (during
|
||||
* construction of BanningAuthenticationProvider), or processing of
|
||||
* received events will fail internally.
|
||||
*
|
||||
* @param tracker
|
||||
* The tracker instance to use for received authentication events.
|
||||
*/
|
||||
public static void setAuthenticationFailureTracker(AuthenticationFailureTracker tracker) {
|
||||
BanningAuthenticationListener.tracker = tracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(Object event) throws GuacamoleException {
|
||||
|
||||
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());
|
||||
|
||||
}
|
||||
|
||||
else if (event instanceof AuthenticationSuccessEvent) {
|
||||
AuthenticationSuccessEvent success = (AuthenticationSuccessEvent) event;
|
||||
tracker.notifyAuthenticationSuccess(success.getCredentials());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.InMemoryAuthenticationFailureTracker;
|
||||
import org.apache.guacamole.auth.ban.status.AuthenticationFailureTracker;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
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.AbstractAuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.LongGuacamoleProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* AuthenticationProvider implementation that blocks further authentication
|
||||
* attempts that are related to past authentication failures flagged by
|
||||
* {@link BanningAuthenticationListener}.
|
||||
*/
|
||||
public class BanningAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(BanningAuthenticationProvider.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;
|
||||
|
||||
/**
|
||||
* Shared tracker of addresses that have repeatedly failed authentication.
|
||||
*/
|
||||
private final AuthenticationFailureTracker tracker;
|
||||
|
||||
/**
|
||||
* Creates a new BanningAuthenticationProvider 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 BanningAuthenticationProvider() 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);
|
||||
}
|
||||
|
||||
BanningAuthenticationListener.setAuthenticationFailureTracker(tracker);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "ban";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException {
|
||||
tracker.notifyAuthenticationRequestReceived(credentials);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext getUserContext(AuthenticatedUser authenticatedUser) throws GuacamoleException {
|
||||
tracker.notifyAuthenticationRequestReceived(authenticatedUser.getCredentials());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -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,231 @@
|
||||
/*
|
||||
* 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 javax.servlet.http.HttpServletRequest;
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given Credentials do not contain any specific
|
||||
* authentication parameters, including HTTP parameters. An authentication
|
||||
* request that contains no parameters whatsoever will tend to be the
|
||||
* first, anonymous, credential-less authentication attempt that results in
|
||||
* the initial login screen rendering.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials object to test.
|
||||
*
|
||||
* @return
|
||||
* true if the given Credentials contain no authentication parameters
|
||||
* whatsoever, false otherwise.
|
||||
*/
|
||||
private boolean isEmpty(Credentials credentials) {
|
||||
|
||||
// An authentication request that contains an explicit username or
|
||||
// password (even if blank) is non-empty, regardless of how the values
|
||||
// were passed
|
||||
if (credentials.getUsername() != null || credentials.getPassword() != null)
|
||||
return false;
|
||||
|
||||
// All further tests depend on HTTP request details
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
if (request == null)
|
||||
return true;
|
||||
|
||||
// An authentication request is non-empty if it contains any HTTP
|
||||
// parameters at all or contains an authentication token
|
||||
return !request.getParameterNames().hasMoreElements()
|
||||
&& request.getHeader("Guacamole-Token") == null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (isEmpty(credentials))
|
||||
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,20 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "1.4.0",
|
||||
|
||||
"name" : "Brute-force Authentication Detection/Prevention",
|
||||
"namespace" : "ban",
|
||||
|
||||
"authProviders" : [
|
||||
"org.apache.guacamole.auth.ban.BanningAuthenticationProvider"
|
||||
],
|
||||
|
||||
"listeners" : [
|
||||
"org.apache.guacamole.auth.ban.BanningAuthenticationListener"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/en.json"
|
||||
]
|
||||
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"LOGIN": {
|
||||
"ERROR_TOO_MANY_ATTEMPTS" : "Too many failed authentication attempts. Please try again later."
|
||||
}
|
||||
}
|
@@ -40,6 +40,7 @@
|
||||
<modules>
|
||||
|
||||
<!-- Authentication extensions -->
|
||||
<module>guacamole-auth-ban</module>
|
||||
<module>guacamole-auth-duo</module>
|
||||
<module>guacamole-auth-header</module>
|
||||
<module>guacamole-auth-jdbc</module>
|
||||
|
@@ -198,3 +198,13 @@ if [ -f extensions/guacamole-auth-json/target/guacamole-auth-json*.jar ]; then
|
||||
mkdir -p "$DESTINATION/json"
|
||||
cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json"
|
||||
fi
|
||||
|
||||
#
|
||||
# Copy automatic brute-force banning auth extension if it was built
|
||||
#
|
||||
|
||||
if [ -f extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar ]; then
|
||||
mkdir -p "$DESTINATION/ban"
|
||||
cp extensions/guacamole-auth-ban/target/guacamole-auth-ban*.jar "$DESTINATION/ban"
|
||||
fi
|
||||
|
||||
|
@@ -1160,6 +1160,18 @@ if [ -n "$API_SESSION_TIMEOUT" ]; then
|
||||
associate_apisessiontimeout
|
||||
fi
|
||||
|
||||
# Apply any overrides for default address ban behavior
|
||||
set_optional_property "ban-address-duration" "$BAN_ADDRESS_DURATION"
|
||||
set_optional_property "ban-max-addresses" "$BAN_MAX_ADDRESSES"
|
||||
set_optional_property "ban-max-invalid-attempts" "$BAN_MAX_INVALID_ATTEMPTS"
|
||||
|
||||
# Ensure guacamole-auth-ban always loads before other extensions unless
|
||||
# explicitly overridden via naming or EXTENSION_PRIORITY (allowing other
|
||||
# extensions to attempt authentication before guacamole-auth-ban has a chance
|
||||
# to enforce any bans could allow credentials to continue to be guessed even
|
||||
# after the address has been blocked via timing attacks)
|
||||
ln -s /opt/guacamole/ban/guacamole-auth-*.jar "$GUACAMOLE_EXT/_guacamole-auth-ban.jar"
|
||||
|
||||
# Set logback level if specified
|
||||
if [ -n "$LOGBACK_LEVEL" ]; then
|
||||
unzip -o -j /opt/guacamole/guacamole.war WEB-INF/classes/logback.xml -d $GUACAMOLE_HOME
|
||||
|
@@ -19,28 +19,91 @@
|
||||
|
||||
package org.apache.guacamole.net.event;
|
||||
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.event.listener.Listener;
|
||||
|
||||
/**
|
||||
* An event which is triggered whenever a user's credentials fail to be
|
||||
* authenticated. The credentials that failed to be authenticated are included
|
||||
* within this event, and can be retrieved using getCredentials().
|
||||
*/
|
||||
public class AuthenticationFailureEvent implements CredentialEvent {
|
||||
public class AuthenticationFailureEvent implements AuthenticationProviderEvent,
|
||||
CredentialEvent, FailureEvent {
|
||||
|
||||
/**
|
||||
* The credentials which failed authentication.
|
||||
*/
|
||||
private Credentials credentials;
|
||||
private final Credentials credentials;
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationFailureEvent which represents the failure
|
||||
* to authenticate the given credentials.
|
||||
* The AuthenticationProvider that encountered the failure. This may be
|
||||
* null if the AuthenticationProvider is not known, such as if the failure
|
||||
* is caused by every AuthenticationProvider passively refusing to
|
||||
* authenticate the user but without explicitly rejecting the user
|
||||
* (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}),
|
||||
* or if the failure is external to any installed AuthenticationProvider
|
||||
* (such as within a {@link Listener}.
|
||||
*/
|
||||
private final AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* The Throwable that was thrown resulting in the failure, if any. This
|
||||
* may be null if authentication failed without a known error, such as if
|
||||
* the failure is caused by every AuthenticationProvider passively refusing
|
||||
* to authenticate the user but without explicitly rejecting the user
|
||||
* (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}).
|
||||
*/
|
||||
private final Throwable failure;
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationFailureEvent which represents a failure
|
||||
* to authenticate the given credentials where there is no specific
|
||||
* AuthenticationProvider nor Throwable associated with the failure.
|
||||
*
|
||||
* @param credentials The credentials which failed authentication.
|
||||
* @param credentials
|
||||
* The credentials which failed authentication.
|
||||
*/
|
||||
public AuthenticationFailureEvent(Credentials credentials) {
|
||||
this(credentials, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationFailureEvent which represents a failure
|
||||
* to authenticate the given credentials where there is no specific
|
||||
* AuthenticationProvider causing the failure.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials which failed authentication.
|
||||
*
|
||||
* @param failure
|
||||
* The Throwable that was thrown resulting in the failure, or null if
|
||||
* there is no such Throwable.
|
||||
*/
|
||||
public AuthenticationFailureEvent(Credentials credentials, Throwable failure) {
|
||||
this(credentials, null, failure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationFailureEvent which represents a failure
|
||||
* to authenticate the given credentials.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials which failed authentication.
|
||||
*
|
||||
* @param authProvider
|
||||
* The AuthenticationProvider that caused the failure, or null if there
|
||||
* is no such AuthenticationProvider.
|
||||
*
|
||||
* @param failure
|
||||
* The Throwable that was thrown resulting in the failure, or null if
|
||||
* there is no such Throwable.
|
||||
*/
|
||||
public AuthenticationFailureEvent(Credentials credentials,
|
||||
AuthenticationProvider authProvider, Throwable failure) {
|
||||
this.credentials = credentials;
|
||||
this.authProvider = authProvider;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,4 +111,35 @@ public class AuthenticationFailureEvent implements CredentialEvent {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>NOTE: In the case of an authentication failure, cases where this may
|
||||
* be null include if authentication failed without a definite single
|
||||
* AuthenticationProvider causing that failure, such as if the failure is
|
||||
* caused by every AuthenticationProvider passively refusing to
|
||||
* authenticate the user but without explicitly rejecting the user
|
||||
* (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}),
|
||||
* or if the failure is external to any installed AuthenticationProvider
|
||||
* (such as within a {@link Listener}.
|
||||
*/
|
||||
@Override
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>NOTE: In the case of an authentication failure, cases where this may
|
||||
* be null include if authentication failed without a known error, such as
|
||||
* if the failure is caused by every AuthenticationProvider passively
|
||||
* refusing to authenticate the user but without explicitly rejecting the
|
||||
* user (returning null for calls to {@link AuthenticationProvider#authenticateUser(org.apache.guacamole.net.auth.Credentials)}).
|
||||
*/
|
||||
@Override
|
||||
public Throwable getFailure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.net.event;
|
||||
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
|
||||
/**
|
||||
* An event which may be dispatched due to a specific AuthenticationProvider.
|
||||
*/
|
||||
public interface AuthenticationProviderEvent {
|
||||
|
||||
/**
|
||||
* Returns the AuthenticationProvider that resulted in the event, if any.
|
||||
* If the event occurred without any definite causing
|
||||
* AuthenticationProvider, this may be null.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticationProvider that resulted in the event, or null if no
|
||||
* such AuthenticationProvider is known.
|
||||
*/
|
||||
AuthenticationProvider getAuthenticationProvider();
|
||||
|
||||
}
|
@@ -20,6 +20,7 @@
|
||||
package org.apache.guacamole.net.event;
|
||||
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,8 @@ import org.apache.guacamole.net.auth.Credentials;
|
||||
* is effectively <em>vetoed</em> and will be subsequently processed as though the
|
||||
* authentication failed.
|
||||
*/
|
||||
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
||||
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent,
|
||||
AuthenticationProviderEvent {
|
||||
|
||||
/**
|
||||
* The AuthenticatedUser identifying the user that successfully
|
||||
@@ -60,7 +62,12 @@ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
||||
|
||||
@Override
|
||||
public Credentials getCredentials() {
|
||||
return authenticatedUser.getCredentials();
|
||||
return getAuthenticatedUser().getCredentials();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return getAuthenticatedUser().getAuthenticationProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.net.event;
|
||||
|
||||
/**
|
||||
* An event which represents failure of an operation, where that failure may
|
||||
* be associated with a particular Throwable.
|
||||
*/
|
||||
public interface FailureEvent {
|
||||
|
||||
/**
|
||||
* Returns the Throwable that represents the failure that occurred, if any.
|
||||
* If the failure was recognized but without a definite known error, this
|
||||
* may be null.
|
||||
*
|
||||
* @return
|
||||
* The Throwable that represents the failure that occurred, or null if
|
||||
* no such Throwable is known.
|
||||
*/
|
||||
Throwable getFailure();
|
||||
|
||||
}
|
@@ -198,12 +198,16 @@ angular.module('auth').factory('authenticationService', ['$injector',
|
||||
['catch'](requestService.createErrorCallback(function authenticationFailed(error) {
|
||||
|
||||
// Request credentials if provided credentials were invalid
|
||||
if (error.type === Error.Type.INVALID_CREDENTIALS)
|
||||
if (error.type === Error.Type.INVALID_CREDENTIALS) {
|
||||
$rootScope.$broadcast('guacInvalidCredentials', parameters, error);
|
||||
clearAuthenticationResult();
|
||||
}
|
||||
|
||||
// Request more credentials if provided credentials were not enough
|
||||
else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS)
|
||||
else if (error.type === Error.Type.INSUFFICIENT_CREDENTIALS) {
|
||||
$rootScope.$broadcast('guacInsufficientCredentials', parameters, error);
|
||||
clearAuthenticationResult();
|
||||
}
|
||||
|
||||
// Abort rendering of page if an internal error occurs
|
||||
else if (error.type === Error.Type.INTERNAL_ERROR)
|
||||
|
@@ -34,7 +34,6 @@ 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.GuacamoleCredentialsException;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
@@ -169,14 +168,15 @@ public class AuthenticationService {
|
||||
* The AuthenticatedUser given by the highest-priority
|
||||
* AuthenticationProvider for which the given credentials are valid.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If the given credentials are not valid for any
|
||||
* AuthenticationProvider, or if an error occurs while authenticating
|
||||
* the user.
|
||||
*/
|
||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
AuthenticationProvider failedAuthProvider = null;
|
||||
GuacamoleCredentialsException authFailure = null;
|
||||
|
||||
// Attempt authentication against each AuthenticationProvider
|
||||
@@ -191,27 +191,29 @@ public class AuthenticationService {
|
||||
|
||||
// Insufficient credentials should take precedence
|
||||
catch (GuacamoleInsufficientCredentialsException e) {
|
||||
if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException)
|
||||
if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException) {
|
||||
failedAuthProvider = authProvider;
|
||||
authFailure = e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Catch other credentials exceptions and assign the first one
|
||||
catch (GuacamoleCredentialsException e) {
|
||||
if (authFailure == null)
|
||||
if (authFailure == null) {
|
||||
failedAuthProvider = authProvider;
|
||||
authFailure = e;
|
||||
}
|
||||
}
|
||||
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication was aborted.", authProvider, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If a specific failure occured, rethrow that
|
||||
if (authFailure != null)
|
||||
throw authFailure;
|
||||
|
||||
// Otherwise, request standard username/password
|
||||
throw new GuacamoleInvalidCredentialsException(
|
||||
"Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD
|
||||
);
|
||||
throw new GuacamoleAuthenticationProcessException("User authentication "
|
||||
+ "failed.", failedAuthProvider, authFailure);
|
||||
|
||||
}
|
||||
|
||||
@@ -230,51 +232,29 @@ public class AuthenticationService {
|
||||
* A AuthenticatedUser which may have been updated due to re-
|
||||
* authentication.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If an error prevents the user from being re-authenticated.
|
||||
*/
|
||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Get original AuthenticationProvider
|
||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||
|
||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
if (authenticatedUser == null)
|
||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||
try {
|
||||
|
||||
return authenticatedUser;
|
||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
||||
if (authenticatedUser == null)
|
||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
||||
|
||||
}
|
||||
return authenticatedUser;
|
||||
|
||||
/**
|
||||
* Notify all bound listeners that a successful authentication
|
||||
* has occurred.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The user that was successfully authenticated.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If thrown by a listener.
|
||||
*/
|
||||
private void fireAuthenticationSuccessEvent(AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser));
|
||||
}
|
||||
}
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User re-authentication failed.", authProvider, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all bound listeners that an authentication attempt has failed.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials that failed to authenticate.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If thrown by a listener.
|
||||
*/
|
||||
private void fireAuthenticationFailedEvent(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
listenerService.handleEvent(new AuthenticationFailureEvent(credentials));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,61 +272,23 @@ public class AuthenticationService {
|
||||
* The AuthenticatedUser associated with the given session and
|
||||
* credentials.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If an error occurs while authenticating or re-authenticating the
|
||||
* user.
|
||||
*/
|
||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
try {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null) {
|
||||
AuthenticatedUser updatedUser = updateAuthenticatedUser(
|
||||
existingSession.getAuthenticatedUser(), credentials);
|
||||
fireAuthenticationSuccessEvent(updatedUser);
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
// Otherwise, attempt authentication as a new user
|
||||
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||
fireAuthenticationSuccessEvent(authenticatedUser);
|
||||
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(),
|
||||
getLoggableAddress(credentials.getRequest()));
|
||||
|
||||
return authenticatedUser;
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Re-authenticate user if session exists
|
||||
if (existingSession != null) {
|
||||
AuthenticatedUser updatedUser = updateAuthenticatedUser(
|
||||
existingSession.getAuthenticatedUser(), credentials);
|
||||
return updatedUser;
|
||||
}
|
||||
|
||||
// Log and rethrow any authentication errors
|
||||
catch (GuacamoleException e) {
|
||||
|
||||
fireAuthenticationFailedEvent(credentials);
|
||||
|
||||
// Get request and username for sake of logging
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
String username = credentials.getUsername();
|
||||
|
||||
// Log authentication failures with associated usernames
|
||||
if (username != null) {
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||
getLoggableAddress(request), username);
|
||||
}
|
||||
|
||||
// Log anonymous authentication failures
|
||||
else if (logger.isDebugEnabled())
|
||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||
getLoggableAddress(request));
|
||||
|
||||
// Rethrow exception
|
||||
throw e;
|
||||
|
||||
}
|
||||
// Otherwise, attempt authentication as a new user
|
||||
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||
return authenticatedUser;
|
||||
|
||||
}
|
||||
|
||||
@@ -371,15 +313,14 @@ public class AuthenticationService {
|
||||
* A List of all UserContexts associated with the given
|
||||
* AuthenticatedUser.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If an error occurs while creating or updating any UserContext.
|
||||
*/
|
||||
private List<DecoratedUserContext> getUserContexts(GuacamoleSession existingSession,
|
||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
List<DecoratedUserContext> userContexts =
|
||||
new ArrayList<DecoratedUserContext>(authProviders.size());
|
||||
List<DecoratedUserContext> userContexts = new ArrayList<>(authProviders.size());
|
||||
|
||||
// If UserContexts already exist, update them and add to the list
|
||||
if (existingSession != null) {
|
||||
@@ -392,7 +333,15 @@ public class AuthenticationService {
|
||||
|
||||
// Update existing UserContext
|
||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
||||
UserContext updatedUserContext = authProvider.updateUserContext(oldUserContext, authenticatedUser, credentials);
|
||||
UserContext updatedUserContext;
|
||||
try {
|
||||
updatedUserContext = authProvider.updateUserContext(oldUserContext, authenticatedUser, credentials);
|
||||
}
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication aborted during UserContext update.",
|
||||
authProvider, e);
|
||||
}
|
||||
|
||||
// Add to available data, if successful
|
||||
if (updatedUserContext != null)
|
||||
@@ -415,7 +364,15 @@ public class AuthenticationService {
|
||||
for (AuthenticationProvider authProvider : authProviders) {
|
||||
|
||||
// Generate new UserContext
|
||||
UserContext userContext = authProvider.getUserContext(authenticatedUser);
|
||||
UserContext userContext;
|
||||
try {
|
||||
userContext = authProvider.getUserContext(authenticatedUser);
|
||||
}
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication aborted during initial "
|
||||
+ "UserContext creation.", authProvider, e);
|
||||
}
|
||||
|
||||
// Add to available data, if successful
|
||||
if (userContext != null)
|
||||
@@ -453,7 +410,7 @@ public class AuthenticationService {
|
||||
* If the authentication or re-authentication attempt fails.
|
||||
*/
|
||||
public String authenticate(Credentials credentials, String token)
|
||||
throws GuacamoleException {
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull existing session if token provided
|
||||
GuacamoleSession existingSession;
|
||||
@@ -462,25 +419,72 @@ public class AuthenticationService {
|
||||
else
|
||||
existingSession = null;
|
||||
|
||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||
List<DecoratedUserContext> userContexts = getUserContexts(existingSession, authenticatedUser, credentials);
|
||||
|
||||
// Update existing session, if it exists
|
||||
AuthenticatedUser authenticatedUser;
|
||||
String authToken;
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
|
||||
try {
|
||||
|
||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
||||
authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
||||
List<DecoratedUserContext> userContexts = getUserContexts(existingSession, authenticatedUser, credentials);
|
||||
|
||||
// Update existing session, if it exists
|
||||
if (existingSession != null) {
|
||||
authToken = token;
|
||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
||||
existingSession.setUserContexts(userContexts);
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
}
|
||||
|
||||
// Report authentication success
|
||||
try {
|
||||
listenerService.handleEvent(new AuthenticationSuccessEvent(authenticatedUser));
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication aborted by event listener.", null, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If no existing session, generate a new token/session pair
|
||||
else {
|
||||
authToken = authTokenGenerator.getToken();
|
||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
||||
// Log and rethrow any authentication errors
|
||||
catch (GuacamoleAuthenticationProcessException e) {
|
||||
|
||||
// Get request and username for sake of logging
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
String username = credentials.getUsername();
|
||||
|
||||
listenerService.handleEvent(new AuthenticationFailureEvent(credentials,
|
||||
e.getAuthenticationProvider(), e.getCause()));
|
||||
|
||||
// Log authentication failures with associated usernames
|
||||
if (username != null) {
|
||||
if (logger.isWarnEnabled())
|
||||
logger.warn("Authentication attempt from {} for user \"{}\" failed.",
|
||||
getLoggableAddress(request), username);
|
||||
}
|
||||
|
||||
// Log anonymous authentication failures
|
||||
else if (logger.isDebugEnabled())
|
||||
logger.debug("Anonymous authentication attempt from {} failed.",
|
||||
getLoggableAddress(request));
|
||||
|
||||
// Rethrow exception
|
||||
throw e.getCauseAsGuacamoleException();
|
||||
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled())
|
||||
logger.info("User \"{}\" successfully authenticated from {}.",
|
||||
authenticatedUser.getIdentifier(),
|
||||
getLoggableAddress(credentials.getRequest()));
|
||||
|
||||
return authToken;
|
||||
|
||||
}
|
||||
|
@@ -76,21 +76,29 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* given AuthenticationProvider, or the original UserContext if the
|
||||
* given AuthenticationProvider originated the UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If the given AuthenticationProvider fails while decorating the
|
||||
* UserContext.
|
||||
*/
|
||||
private static UserContext decorate(AuthenticationProvider authProvider,
|
||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Skip the AuthenticationProvider which produced the UserContext
|
||||
// being decorated
|
||||
if (authProvider != userContext.getAuthenticationProvider()) {
|
||||
|
||||
// Apply layer of wrapping around UserContext
|
||||
UserContext decorated = authProvider.decorate(userContext,
|
||||
authenticatedUser, credentials);
|
||||
UserContext decorated;
|
||||
try {
|
||||
decorated = authProvider.decorate(userContext,
|
||||
authenticatedUser, credentials);
|
||||
}
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication aborted by decorating UserContext.",
|
||||
authProvider, e);
|
||||
}
|
||||
|
||||
// Do not allow misbehaving extensions to wipe out the
|
||||
// UserContext entirely
|
||||
@@ -130,13 +138,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* given AuthenticationProvider, or the original UserContext if the
|
||||
* given AuthenticationProvider originated the UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If the given AuthenticationProvider fails while decorating the
|
||||
* UserContext.
|
||||
*/
|
||||
private static UserContext redecorate(DecoratedUserContext decorated,
|
||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
AuthenticationProvider authProvider = decorated.getDecoratingAuthenticationProvider();
|
||||
|
||||
@@ -145,8 +153,16 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
if (authProvider != userContext.getAuthenticationProvider()) {
|
||||
|
||||
// Apply next layer of wrapping around UserContext
|
||||
UserContext redecorated = authProvider.redecorate(decorated,
|
||||
userContext, authenticatedUser, credentials);
|
||||
UserContext redecorated;
|
||||
try {
|
||||
redecorated = authProvider.redecorate(decorated.getDelegateUserContext(),
|
||||
userContext, authenticatedUser, credentials);
|
||||
}
|
||||
catch (GuacamoleException | RuntimeException | Error e) {
|
||||
throw new GuacamoleAuthenticationProcessException("User "
|
||||
+ "authentication aborted by redecorating UserContext.",
|
||||
authProvider, e);
|
||||
}
|
||||
|
||||
// Do not allow misbehaving extensions to wipe out the
|
||||
// UserContext entirely
|
||||
@@ -181,13 +197,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* The credentials associated with the request which produced the given
|
||||
* UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any of the given AuthenticationProviders fails while decorating
|
||||
* the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext(AuthenticationProvider authProvider,
|
||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
||||
super(decorate(authProvider, userContext, authenticatedUser, credentials));
|
||||
@@ -221,13 +237,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* The credentials associated with the request which produced the given
|
||||
* UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any of the given AuthenticationProviders fails while decorating
|
||||
* the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext(AuthenticationProvider authProvider,
|
||||
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
||||
super(decorate(authProvider, userContext, authenticatedUser, credentials));
|
||||
@@ -261,13 +277,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* The credentials associated with the request which produced the given
|
||||
* UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any of the given AuthenticationProviders fails while decorating
|
||||
* the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext(DecoratedUserContext decorated,
|
||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
||||
super(redecorate(decorated, userContext, authenticatedUser, credentials));
|
||||
@@ -303,13 +319,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
||||
* The credentials associated with the request which produced the given
|
||||
* UserContext.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any of the given AuthenticationProviders fails while decorating
|
||||
* the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext(DecoratedUserContext decorated,
|
||||
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
||||
super(redecorate(decorated, userContext, authenticatedUser, credentials));
|
||||
|
@@ -23,7 +23,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
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;
|
||||
@@ -65,12 +64,12 @@ public class DecorationService {
|
||||
* A new DecoratedUserContext which has been decorated by all
|
||||
* AuthenticationProviders.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any AuthenticationProvider fails while decorating the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext decorate(UserContext userContext,
|
||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// Get first AuthenticationProvider in list
|
||||
Iterator<AuthenticationProvider> current = authProviders.iterator();
|
||||
@@ -119,12 +118,12 @@ public class DecorationService {
|
||||
* A new DecoratedUserContext which has been decorated by all
|
||||
* AuthenticationProviders.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* @throws GuacamoleAuthenticationProcessException
|
||||
* If any AuthenticationProvider fails while decorating the UserContext.
|
||||
*/
|
||||
public DecoratedUserContext redecorate(DecoratedUserContext decorated,
|
||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||
|
||||
// If the given DecoratedUserContext contains further decorated layers,
|
||||
// redecorate those first
|
||||
|
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.rest.auth;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
|
||||
/**
|
||||
* An exception that occurs during Guacamole's authentication and authorization
|
||||
* process, possibly associated with a specific AuthenticationProvider.
|
||||
*/
|
||||
public class GuacamoleAuthenticationProcessException extends GuacamoleException {
|
||||
|
||||
/**
|
||||
* Internal identifier unique to this version of
|
||||
* GuacamoleAuthenticationProcessException, as required by Java's
|
||||
* {@link Serializable} interface.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The AuthenticationProvider that caused the failure, or null if there is
|
||||
* no such specific AuthenticationProvider involved in this failure.
|
||||
*/
|
||||
private final transient AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* A GuacamoleException representation of the failure that occurred. If
|
||||
* the cause provided when this GuacamoleAuthenticationProcessException
|
||||
* was created was a GuacamoleException, this will just be that exception.
|
||||
* Otherwise, this will be a GuacamoleServerException wrapping the cause
|
||||
* or a generic GuacamoleInvalidCredentialsException requesting a
|
||||
* username/password if there is no specific cause at all.
|
||||
*/
|
||||
private final GuacamoleException guacCause;
|
||||
|
||||
/**
|
||||
* Converts the given Throwable to a GuacamoleException representing the
|
||||
* failure that occurred. If the Throwable already is a GuacamoleException,
|
||||
* this will just be that Throwable. For all other cases, a new
|
||||
* GuacamoleException will be created that best represents the provided
|
||||
* failure. If no failure is provided at all, a generic
|
||||
* GuacamoleInvalidCredentialsException requesting a username/password is
|
||||
* created.
|
||||
*
|
||||
* @param message
|
||||
* A human-readable message describing the failure that occurred.
|
||||
*
|
||||
* @param cause
|
||||
* The Throwable cause of the failure that occurred, if any, or null if
|
||||
* the cause is not known to be a specific Throwable.
|
||||
*
|
||||
* @return
|
||||
* A GuacamoleException representation of the message and cause
|
||||
* provided.
|
||||
*/
|
||||
private static GuacamoleException toGuacamoleException(String message,
|
||||
Throwable cause) {
|
||||
|
||||
// Create generic invalid username/password exception if we have no
|
||||
// specific cause
|
||||
if (cause == null)
|
||||
return new GuacamoleInvalidCredentialsException(
|
||||
"Permission Denied.",
|
||||
CredentialsInfo.USERNAME_PASSWORD
|
||||
);
|
||||
|
||||
// If the specific cause is already a GuacamoleException, there's
|
||||
// nothing for us to do here
|
||||
if (cause instanceof GuacamoleException)
|
||||
return (GuacamoleException) cause;
|
||||
|
||||
// Wrap all other Throwables as generic internal errors
|
||||
return new GuacamoleServerException(message, cause);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GuacamoleAuthenticationProcessException with the given
|
||||
* message, associated AuthenticationProvider, and cause.
|
||||
*
|
||||
* @param message
|
||||
* A human readable description of the exception that occurred.
|
||||
*
|
||||
* @param authProvider
|
||||
* The AuthenticationProvider that caused the failure, or null if there
|
||||
* is no such specific AuthenticationProvider involved in this failure.
|
||||
*
|
||||
* @param cause
|
||||
* The cause of this exception, or null if the cause is unknown or
|
||||
* there is no such cause.
|
||||
*/
|
||||
public GuacamoleAuthenticationProcessException(String message,
|
||||
AuthenticationProvider authProvider, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.authProvider = authProvider;
|
||||
this.guacCause = toGuacamoleException(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthenticationProvider that caused the failure, if any. If
|
||||
* there is no specific AuthenticationProvider involved in this failure,
|
||||
* including if the failure is due to multiple AuthenticationProviders,
|
||||
* this will be null.
|
||||
*
|
||||
* @return
|
||||
* The AuthenticationProvider that caused the failure, or null if there
|
||||
* is no such specific AuthenticationProvider involved in this failure.
|
||||
*/
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GuacamoleException that represents the user-facing cause of
|
||||
* this exception. A GuacamoleException will be returned by this function
|
||||
* in all cases, including if no specific cause was given.
|
||||
*
|
||||
* @return
|
||||
* A GuacamoleException that represents the user-facing cause of this
|
||||
* exception.
|
||||
*/
|
||||
public GuacamoleException getCauseAsGuacamoleException() {
|
||||
return guacCause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GuacamoleStatus getStatus() {
|
||||
return getCauseAsGuacamoleException().getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHttpStatusCode() {
|
||||
return getCauseAsGuacamoleException().getHttpStatusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWebSocketCode() {
|
||||
return getCauseAsGuacamoleException().getWebSocketCode();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user