blob: 6c6474b457956b11e80c4346c92bcd2d3a99e1bb [file] [log] [blame]
/*
* 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.extension;
import java.util.Set;
import java.util.UUID;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.UserContext;
import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides a safe wrapper around an AuthenticationProvider subclass, such that
* authentication attempts can cleanly fail, and errors can be properly logged,
* even if the AuthenticationProvider cannot be instantiated.
*/
public class AuthenticationProviderFacade implements AuthenticationProvider {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(AuthenticationProviderFacade.class);
/**
* The underlying authentication provider, or null if the authentication
* provider could not be instantiated.
*/
private final AuthenticationProvider authProvider;
/**
* The set of identifiers of all authentication providers whose internal
* failures should be tolerated during the authentication process. If the
* identifier of this authentication provider is within this set, errors
* during authentication will result in the authentication provider being
* ignored for that authentication attempt. By default, errors during
* authentication halt the authentication process entirely.
*/
private final Set<String> tolerateFailures;
/**
* The identifier to provide for the underlying authentication provider if
* the authentication provider could not be loaded.
*/
private final String facadeIdentifier = UUID.randomUUID().toString();
/**
* Creates a new AuthenticationProviderFacade which delegates all function
* calls to an instance of the given AuthenticationProvider subclass. If
* an instance of the given class cannot be created, creation of this
* facade will still succeed, but its use will result in errors being
* logged, and all authentication attempts will fail.
*
* @param authProviderClass
* The AuthenticationProvider subclass to instantiate.
*
* @param tolerateFailures
* The set of identifiers of all authentication providers whose
* internal failures should be tolerated during the authentication
* process. If the identifier of this authentication provider is within
* this set, errors during authentication will result in the
* authentication provider being ignored for that authentication
* attempt. By default, errors during authentication halt the
* authentication process entirely.
*/
public AuthenticationProviderFacade(
Class<? extends AuthenticationProvider> authProviderClass,
Set<String> tolerateFailures) {
this.tolerateFailures = tolerateFailures;
this.authProvider = ProviderFactory.newInstance("authentication provider",
authProviderClass);
}
@Override
public String getIdentifier() {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("The authentication system could not be loaded. Please check for errors earlier in the logs.");
return facadeIdentifier;
}
// Delegate to underlying auth provider
return authProvider.getIdentifier();
}
@Override
public Object getResource() throws GuacamoleException {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("The authentication system could not be loaded. Please check for errors earlier in the logs.");
return null;
}
// Delegate to underlying auth provider
return authProvider.getResource();
}
/**
* Returns whether this authentication provider should tolerate internal
* failures during the authentication process, allowing other
* authentication providers to continue operating as if this authentication
* provider simply is not present.
*
* @return
* true if this authentication provider should tolerate internal
* failures during the authentication process, false otherwise.
*/
private boolean isFailureTolerated() {
return tolerateFailures.contains(getIdentifier());
}
/**
* Logs a warning that this authentication provider is being skipped due to
* an internal error. If debug-level logging is enabled, the full details
* of the internal error are also logged.
*
* @param e
* The internal error that occurred which has resulted in this
* authentication provider being skipped.
*/
private void warnAuthProviderSkipped(Throwable e) {
logger.warn("The \"{}\" authentication provider has been skipped due "
+ "to an internal error. If this is unexpected or you are the "
+ "developer of this authentication provider, you may wish to "
+ "enable debug-level logging: {}",
getIdentifier(), e.getMessage());
logger.debug("Authentication provider skipped due to an internal failure.", e);
}
/**
* Logs a warning that the authentication process will be entirely aborted
* due to an internal error, advising the administrator to set the
* "skip-if-unavailable" property if error encountered is expected and
* should be tolerated.
*/
private void warnAuthAborted() {
String identifier = getIdentifier();
logger.warn("The \"{}\" authentication provider has encountered an "
+ "internal error which will halt the authentication "
+ "process. If this is unexpected or you are the developer of "
+ "this authentication provider, you may wish to enable "
+ "debug-level logging. If this is expected and you wish to "
+ "ignore such failures in the future, please set \"{}: {}\" "
+ "within your guacamole.properties.",
identifier, ExtensionModule.SKIP_IF_UNAVAILABLE.getName(),
identifier);
}
@Override
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("Authentication attempt ignored because the relevant "
+ "authentication provider could not be loaded. Please "
+ "check for errors earlier in the logs.");
return null;
}
// Delegate to underlying auth provider
try {
return authProvider.authenticateUser(credentials);
}
// Pass through credential exceptions untouched, as these are not
// internal failures
catch (GuacamoleCredentialsException e) {
throw e;
}
// Pass through all other exceptions (aborting authentication entirely)
// only if not configured to ignore such failures
catch (GuacamoleException e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
catch (RuntimeException e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
catch (Error e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
}
@Override
public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("Reauthentication attempt ignored because the relevant "
+ "authentication provider could not be loaded. Please "
+ "check for errors earlier in the logs.");
return null;
}
// Delegate to underlying auth provider
return authProvider.updateAuthenticatedUser(authenticatedUser, credentials);
}
@Override
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("User data retrieval attempt ignored because the "
+ "relevant authentication provider could not be loaded. "
+ "Please check for errors earlier in the logs.");
return null;
}
// Delegate to underlying auth provider
try {
return authProvider.getUserContext(authenticatedUser);
}
// Pass through credential exceptions untouched, as these are not
// internal failures
catch (GuacamoleCredentialsException e) {
throw e;
}
// Pass through all other exceptions (aborting authentication entirely)
// only if not configured to ignore such failures
catch (GuacamoleException e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
catch (RuntimeException e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
catch (Error e) {
// Skip using this authentication provider if configured to ignore
// internal failures during auth
if (isFailureTolerated()) {
warnAuthProviderSkipped(e);
return null;
}
warnAuthAborted();
throw e;
}
}
@Override
public UserContext updateUserContext(UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
// Ignore auth attempts if no auth provider could be loaded
if (authProvider == null) {
logger.warn("User data refresh attempt ignored because the "
+ "relevant authentication provider could not be loaded. "
+ "Please check for errors earlier in the logs.");
return null;
}
// Delegate to underlying auth provider
return authProvider.updateUserContext(context, authenticatedUser, credentials);
}
@Override
public UserContext decorate(UserContext context,
AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Do nothing if underlying auth provider could not be loaded
if (authProvider == null)
return context;
// Delegate to underlying auth provider
return authProvider.decorate(context, authenticatedUser, credentials);
}
@Override
public UserContext redecorate(UserContext decorated, UserContext context,
AuthenticatedUser authenticatedUser,
Credentials credentials) throws GuacamoleException {
// Do nothing if underlying auth provider could not be loaded
if (authProvider == null)
return context;
// Delegate to underlying auth provider
return authProvider.redecorate(decorated, context,
authenticatedUser, credentials);
}
@Override
public void shutdown() {
if (authProvider != null)
authProvider.shutdown();
}
}