package hudson.plugins.collabnet.auth;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.net.URLEncoder;
import hudson.model.Hudson;
import hudson.plugins.collabnet.util.CommonUtil;
import hudson.security.SecurityRealm;
import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.lang.StringUtils;
import com.collabnet.ce.webservices.CollabNetApp;
/**
* Class for filtering CollabNet auth information for SSO.
*/
public class CNFilter implements Filter {
private static Logger log = Logger.getLogger("CNFilter");
public void init(FilterConfig filterConfig) {
}
/**
* Filter for the CollabNet plugin. Handles 2 separate tasks:
* 1. Attempts to use CollabNet tokens to login (if they are present and
* we're not currently authed.).
* 2. If we have not yet logged into the CollabNet server, redirect
* to the CollabNet server and login.
*
* @param request the servlet request
* @param response the servlet response
* @param chain remaining filters to handle.
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (Hudson.getInstance().isUseSecurity()) {
// check if we're in the CollabNetSecurity Realm
SecurityRealm securityRealm = Hudson.getInstance().getSecurityRealm();
if (securityRealm instanceof CollabNetSecurityRealm) {
CollabNetSecurityRealm cnRealm = (CollabNetSecurityRealm) securityRealm;
boolean enableSSOFromCTF = cnRealm.getEnableSSOAuthFromCTF();
boolean enableSSOToCTF = cnRealm.getEnableSSOAuthToCTF();
Authentication auth = Hudson.getAuthentication();
if (enableSSOFromCTF) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// first detect if we are accessing hudson through CTF
String username = request.getParameter("sfUsername");
if (username != null) {
// 'sfUsername' is used for CTF linked apps. if present make sure match the authenticated user
if (!username.equals(auth.getName())) {
auth.setAuthenticated(false);
}
}
if (!auth.isAuthenticated() || auth.getPrincipal().equals("anonymous")) {
loginHudsonUsingCTFSSO((CollabNetSecurityRealm)securityRealm, httpRequest);
}
}
if (enableSSOToCTF && auth instanceof CNAuthentication) {
CNAuthentication cnauth = (CNAuthentication) auth;
if (!cnauth.isCNAuthed()) {
loginToCTF(cnauth, (CollabNetSecurityRealm)securityRealm,
(HttpServletRequest) request, (HttpServletResponse) response);
return;
}
}
}
}
chain.doFilter(request, response);
}
/**
* Catch SSO data from CollabNet if data is present, and
* automatically login. Used when the Hudson server is setup as a
* linked application in the CollabNet server.
* The CollabNet server will sent 2 parameters: sfUsername and
* sfLoginToken.
* The token is a one-time token that can be used to initiate a SOAP
* session and set authentication.
*
* @param securityRealm the security realm
* @param request the huttp servlet reuqest
*/
private void loginHudsonUsingCTFSSO(CollabNetSecurityRealm securityRealm, HttpServletRequest request) {
String url = securityRealm.getCollabNetUrl();
String username = request.getParameter("sfUsername");
String token = request.getParameter("sfLoginToken");
Authentication auth = null;
boolean logoff = false;
if (username != null && token != null) {
CollabNetApp ca = new CollabNetApp(url, username);
try {
ca.loginWithToken(token);
auth = new CNAuthentication(username, ca);
} catch (RemoteException re) {
// login failed, but continue
log.severe("Login failed with RemoteException: " +
re.getMessage());
logoff = true;
}
} else {
logoff = true;
}
if (logoff) {
auth = new AnonymousAuthenticationToken("anonymous","anonymous",
new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")});
}
// ensure that a session exists before we set context in it
// see artf42298
request.getSession(true);
SecurityContextHolder.getContext().setAuthentication(auth);
}
/**
* Redirect to the CollabNet Server to login there, and then
* redirect back to our original location.
*
* @param cnauth the CNAuthentication object
* @param securityRealm the security realm
* @param request the http request
* @param response the http response
* @throws IOException
* @throws ServletException
*/
private void loginToCTF(CNAuthentication cnauth, CollabNetSecurityRealm securityRealm,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
cnauth.setCNAuthed(true);
String reqUrl = getCurrentUrl(request);
String collabNetUrl = securityRealm.getCollabNetUrl();
String username = (String)cnauth.getPrincipal();
String id = cnauth.getSessionId();
String cnauthUrl = collabNetUrl + "/sf/sfmain/do/soapredirect?id="
+ URLEncoder.encode(id, "UTF-8") + "&user=" +
URLEncoder.encode(username,"UTF-8");
if (securityRealm.getEnableSSORedirect()) {
// append redirect only if enabled
cnauthUrl = cnauthUrl + "&redirectUrl=" + URLEncoder.encode(reqUrl,"UTF-8");
}
// prepare a redirect
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.setHeader("Location", cnauthUrl);
}
/**
* @param req the servlet request to pull data from, if root url is unset.
* @return the best guess for the current base URL (i.e. just the scheme,
* server, port) plus the contextPath.
*/
public static String getCurrentBaseUrl(HttpServletRequest req) {
StringBuilder url = new StringBuilder();
// Use the user configured url, if available.
String rootUrl = Hudson.getInstance().getRootUrl();
if (rootUrl != null) {
url.append(rootUrl);
}else {
// otherwise, use the request
url.append(req.getScheme());
url.append("://");
url.append(req.getServerName());
if (req.getServerPort() != 80) {
url.append(':').append(req.getServerPort());
}
url.append(req.getContextPath());
}
return url.toString();
}
/**
* @return the best guess for the current full URL.
* It will use the "referer" field from the request
* to determine the url, if it is present.
*/
public static String getCurrentUrl(HttpServletRequest req) {
String curBaseUrl = getCurrentBaseUrl(req);
// remove the contextPath from the url, since it will also
// be present in the requestURI.
curBaseUrl = CommonUtil.stripSlashes(curBaseUrl);
curBaseUrl = StringUtils.removeEnd(curBaseUrl, req.getContextPath());
StringBuilder url = new StringBuilder(curBaseUrl);
if (req.getRequestURI() != null) {
url.append(req.getRequestURI());
}
if (req.getQueryString() != null) {
url.append("?" + req.getQueryString());
}
return url.toString();
}
// destroy is currently a no-op
public void destroy() {
}
}