/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2014] 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.login;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* Authentication filter accepting basic authorization header and using it to
* relay to a remote <code>/clientinfo</code> endpoint. Allows rejecting of
* clients based on id or authorized grant type.
*
* @author Dave Syer
*
*/
public class ClientInfoAuthenticationFilter implements Filter {
protected final Log logger = LogFactory.getLog(getClass());
private Set<String> allowedClients = Collections.singleton(".*");
private Set<String> allowedGrantTypes = Collections.singleton(".*");
private RestOperations restTemplate = new RestTemplate();
private String clientInfoUrl;
private AuthenticationEntryPoint authenticationEntryPoint = new BasicAuthenticationEntryPoint();
public void setRestTemplate(RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
public void setClientInfoUrl(String clientInfoUrl) {
this.clientInfoUrl = clientInfoUrl;
}
/**
* @param allowedClients the allowedClients to set
*/
public void setAllowedClients(Set<String> allowedClients) {
this.allowedClients = new HashSet<String>(allowedClients);
}
/**
* @param allowedGrantTypes the allowedGrantTypes to set
*/
public void setAllowedGrantTypes(Set<String> allowedGrantTypes) {
this.allowedGrantTypes = allowedGrantTypes;
}
/**
* @param authenticationEntryPoint the authenticationEntryPoint to set
*/
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
/**
* Populates the Spring Security context with a
* {@link UsernamePasswordAuthenticationToken} referring to the client
* that authenticates using the basic authorization header.
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
String header = servletRequest.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", header);
try {
ResponseEntity<BaseClientDetails> result = restTemplate.exchange(clientInfoUrl, HttpMethod.GET,
new HttpEntity<Void>(headers), BaseClientDetails.class);
ClientDetails client = result.getBody();
String clientId = client.getClientId();
validateClient(client);
Authentication authResult = new UsernamePasswordAuthenticationToken(clientId, "<NONE>",
client.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (RuntimeException e) {
logger.debug("Authentication failed");
authenticationEntryPoint.commence(servletRequest, (HttpServletResponse) response,
new BadCredentialsException("Could not authenticate", e));
return;
}
chain.doFilter(request, response);
}
protected void validateClient(ClientDetails client) {
String clientId = client.getClientId();
for (String pattern : allowedClients) {
if (!clientId.matches(pattern)) {
throw new BadCredentialsException("Client not permitted: " + clientId);
}
}
Set<String> grantTypes = client.getAuthorizedGrantTypes();
boolean matched = false;
for (String pattern : allowedGrantTypes) {
for (String grantType : grantTypes) {
if (grantType.matches(pattern)) {
matched = true;
}
}
}
if (!matched) {
throw new BadCredentialsException("Client not permitted (wrong grant type): " + clientId);
}
}
protected List<GrantedAuthority> getAuthorities(Collection<String> authorities) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString(authorities));
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}