/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed 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 jenkins.security;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationTrustResolver;
import org.acegisecurity.AuthenticationTrustResolverImpl;
import org.acegisecurity.InsufficientAuthenticationException;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.ui.AbstractProcessingFilter;
import org.acegisecurity.ui.AccessDeniedHandler;
import org.acegisecurity.ui.AccessDeniedHandlerImpl;
import org.acegisecurity.ui.AuthenticationEntryPoint;
import org.acegisecurity.ui.savedrequest.SavedRequest;
import org.acegisecurity.util.PortResolver;
import org.acegisecurity.util.PortResolverImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
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.logging.Level;
import java.util.logging.Logger;
/**
* Handles any <code>AccessDeniedException</code> and <code>AuthenticationException</code> thrown within the
* filter chain.
* <p>
* This filter is necessary because it provides the bridge between Java exceptions and HTTP responses.
* It is solely concerned with maintaining the user interface. This filter does not do any actual security enforcement.
* </p>
* <p>
* If an {@link AuthenticationException} is detected, the filter will launch the <code>authenticationEntryPoint</code>.
* This allows common handling of authentication failures originating from any subclass of
* <tt>AbstractSecurityInterceptor</tt>.
* </p>
* <p>
* If an {@link AccessDeniedException} is detected, the filter will determine whether or not the user is an anonymous
* user. If they are an anonymous user, the <code>authenticationEntryPoint</code> will be launched. If they are not
* an anonymous user, the filter will delegate to the <tt>AccessDeniedHandler</tt>.
* By default the filter will use <tt>AccessDeniedHandlerImpl</tt>.
* </p>
* <p>
* To use this filter, it is necessary to specify the following properties:
* </p>
* <ul>
* <li><code>authenticationEntryPoint</code> indicates the handler that
* should commence the authentication process if an
* <code>AuthenticationException</code> is detected. Note that this may also
* switch the current protocol from http to https for an SSL login.</li>
* <li><code>portResolver</code> is used to determine the "real" port that a
* request was received on.</li>
* </ul>
* <P>
* <B>Do not use this class directly.</B> Instead configure
* <code>web.xml</code> to use the <tt>FilterToBeanProxy</tt>.
* </p>
*
* @author Ben Alex
* @author colin sampaleanu
* @version $Id: ExceptionTranslationFilter.java 2134 2007-09-19 16:41:06Z luke_t $
*/
public class ExceptionTranslationFilter implements Filter, InitializingBean {
//~ Static fields/initializers =====================================================================================
private static final Logger LOGGER = Logger.getLogger(ExceptionTranslationFilter.class.getName());
//~ Instance fields ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private PortResolver portResolver = new PortResolverImpl();
private boolean createSessionAllowed = true;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified");
Assert.notNull(portResolver, "portResolver must be specified");
Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("HttpServletRequest required");
}
if (!(response instanceof HttpServletResponse)) {
throw new ServletException("HttpServletResponse required");
}
try {
chain.doFilter(request, response);
LOGGER.finer("Chain processed normally");
}
catch (AuthenticationException ex) {
handleException(request, response, chain, ex);
}
catch (AccessDeniedException ex) {
handleException(request, response, chain, ex);
}
catch (ServletException ex) {
if (ex.getRootCause() instanceof AuthenticationException
|| ex.getRootCause() instanceof AccessDeniedException) {
handleException(request, response, chain, (AcegiSecurityException) ex.getRootCause());
}
else {
throw ex;
}
}
catch (IOException ex) {
throw ex;
}
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
public PortResolver getPortResolver() {
return portResolver;
}
private void handleException(ServletRequest request, ServletResponse response, FilterChain chain,
AcegiSecurityException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
LOGGER.log(Level.FINER, "Authentication exception occurred; redirecting to authentication entry point", exception);
sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
LOGGER.log(Level.FINER, "Access is denied (user is anonymous); redirecting to authentication entry point",
exception);
sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(
"Full authentication is required to access this resource",exception));
}
else {
LOGGER.log(Level.FINER, "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
}
}
}
/**
* If <code>true</code>, indicates that <code>SecurityEnforcementFilter</code> is permitted to store the target
* URL and exception information in the <code>HttpSession</code> (the default).
* In situations where you do not wish to unnecessarily create <code>HttpSession</code>s - because the user agent
* will know the failed URL, such as with BASIC or Digest authentication - you may wish to
* set this property to <code>false</code>. Remember to also set the
* {@link org.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation}
* to <code>false</code> if you set this property to <code>false</code>.
*
* @return <code>true</code> if the <code>HttpSession</code> will be
* used to store information about the failed request, <code>false</code>
* if the <code>HttpSession</code> will not be used
*/
public boolean isCreateSessionAllowed() {
return createSessionAllowed;
}
protected void sendStartAuthentication(ServletRequest request, ServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SavedRequest savedRequest = new SavedRequest(httpRequest, portResolver);
LOGGER.finer("Authentication entry point being called; SavedRequest added to Session: " + savedRequest);
if (createSessionAllowed) {
// Store the HTTP request itself. Used by AbstractProcessingFilter
// for redirection after successful authentication (SEC-29)
httpRequest.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, savedRequest);
}
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
authenticationEntryPoint.commence(httpRequest, response, reason);
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
this.authenticationTrustResolver = authenticationTrustResolver;
}
public void setCreateSessionAllowed(boolean createSessionAllowed) {
this.createSessionAllowed = createSessionAllowed;
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}