/*
* #%L
* FiwareMarketplace
* %%
* Copyright (C) 2014-2015 CoNWeT Lab, Universidad Politécnica de Madrid
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of copyright holders nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package es.upm.fiware.rss.oauth.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.oauth.credentials.OAuthCredentials;
import org.pac4j.springframework.security.authentication.ClientAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Filter that will identify a user when the Authorization header is specified
* in a request to the API
*
* @author aitor
*
*/
public class FIWAREHeaderAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String headerName;
private FIWAREClient client;
private static final Pattern AUTHORIZATION_PATTERN
= Pattern.compile("^bearer ([^\\s]+)$", Pattern.CASE_INSENSITIVE);
protected FIWAREHeaderAuthenticationFilter() {
this("/rss/", "Authorization");
}
protected FIWAREHeaderAuthenticationFilter(String baseUrl, String headerName) {
// Super class constructor must be called.
super(new FIWAREHeaderAuthenticationRequestMatcher(baseUrl, headerName));
// Store header name
this.headerName = headerName;
// Needed to continue with the process of the request
setContinueChainBeforeSuccessfulAuthentication(true);
// Set the authentication in the context
setSessionAuthenticationStrategy(new FIWAREHeaderAuthenticationStrategy());
// This handler doesn't do anything but it's required to replace the default one
setAuthenticationSuccessHandler(new FIWAREHeaderAuthenticationSuccessHandler());
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
Authentication auth = null;
String authHeader = request.getHeader(headerName);
Matcher matcher = AUTHORIZATION_PATTERN.matcher(authHeader);
try {
// We only have one possible match
if (matcher.find()) {
String authToken = matcher.group(1);
// This method can return an exception when the Token is invalid
// In this case, the exception is caught and the correct exceptions is thrown...
UserProfile profile = client.getUserProfile(authToken);
// Define authorities
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String role : profile.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
// new token with credentials (like previously) and user profile and authorities
OAuthCredentials credentials = new OAuthCredentials(null, authToken, "", client.getName());
auth = new ClientAuthenticationToken(credentials, client.getName(), profile, authorities);
} else {
// This is not supposed to happen
throw new IllegalStateException("Pattern is suppossed to match.");
}
} catch (Exception ex) {
// This exception should be risen in order to return a 401
throw new BadCredentialsException("The provided token is invalid or the system was not able to check it");
}
return auth;
}
public FIWAREClient getClient() {
return this.client;
}
public void setClient(FIWAREClient client) {
this.client = client;
}
// AUXILIAR CLASSES //
/**
* Request Matcher that specifies when the filter should be executed. In
* this case we want the filter to be executed when the following two
* conditions are true: 1) The request is to the API (/api/...) 2) The
* X-Auth-Token header is present (Authorization: ...)
*
* @author aitor
*
*/
static class FIWAREHeaderAuthenticationRequestMatcher implements RequestMatcher {
private String baseUrl;
private String headerName;
public FIWAREHeaderAuthenticationRequestMatcher(String baseUrl, String headerName) {
this.baseUrl = baseUrl;
this.headerName = headerName;
}
@Override
public boolean matches(HttpServletRequest request) {
String authHeader = request.getHeader(headerName);
// Get path
String url = request.getServletPath();
String pathInfo = request.getPathInfo();
String query = request.getQueryString();
if (pathInfo != null || query != null) {
StringBuilder sb = new StringBuilder(url);
if (pathInfo != null) {
sb.append(pathInfo);
}
if (query != null) {
sb.append('?').append(query);
}
url = sb.toString();
}
return url.startsWith(baseUrl) && authHeader != null
&& AUTHORIZATION_PATTERN.matcher(authHeader).matches();
}
}
/**
* Actions to be carried out when the authentication is successful. In this
* case no actions are required.
*
* @author aitor
*
*/
static class FIWAREHeaderAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
// Nothing to do... The chain will continue
}
}
/**
* Set the Session in the Security Context when the Authorization token is
* valid
*
* @author aitor
*
*/
static class FIWAREHeaderAuthenticationStrategy implements SessionAuthenticationStrategy {
@Override
public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response)
throws SessionAuthenticationException {
// Set the authentication in the current context
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}