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>
|
<modules>
|
||||||
|
|
||||||
<!-- Authentication extensions -->
|
<!-- Authentication extensions -->
|
||||||
|
<module>guacamole-auth-ban</module>
|
||||||
<module>guacamole-auth-duo</module>
|
<module>guacamole-auth-duo</module>
|
||||||
<module>guacamole-auth-header</module>
|
<module>guacamole-auth-header</module>
|
||||||
<module>guacamole-auth-jdbc</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"
|
mkdir -p "$DESTINATION/json"
|
||||||
cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json"
|
cp extensions/guacamole-auth-json/target/guacamole-auth-json*.jar "$DESTINATION/json"
|
||||||
fi
|
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
|
associate_apisessiontimeout
|
||||||
fi
|
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
|
# Set logback level if specified
|
||||||
if [ -n "$LOGBACK_LEVEL" ]; then
|
if [ -n "$LOGBACK_LEVEL" ]; then
|
||||||
unzip -o -j /opt/guacamole/guacamole.war WEB-INF/classes/logback.xml -d $GUACAMOLE_HOME
|
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;
|
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.auth.Credentials;
|
||||||
|
import org.apache.guacamole.net.event.listener.Listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event which is triggered whenever a user's credentials fail to be
|
* An event which is triggered whenever a user's credentials fail to be
|
||||||
* authenticated. The credentials that failed to be authenticated are included
|
* authenticated. The credentials that failed to be authenticated are included
|
||||||
* within this event, and can be retrieved using getCredentials().
|
* 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.
|
* The credentials which failed authentication.
|
||||||
*/
|
*/
|
||||||
private Credentials credentials;
|
private final Credentials credentials;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new AuthenticationFailureEvent which represents the failure
|
* The AuthenticationProvider that encountered the failure. This may be
|
||||||
* to authenticate the given credentials.
|
* 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) {
|
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.credentials = credentials;
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
this.failure = failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,4 +111,35 @@ public class AuthenticationFailureEvent implements CredentialEvent {
|
|||||||
return credentials;
|
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;
|
package org.apache.guacamole.net.event;
|
||||||
|
|
||||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
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.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
|
* is effectively <em>vetoed</em> and will be subsequently processed as though the
|
||||||
* authentication failed.
|
* authentication failed.
|
||||||
*/
|
*/
|
||||||
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent,
|
||||||
|
AuthenticationProviderEvent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The AuthenticatedUser identifying the user that successfully
|
* The AuthenticatedUser identifying the user that successfully
|
||||||
@@ -60,7 +62,12 @@ public class AuthenticationSuccessEvent implements UserEvent, CredentialEvent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Credentials getCredentials() {
|
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) {
|
['catch'](requestService.createErrorCallback(function authenticationFailed(error) {
|
||||||
|
|
||||||
// Request credentials if provided credentials were invalid
|
// 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);
|
$rootScope.$broadcast('guacInvalidCredentials', parameters, error);
|
||||||
|
clearAuthenticationResult();
|
||||||
|
}
|
||||||
|
|
||||||
// Request more credentials if provided credentials were not enough
|
// 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);
|
$rootScope.$broadcast('guacInsufficientCredentials', parameters, error);
|
||||||
|
clearAuthenticationResult();
|
||||||
|
}
|
||||||
|
|
||||||
// Abort rendering of page if an internal error occurs
|
// Abort rendering of page if an internal error occurs
|
||||||
else if (error.type === Error.Type.INTERNAL_ERROR)
|
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.AuthenticationProvider;
|
||||||
import org.apache.guacamole.net.auth.Credentials;
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
import org.apache.guacamole.net.auth.UserContext;
|
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.GuacamoleCredentialsException;
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
|
||||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||||
@@ -169,14 +168,15 @@ public class AuthenticationService {
|
|||||||
* The AuthenticatedUser given by the highest-priority
|
* The AuthenticatedUser given by the highest-priority
|
||||||
* AuthenticationProvider for which the given credentials are valid.
|
* AuthenticationProvider for which the given credentials are valid.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If the given credentials are not valid for any
|
* If the given credentials are not valid for any
|
||||||
* AuthenticationProvider, or if an error occurs while authenticating
|
* AuthenticationProvider, or if an error occurs while authenticating
|
||||||
* the user.
|
* the user.
|
||||||
*/
|
*/
|
||||||
private AuthenticatedUser authenticateUser(Credentials credentials)
|
private AuthenticatedUser authenticateUser(Credentials credentials)
|
||||||
throws GuacamoleException {
|
throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
|
AuthenticationProvider failedAuthProvider = null;
|
||||||
GuacamoleCredentialsException authFailure = null;
|
GuacamoleCredentialsException authFailure = null;
|
||||||
|
|
||||||
// Attempt authentication against each AuthenticationProvider
|
// Attempt authentication against each AuthenticationProvider
|
||||||
@@ -191,27 +191,29 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
// Insufficient credentials should take precedence
|
// Insufficient credentials should take precedence
|
||||||
catch (GuacamoleInsufficientCredentialsException e) {
|
catch (GuacamoleInsufficientCredentialsException e) {
|
||||||
if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException)
|
if (authFailure == null || authFailure instanceof GuacamoleInvalidCredentialsException) {
|
||||||
|
failedAuthProvider = authProvider;
|
||||||
authFailure = e;
|
authFailure = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catch other credentials exceptions and assign the first one
|
// Catch other credentials exceptions and assign the first one
|
||||||
catch (GuacamoleCredentialsException e) {
|
catch (GuacamoleCredentialsException e) {
|
||||||
if (authFailure == null)
|
if (authFailure == null) {
|
||||||
|
failedAuthProvider = authProvider;
|
||||||
authFailure = e;
|
authFailure = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (GuacamoleException | RuntimeException | Error e) {
|
||||||
|
throw new GuacamoleAuthenticationProcessException("User "
|
||||||
|
+ "authentication was aborted.", authProvider, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a specific failure occured, rethrow that
|
throw new GuacamoleAuthenticationProcessException("User authentication "
|
||||||
if (authFailure != null)
|
+ "failed.", failedAuthProvider, authFailure);
|
||||||
throw authFailure;
|
|
||||||
|
|
||||||
// Otherwise, request standard username/password
|
|
||||||
throw new GuacamoleInvalidCredentialsException(
|
|
||||||
"Permission Denied.",
|
|
||||||
CredentialsInfo.USERNAME_PASSWORD
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,51 +232,29 @@ public class AuthenticationService {
|
|||||||
* A AuthenticatedUser which may have been updated due to re-
|
* A AuthenticatedUser which may have been updated due to re-
|
||||||
* authentication.
|
* authentication.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If an error prevents the user from being re-authenticated.
|
* If an error prevents the user from being re-authenticated.
|
||||||
*/
|
*/
|
||||||
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
private AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Get original AuthenticationProvider
|
// Get original AuthenticationProvider
|
||||||
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
AuthenticationProvider authProvider = authenticatedUser.getAuthenticationProvider();
|
||||||
|
|
||||||
// Re-authenticate the AuthenticatedUser against the original AuthenticationProvider only
|
try {
|
||||||
authenticatedUser = authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
|
|
||||||
if (authenticatedUser == null)
|
|
||||||
throw new GuacamoleSecurityException("User re-authentication failed.");
|
|
||||||
|
|
||||||
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
|
catch (GuacamoleException | RuntimeException | Error e) {
|
||||||
* has occurred.
|
throw new GuacamoleAuthenticationProcessException("User re-authentication failed.", authProvider, e);
|
||||||
*
|
}
|
||||||
* @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));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* The AuthenticatedUser associated with the given session and
|
||||||
* credentials.
|
* credentials.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If an error occurs while authenticating or re-authenticating the
|
* If an error occurs while authenticating or re-authenticating the
|
||||||
* user.
|
* user.
|
||||||
*/
|
*/
|
||||||
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
private AuthenticatedUser getAuthenticatedUser(GuacamoleSession existingSession,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
// Re-authenticate user if session exists
|
||||||
|
if (existingSession != null) {
|
||||||
|
AuthenticatedUser updatedUser = updateAuthenticatedUser(
|
||||||
|
existingSession.getAuthenticatedUser(), credentials);
|
||||||
|
return updatedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log and rethrow any authentication errors
|
// Otherwise, attempt authentication as a new user
|
||||||
catch (GuacamoleException e) {
|
AuthenticatedUser authenticatedUser = AuthenticationService.this.authenticateUser(credentials);
|
||||||
|
return authenticatedUser;
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,15 +313,14 @@ public class AuthenticationService {
|
|||||||
* A List of all UserContexts associated with the given
|
* A List of all UserContexts associated with the given
|
||||||
* AuthenticatedUser.
|
* AuthenticatedUser.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If an error occurs while creating or updating any UserContext.
|
* If an error occurs while creating or updating any UserContext.
|
||||||
*/
|
*/
|
||||||
private List<DecoratedUserContext> getUserContexts(GuacamoleSession existingSession,
|
private List<DecoratedUserContext> getUserContexts(GuacamoleSession existingSession,
|
||||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||||
throws GuacamoleException {
|
throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
List<DecoratedUserContext> userContexts =
|
List<DecoratedUserContext> userContexts = new ArrayList<>(authProviders.size());
|
||||||
new ArrayList<DecoratedUserContext>(authProviders.size());
|
|
||||||
|
|
||||||
// If UserContexts already exist, update them and add to the list
|
// If UserContexts already exist, update them and add to the list
|
||||||
if (existingSession != null) {
|
if (existingSession != null) {
|
||||||
@@ -392,7 +333,15 @@ public class AuthenticationService {
|
|||||||
|
|
||||||
// Update existing UserContext
|
// Update existing UserContext
|
||||||
AuthenticationProvider authProvider = oldUserContext.getAuthenticationProvider();
|
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
|
// Add to available data, if successful
|
||||||
if (updatedUserContext != null)
|
if (updatedUserContext != null)
|
||||||
@@ -415,7 +364,15 @@ public class AuthenticationService {
|
|||||||
for (AuthenticationProvider authProvider : authProviders) {
|
for (AuthenticationProvider authProvider : authProviders) {
|
||||||
|
|
||||||
// Generate new UserContext
|
// 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
|
// Add to available data, if successful
|
||||||
if (userContext != null)
|
if (userContext != null)
|
||||||
@@ -453,7 +410,7 @@ public class AuthenticationService {
|
|||||||
* If the authentication or re-authentication attempt fails.
|
* If the authentication or re-authentication attempt fails.
|
||||||
*/
|
*/
|
||||||
public String authenticate(Credentials credentials, String token)
|
public String authenticate(Credentials credentials, String token)
|
||||||
throws GuacamoleException {
|
throws GuacamoleException {
|
||||||
|
|
||||||
// Pull existing session if token provided
|
// Pull existing session if token provided
|
||||||
GuacamoleSession existingSession;
|
GuacamoleSession existingSession;
|
||||||
@@ -462,25 +419,72 @@ public class AuthenticationService {
|
|||||||
else
|
else
|
||||||
existingSession = null;
|
existingSession = null;
|
||||||
|
|
||||||
// Get up-to-date AuthenticatedUser and associated UserContexts
|
AuthenticatedUser authenticatedUser;
|
||||||
AuthenticatedUser authenticatedUser = getAuthenticatedUser(existingSession, credentials);
|
|
||||||
List<DecoratedUserContext> userContexts = getUserContexts(existingSession, authenticatedUser, credentials);
|
|
||||||
|
|
||||||
// Update existing session, if it exists
|
|
||||||
String authToken;
|
String authToken;
|
||||||
if (existingSession != null) {
|
|
||||||
authToken = token;
|
try {
|
||||||
existingSession.setAuthenticatedUser(authenticatedUser);
|
|
||||||
existingSession.setUserContexts(userContexts);
|
// 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
|
// Log and rethrow any authentication errors
|
||||||
else {
|
catch (GuacamoleAuthenticationProcessException e) {
|
||||||
authToken = authTokenGenerator.getToken();
|
|
||||||
tokenSessionMap.put(authToken, new GuacamoleSession(environment, authenticatedUser, userContexts));
|
// Get request and username for sake of logging
|
||||||
logger.debug("Login was successful for user \"{}\".", authenticatedUser.getIdentifier());
|
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;
|
return authToken;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -76,21 +76,29 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
|||||||
* given AuthenticationProvider, or the original UserContext if the
|
* given AuthenticationProvider, or the original UserContext if the
|
||||||
* given AuthenticationProvider originated the UserContext.
|
* given AuthenticationProvider originated the UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If the given AuthenticationProvider fails while decorating the
|
* If the given AuthenticationProvider fails while decorating the
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*/
|
*/
|
||||||
private static UserContext decorate(AuthenticationProvider authProvider,
|
private static UserContext decorate(AuthenticationProvider authProvider,
|
||||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Skip the AuthenticationProvider which produced the UserContext
|
// Skip the AuthenticationProvider which produced the UserContext
|
||||||
// being decorated
|
// being decorated
|
||||||
if (authProvider != userContext.getAuthenticationProvider()) {
|
if (authProvider != userContext.getAuthenticationProvider()) {
|
||||||
|
|
||||||
// Apply layer of wrapping around UserContext
|
// Apply layer of wrapping around UserContext
|
||||||
UserContext decorated = authProvider.decorate(userContext,
|
UserContext decorated;
|
||||||
authenticatedUser, credentials);
|
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
|
// Do not allow misbehaving extensions to wipe out the
|
||||||
// UserContext entirely
|
// UserContext entirely
|
||||||
@@ -130,13 +138,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
|||||||
* given AuthenticationProvider, or the original UserContext if the
|
* given AuthenticationProvider, or the original UserContext if the
|
||||||
* given AuthenticationProvider originated the UserContext.
|
* given AuthenticationProvider originated the UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If the given AuthenticationProvider fails while decorating the
|
* If the given AuthenticationProvider fails while decorating the
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*/
|
*/
|
||||||
private static UserContext redecorate(DecoratedUserContext decorated,
|
private static UserContext redecorate(DecoratedUserContext decorated,
|
||||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
AuthenticationProvider authProvider = decorated.getDecoratingAuthenticationProvider();
|
AuthenticationProvider authProvider = decorated.getDecoratingAuthenticationProvider();
|
||||||
|
|
||||||
@@ -145,8 +153,16 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
|||||||
if (authProvider != userContext.getAuthenticationProvider()) {
|
if (authProvider != userContext.getAuthenticationProvider()) {
|
||||||
|
|
||||||
// Apply next layer of wrapping around UserContext
|
// Apply next layer of wrapping around UserContext
|
||||||
UserContext redecorated = authProvider.redecorate(decorated,
|
UserContext redecorated;
|
||||||
userContext, authenticatedUser, credentials);
|
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
|
// Do not allow misbehaving extensions to wipe out the
|
||||||
// UserContext entirely
|
// UserContext entirely
|
||||||
@@ -181,13 +197,13 @@ public class DecoratedUserContext extends DelegatingUserContext {
|
|||||||
* The credentials associated with the request which produced the given
|
* The credentials associated with the request which produced the given
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any of the given AuthenticationProviders fails while decorating
|
* If any of the given AuthenticationProviders fails while decorating
|
||||||
* the UserContext.
|
* the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext(AuthenticationProvider authProvider,
|
public DecoratedUserContext(AuthenticationProvider authProvider,
|
||||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
||||||
super(decorate(authProvider, userContext, authenticatedUser, credentials));
|
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
|
* The credentials associated with the request which produced the given
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any of the given AuthenticationProviders fails while decorating
|
* If any of the given AuthenticationProviders fails while decorating
|
||||||
* the UserContext.
|
* the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext(AuthenticationProvider authProvider,
|
public DecoratedUserContext(AuthenticationProvider authProvider,
|
||||||
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
// Wrap the result of invoking decorate() on the given AuthenticationProvider
|
||||||
super(decorate(authProvider, userContext, authenticatedUser, credentials));
|
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
|
* The credentials associated with the request which produced the given
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any of the given AuthenticationProviders fails while decorating
|
* If any of the given AuthenticationProviders fails while decorating
|
||||||
* the UserContext.
|
* the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext(DecoratedUserContext decorated,
|
public DecoratedUserContext(DecoratedUserContext decorated,
|
||||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
||||||
super(redecorate(decorated, userContext, authenticatedUser, credentials));
|
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
|
* The credentials associated with the request which produced the given
|
||||||
* UserContext.
|
* UserContext.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any of the given AuthenticationProviders fails while decorating
|
* If any of the given AuthenticationProviders fails while decorating
|
||||||
* the UserContext.
|
* the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext(DecoratedUserContext decorated,
|
public DecoratedUserContext(DecoratedUserContext decorated,
|
||||||
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
DecoratedUserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
// Wrap the result of invoking redecorate() on the given AuthenticationProvider
|
||||||
super(redecorate(decorated, userContext, authenticatedUser, credentials));
|
super(redecorate(decorated, userContext, authenticatedUser, credentials));
|
||||||
|
@@ -23,7 +23,6 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import org.apache.guacamole.GuacamoleException;
|
|
||||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||||
import org.apache.guacamole.net.auth.Credentials;
|
import org.apache.guacamole.net.auth.Credentials;
|
||||||
@@ -65,12 +64,12 @@ public class DecorationService {
|
|||||||
* A new DecoratedUserContext which has been decorated by all
|
* A new DecoratedUserContext which has been decorated by all
|
||||||
* AuthenticationProviders.
|
* AuthenticationProviders.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any AuthenticationProvider fails while decorating the UserContext.
|
* If any AuthenticationProvider fails while decorating the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext decorate(UserContext userContext,
|
public DecoratedUserContext decorate(UserContext userContext,
|
||||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||||
throws GuacamoleException {
|
throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// Get first AuthenticationProvider in list
|
// Get first AuthenticationProvider in list
|
||||||
Iterator<AuthenticationProvider> current = authProviders.iterator();
|
Iterator<AuthenticationProvider> current = authProviders.iterator();
|
||||||
@@ -119,12 +118,12 @@ public class DecorationService {
|
|||||||
* A new DecoratedUserContext which has been decorated by all
|
* A new DecoratedUserContext which has been decorated by all
|
||||||
* AuthenticationProviders.
|
* AuthenticationProviders.
|
||||||
*
|
*
|
||||||
* @throws GuacamoleException
|
* @throws GuacamoleAuthenticationProcessException
|
||||||
* If any AuthenticationProvider fails while decorating the UserContext.
|
* If any AuthenticationProvider fails while decorating the UserContext.
|
||||||
*/
|
*/
|
||||||
public DecoratedUserContext redecorate(DecoratedUserContext decorated,
|
public DecoratedUserContext redecorate(DecoratedUserContext decorated,
|
||||||
UserContext userContext, AuthenticatedUser authenticatedUser,
|
UserContext userContext, AuthenticatedUser authenticatedUser,
|
||||||
Credentials credentials) throws GuacamoleException {
|
Credentials credentials) throws GuacamoleAuthenticationProcessException {
|
||||||
|
|
||||||
// If the given DecoratedUserContext contains further decorated layers,
|
// If the given DecoratedUserContext contains further decorated layers,
|
||||||
// redecorate those first
|
// 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