/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.common.exceptions.UnauthorizedClientException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.saml.SAMLProcessingFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_SAML2_BEARER;
/**
* Provides an implementation that sets the UserAuthentication
* prior to createAuthorizatioRequest is called.
* Backwards compatible with Spring Security Oauth2 v1
* This is a copy of the TokenEndpointAuthenticationFilter from Spring Security Oauth2 v2, but made to work with UAA
*
*/
public class BackwardsCompatibleTokenEndpointAuthenticationFilter implements Filter {
private static final Log logger = LogFactory.getLog(BackwardsCompatibleTokenEndpointAuthenticationFilter.class);
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
private final AuthenticationManager authenticationManager;
private final OAuth2RequestFactory oAuth2RequestFactory;
private final SAMLProcessingFilter samlAuthenticationFilter;
public BackwardsCompatibleTokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager,
OAuth2RequestFactory oAuth2RequestFactory) {
this(authenticationManager, oAuth2RequestFactory, null);
}
/**
* @param authenticationManager an AuthenticationManager for the incoming request
*/
public BackwardsCompatibleTokenEndpointAuthenticationFilter(AuthenticationManager authenticationManager,
OAuth2RequestFactory oAuth2RequestFactory,
SAMLProcessingFilter samlAuthenticationFilter) {
super();
this.authenticationManager = authenticationManager;
this.oAuth2RequestFactory = oAuth2RequestFactory;
this.samlAuthenticationFilter = samlAuthenticationFilter;
}
/**
* An authentication entry point that can handle unsuccessful authentication. Defaults to an
* {@link OAuth2AuthenticationEntryPoint}.
*
* @param authenticationEntryPoint the authenticationEntryPoint to set
*/
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
/**
* A source of authentication details for requests that result in authentication.
*
* @param authenticationDetailsSource the authenticationDetailsSource to set
*/
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
Authentication userAuthentication = extractCredentials(request, response);
if (userAuthentication != null) {
Authentication clientAuth = SecurityContextHolder.getContext().getAuthentication();
if (clientAuth == null) {
throw new BadCredentialsException(
"No client authentication found. Remember to put a filter upstream of the TokenEndpointAuthenticationFilter.");
}
Map<String, String> map = getSingleValueMap(request);
map.put(OAuth2Utils.CLIENT_ID, clientAuth.getName());
SecurityContextHolder.getContext().setAuthentication(userAuthentication);
AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(map);
//authorizationRequest.setScope(getScope(request));
if (clientAuth.isAuthenticated()) {
// Ensure the OAuth2Authentication is authenticated
authorizationRequest.setApproved(true);
}
OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(authorizationRequest);
SecurityContextHolder
.getContext()
.setAuthentication(new OAuth2Authentication(storedOAuth2Request, userAuthentication));
onSuccessfulAuthentication(request, response, userAuthentication);
}
} catch (UnauthorizedClientException failed) {
//happens when all went well, but the client is not authorized for the identity provider
UnapprovedClientAuthenticationException ex = new UnapprovedClientAuthenticationException(failed.getMessage(), failed);
SecurityContextHolder.clearContext();
logger.debug("Authentication request for failed: " + failed);
onUnsuccessfulAuthentication(request, response, ex);
authenticationEntryPoint.commence(request, response, ex);
return;
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
logger.debug("Authentication request for failed: " + failed);
onUnsuccessfulAuthentication(request, response, failed);
authenticationEntryPoint.commence(request, response, failed);
return;
} catch (InvalidScopeException failed) {
String message = failed.getMessage();
UnapprovedClientAuthenticationException ex = new UnapprovedClientAuthenticationException(message, failed);
SecurityContextHolder.clearContext();
logger.debug("Authentication request for failed: " + ex);
onUnsuccessfulAuthentication(request, response, ex);
authenticationEntryPoint.commence(request, response, ex);
return;
}
chain.doFilter(request, response);
}
private Map<String, String> getSingleValueMap(HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
Map<String, String[]> parameters = request.getParameterMap();
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
map.put(key, values != null && values.length > 0 ? values[0] : null);
}
return map;
}
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException {
}
protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException {
}
/**
* If the incoming request contains user credentials in headers or parameters then extract them here into an
* Authentication token that can be validated later. This implementation only recognises password grant requests and
* extracts the username and password.
*
* @param request the incoming request, possibly with user credentials
* @return an authentication for validation (or null if there is no further authentication)
*/
protected Authentication extractCredentials(HttpServletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken credentials = new UsernamePasswordAuthenticationToken(username, password);
credentials.setDetails(authenticationDetailsSource.buildDetails(request));
return credentials;
}
protected Authentication extractCredentials(HttpServletRequest request, HttpServletResponse response) {
String grantType = request.getParameter("grant_type");
Authentication authResult = null;
if ("password".equals(grantType)) {
Authentication credentials = extractCredentials(request);
logger.debug("Authentication credentials found password grant for '" + credentials.getName() + "'");
authResult = authenticationManager.authenticate(credentials);
return authResult;
} else if (GRANT_TYPE_SAML2_BEARER.equals(grantType)) {
logger.debug(GRANT_TYPE_SAML2_BEARER +" found. Attempting authentication with assertion");
String assertion = request.getParameter("assertion");
if (assertion != null && samlAuthenticationFilter != null) {
logger.debug("Attempting SAML authentication for token endpoint.");
authResult = samlAuthenticationFilter.attemptAuthentication(request, response);
} else {
logger.debug("No assertion or filter, not attempting SAML authentication for token endpoint.");
}
}
if (authResult != null && authResult.isAuthenticated()) {
logger.debug("Authentication success: " + authResult.getName());
return authResult;
}
return null;
}
private Set<String> getScope(HttpServletRequest request) {
return OAuth2Utils.parseParameterList(request.getParameter("scope"));
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}