blob: 49d8daaffd2d717a682f436da5268884b6b3c0a8 [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.auth.ssl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.auth.ssl.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleResourceNotFoundException;
import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService;
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.RedirectField;
import org.apache.guacamole.language.TranslatableMessage;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
/**
* Service that authenticates Guacamole users using SSL/TLS authentication
* provided by an external SSL termination service.
*/
@Singleton
public class AuthenticationProviderService implements SSOAuthenticationProviderService {
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Session manager for generating and maintaining unique tokens to
* represent the authentication flow of a user who has only partially
* authenticated. Here, these tokens represent a user that has been
* validated by SSL termination and allow the Guacamole instance that
* doesn't require SSL/TLS authentication to retrieve the user's identity
* and complete the authentication process.
*/
@Inject
private SSLAuthenticationSessionManager sessionManager;
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<SSOAuthenticatedUser> authenticatedUserProvider;
/**
* The name of the query parameter containing the temporary session token
* representing the current state of an in-progress authentication attempt.
*/
private static final String AUTH_SESSION_PARAMETER_NAME = "state";
/**
* Return the value of the session identifier associated with the given
* credentials, or null if no session identifier is found in the credentials.
*
* @param credentials
* The credentials from which to extract the session identifier.
*
* @return
* The session identifier associated with the given credentials, or
* null if no identifier is found.
*/
public static String getSessionIdentifier(Credentials credentials) {
// Return the session identifier from the request params, if set, or
// null otherwise
return credentials != null && credentials.getRequest() != null
? credentials.getRequest().getParameter(AUTH_SESSION_PARAMETER_NAME)
: null;
}
/**
* Processes the given credentials, returning the identity represented by
* the auth session token present in that request associated with the
* credentials. If no such token is present, or the token does not represent
* a valid identity, null is returned.
*
* @param credentials
* The credentials to extract the auth session token from.
*
* @return
* The identity represented by the auth session token in the request,
* or null if there is no such token or the token does not represent a
* valid identity.
*/
private SSOAuthenticatedUser processIdentity(Credentials credentials) {
String state = getSessionIdentifier(credentials);
String username = sessionManager.getIdentity(state);
if (username == null)
return null;
SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(username, credentials,
Collections.emptySet(), Collections.emptyMap());
return authenticatedUser;
}
@Override
public SSOAuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
//
// Overall flow:
//
// 1) An unauthenticated user makes a GET request to
// ".../api/ext/ssl/identity". After a series of redirects
// intended to prevent that identity from being inadvertently
// cached and inherited by future authentication attempts on the
// same client machine, an external SSL termination service requests
// and validates the user's certificate, those details are passed
// back to Guacamole via HTTP headers, and Guacamole produces a JSON
// response containing an opaque state value.
//
// 2) The user (still unauthenticated) resubmits the opaque state
// value from the received JSON as the "state" parameter of a
// standard Guacamole authentication request (".../api/tokens").
//
// 3) If the certificate received was valid, the user is authenticated
// according to the identity asserted by that certificate. If not,
// authentication is refused.
//
// NOTE: All SSL termination endpoints in front of Guacamole MUST
// be configured to drop these headers from any inbound requests
// or users may be able to assert arbitrary identities, since this
// extension does not validate anything but the certificate timestamps.
// It relies purely on SSL termination to validate that the certificate
// was signed by the expected CA.
//
// We can't authenticate using SSL/TLS client auth unless there's an
// associated HTTP request
HttpServletRequest request = credentials.getRequest();
if (request == null)
return null;
// We MUST have the domain associated with the request to ensure we
// always get fresh SSL sessions when validating client certificates
String host = request.getHeader("Host");
if (host == null)
return null;
//
// Handle only auth session tokens at the primary URI, using the
// pre-verified information from those tokens to determine user
// identity.
//
if (confService.isPrimaryHostname(host))
return processIdentity(credentials);
// All other requests are not allowed - redirect to proper hostname
throw new GuacamoleInvalidCredentialsException("Authentication is "
+ "only allowed against the primary URL of this Guacamole "
+ "instance.",
new CredentialsInfo(Arrays.asList(new Field[] {
new RedirectField("primaryURI", confService.getPrimaryURI(),
new TranslatableMessage("LOGIN.INFO_REDIRECT_PENDING"))
}))
);
}
@Override
public URI getLoginURI() throws GuacamoleException {
throw new GuacamoleResourceNotFoundException("No such resource.");
}
@Override
public void shutdown() {
sessionManager.shutdown();
}
}