package org.argeo.cms.internal.kernel; import java.io.IOException; import java.util.Properties; import java.util.StringTokenizer; import javax.servlet.FilterChain; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; import org.argeo.cms.internal.kernel.NodeHttp.AnonymousFilter; import org.argeo.cms.internal.kernel.NodeHttp.DavFilter; import org.argeo.jackrabbit.servlet.OpenInViewSessionProvider; import org.argeo.jackrabbit.servlet.RemotingServlet; import org.argeo.jackrabbit.servlet.WebdavServlet; import org.argeo.jcr.ArgeoJcrConstants; import org.eclipse.equinox.http.servlet.ExtendedHttpService; import org.osgi.framework.BundleContext; import org.osgi.service.http.NamespaceException; import org.osgi.util.tracker.ServiceTracker; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** * Intercepts and enriches http access, mainly focusing on security and * transactionality. */ class NodeHttp implements KernelConstants, ArgeoJcrConstants { private final static Log log = LogFactory.getLog(NodeHttp.class); private final static String ATTR_AUTH = "auth"; private final static String HEADER_AUTHORIZATION = "Authorization"; private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT"; private final AuthenticationManager authenticationManager; private final BundleContext bundleContext; private ExtendedHttpService httpService; // FIXME Make it more unique private String httpAuthRealm = "Argeo"; // Filters private final RootFilter rootFilter; // remoting private OpenInViewSessionProvider sessionProvider; private WebdavServlet publicWebdavServlet; private WebdavServlet privateWebdavServlet; private RemotingServlet publicRemotingServlet; private RemotingServlet privateRemotingServlet; NodeHttp(BundleContext bundleContext, JackrabbitNode node, NodeSecurity authenticationManager) { this.bundleContext = bundleContext; this.authenticationManager = authenticationManager; // Equinox dependency ServiceTracker st = new ServiceTracker( bundleContext, ExtendedHttpService.class, null); st.open(); try { httpService = st.waitForService(1000); } catch (InterruptedException e) { httpService = null; } if (httpService == null) throw new CmsException("Could not find " + ExtendedHttpService.class + " service."); // Filters rootFilter = new RootFilter(); // DAV sessionProvider = new OpenInViewSessionProvider(); publicWebdavServlet = new WebdavServlet(node, sessionProvider); privateWebdavServlet = new WebdavServlet(node, sessionProvider); publicRemotingServlet = new RemotingServlet(node, sessionProvider); privateRemotingServlet = new RemotingServlet(node, sessionProvider); } void publish() { try { registerWebdavServlet(PATH_WEBDAV_PUBLIC, ALIAS_NODE, true, publicWebdavServlet); registerWebdavServlet(PATH_WEBDAV_PRIVATE, ALIAS_NODE, false, privateWebdavServlet); registerRemotingServlet(PATH_REMOTING_PUBLIC, ALIAS_NODE, true, publicRemotingServlet); registerRemotingServlet(PATH_REMOTING_PRIVATE, ALIAS_NODE, false, privateRemotingServlet); // httpService.registerFilter("/", rootFilter, null, null); } catch (Exception e) { throw new CmsException("Cannot publish HTTP services to OSGi", e); } } private void registerWebdavServlet(String pathPrefix, String alias, Boolean anonymous, WebdavServlet webdavServlet) throws NamespaceException, ServletException { String path = pathPrefix + "/" + alias; Properties initParameters = new Properties(); initParameters.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, KernelConstants.WEBDAV_CONFIG); initParameters.setProperty( WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); httpService.registerFilter(path, anonymous ? new AnonymousFilter() : new DavFilter(), null, null); // Cast to servlet because of a weird behaviour in Eclipse httpService.registerServlet(path, (Servlet) webdavServlet, initParameters, null); } private void registerRemotingServlet(String pathPrefix, String alias, Boolean anonymous, RemotingServlet remotingServlet) throws NamespaceException, ServletException { String path = pathPrefix + "/" + alias; Properties initParameters = new Properties(); initParameters.setProperty( RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); // Looks like a bug in Jackrabbit remoting init initParameters.setProperty(RemotingServlet.INIT_PARAM_HOME, KernelUtils.getOsgiInstanceDir(bundleContext) + "/tmp/jackrabbit"); initParameters.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting"); // Cast to servlet because of a weird behaviour in Eclipse httpService.registerFilter(path, anonymous ? new AnonymousFilter() : new DavFilter(), null, null); httpService.registerServlet(path, (Servlet) remotingServlet, initParameters, null); } private Boolean isSessionAuthenticated(HttpSession httpSession) { SecurityContext contextFromSession = (SecurityContext) httpSession .getAttribute(SPRING_SECURITY_CONTEXT_KEY); return contextFromSession != null; } private void requestBasicAuth(HttpSession httpSession, HttpServletResponse response) { response.setStatus(401); response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + httpAuthRealm + "\""); httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE); } private UsernamePasswordAuthenticationToken basicAuth(String authHeader) { if (authHeader != null) { StringTokenizer st = new StringTokenizer(authHeader); if (st.hasMoreTokens()) { String basic = st.nextToken(); if (basic.equalsIgnoreCase("Basic")) { try { String credentials = new String(Base64.decodeBase64(st .nextToken()), "UTF-8"); log.debug("Credentials: " + credentials); int p = credentials.indexOf(":"); if (p != -1) { String login = credentials.substring(0, p).trim(); String password = credentials.substring(p + 1) .trim(); return new UsernamePasswordAuthenticationToken( login, password); } else { throw new CmsException( "Invalid authentication token"); } } catch (Exception e) { throw new CmsException( "Couldn't retrieve authentication", e); } } } } throw new CmsException("Couldn't retrieve authentication"); } /** Intercepts all requests. Authenticates. */ class RootFilter extends HttpFilter { @Override public void doFilter(HttpSession httpSession, HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { // Authenticate from session if (isSessionAuthenticated(httpSession)) { filterChain.doFilter(request, response); return; } // TODO Kerberos // TODO Certificate // Process basic auth String basicAuth = request.getHeader(HEADER_AUTHORIZATION); if (basicAuth != null) { UsernamePasswordAuthenticationToken token = basicAuth(basicAuth); Authentication auth = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(auth); httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE); filterChain.doFilter(request, response); return; } Boolean doBasicAuth = true; if (doBasicAuth) { requestBasicAuth(httpSession, response); // skip filter chain return; } // TODO Login page // Anonymous KernelUtils.anonymousLogin(authenticationManager); filterChain.doFilter(request, response); } } /** Intercepts all requests. Authenticates. */ class AnonymousFilter extends HttpFilter { @Override public void doFilter(HttpSession httpSession, HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { // Authenticate from session if (isSessionAuthenticated(httpSession)) { filterChain.doFilter(request, response); return; } KernelUtils.anonymousLogin(authenticationManager); filterChain.doFilter(request, response); } } /** Intercepts all requests. Authenticates. */ class DavFilter extends HttpFilter { @Override public void doFilter(HttpSession httpSession, HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { // Authenticate from session if (isSessionAuthenticated(httpSession)) { filterChain.doFilter(request, response); return; } // Process basic auth String basicAuth = request.getHeader(HEADER_AUTHORIZATION); if (basicAuth != null) { UsernamePasswordAuthenticationToken token = basicAuth(basicAuth); Authentication auth = authenticationManager.authenticate(token); SecurityContextHolder.getContext().setAuthentication(auth); httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); httpSession.setAttribute(ATTR_AUTH, Boolean.FALSE); filterChain.doFilter(request, response); return; } requestBasicAuth(httpSession, response); } } }