/******************************************************************************* * * Copyright (c) 2004-2012 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Winston Prakash * * *******************************************************************************/ package hudson.security; import hudson.Functions; import hudson.model.Hudson; import hudson.util.Scrambler; import java.io.IOException; import java.net.URLEncoder; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder; import org.springframework.security.core.context.SecurityContextHolder; /** * Implements the dual authentcation mechanism. * * <p> Hudson supports both the HTTP basic authentication and the form-based * authentication. The former is for scripted clients, and the latter is for * humans. Unfortunately, becase the servlet spec does not allow us to * programatically authenticate users, we need to rely on some work around to * make it work, and this is the class that implements that work around. * * <p> When an HTTP request arrives with an HTTP basic auth header, this filter * detects that and emulate an invocation of <tt>/j_security_check</tt> (see <a * href="http://mail-archives.apache.org/mod_mbox/tomcat-users/200105.mbox/%3C9005C0C9C85BD31181B20060085DAC8B10C8EF@tuvi.andmevara.ee%3E">this * page</a> for the original technique.) * * <p> This causes the container to perform authentication, but there's no way * to find out whether the user has been successfully authenticated or not. So * to find this out, we then redirect the user to * {@link Hudson#doSecured(org.kohsuke.stapler.StaplerRequest, org.kohsuke.stapler.StaplerResponse) <tt>/secured/...</tt> page}. * * <p> The handler of the above URL checks if the user is authenticated, and if * not report an HTTP error code. Otherwise the user is redirected back to the * original URL, where the request is served. * * <p> So all in all, the redirection works like <tt>/abc/def</tt> -> * <tt>/secured/abc/def</tt> -> <tt>/abc/def</tt>. * * <h2>Notes</h2> <ul> <li> The technique of getting a request dispatcher for * <tt>/j_security_check</tt> may not work for all containers, but so far that * seems like the only way to make this work. <li> This A->B->A redirect is a * cyclic redirection, so we need to watch out for clients that detect this as * an error. </ul> * * @author Kohsuke Kawaguchi */ public class BasicAuthenticationFilter implements Filter { private ServletContext servletContext; public void init(FilterConfig filterConfig) throws ServletException { servletContext = filterConfig.getServletContext(); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse rsp = (HttpServletResponse) response; String authorization = req.getHeader("Authorization"); String path = req.getServletPath(); if (authorization == null || req.getUserPrincipal() != null || path.startsWith("/secured/") || !HudsonSecurityEntitiesHolder.getHudsonSecurityManager().isUseSecurity()) { // normal requests, or security not enabled if (req.getUserPrincipal() != null) { // before we route this request, integrate the container authentication // to Spring Security. For anonymous users that doesn't have user principal, // AnonymousProcessingFilter that follows this should create // an Authentication object. SecurityContextHolder.getContext().setAuthentication(new ContainerAuthentication(req)); } try { chain.doFilter(request, response); } finally { SecurityContextHolder.clearContext(); } return; } // authenticate the user String username = null; String password = null; String uidpassword = Scrambler.descramble(authorization.substring(6)); int idx = uidpassword.indexOf(':'); if (idx >= 0) { username = uidpassword.substring(0, idx); password = uidpassword.substring(idx + 1); } if (username == null) { rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); rsp.setHeader("WWW-Authenticate", "Basic realm=\"Hudson administrator\""); return; } path = Functions.getHttpRequestRootPath(req) + "/secured" + path; String q = req.getQueryString(); if (q != null) { path += '?' + q; } // prepare a redirect rsp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); rsp.setHeader("Location", path); // ... but first let the container authenticate this request RequestDispatcher d = servletContext.getRequestDispatcher("/j_security_check?j_username=" + URLEncoder.encode(username, "UTF-8") + "&j_password=" + URLEncoder.encode(password, "UTF-8")); d.include(req, rsp); } //public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // HttpServletRequest req = (HttpServletRequest) request; // String authorization = req.getHeader("Authorization"); // // String path = req.getServletPath(); // if(authorization==null || req.getUserPrincipal()!=null || path.startsWith("/secured/")) { // chain.doFilter(request,response); // } else { // if(req.getQueryString()!=null) // path += req.getQueryString(); // ((HttpServletResponse)response).sendRedirect(Functions.getRootPath(req)+"/secured"+path); // } //} public void destroy() { } }