X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FNodeHttp.java;h=6dc70144ba3ef6f7ee8f84cc8d189b5647a54c42;hb=92ac99f3ededbcd28def2bf9601bb33c02a351b3;hp=928afd3a302836d28bf6d45b6ce8a3e0441b348f;hpb=04dade003a08c5915280ae653639b0af7cddeb0c;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java index 928afd3a3..6dc70144b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -1,9 +1,25 @@ package org.argeo.cms.internal.kernel; +import static org.argeo.cms.KernelHeader.ACCESS_CONTROL_CONTEXT; + import java.io.IOException; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.cert.X509Certificate; +import java.util.Enumeration; import java.util.Properties; import java.util.StringTokenizer; +import javax.jcr.Repository; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; import javax.servlet.FilterChain; import javax.servlet.Servlet; import javax.servlet.ServletException; @@ -15,19 +31,13 @@ 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.KernelHeader; 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 @@ -40,112 +50,102 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { private final static String HEADER_AUTHORIZATION = "Authorization"; private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - private final AuthenticationManager authenticationManager; - private final BundleContext bundleContext; - private ExtendedHttpService httpService; + // private final AuthenticationManager authenticationManager; + private final ExtendedHttpService httpService; // FIXME Make it more unique private String httpAuthRealm = "Argeo"; // Filters - // private final RootFilter rootFilter; + private final RootFilter rootFilter; + // private final DoSFilter dosFilter; + // private final QoSFilter qosFilter; - // remoting + // WebDav / JCR 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."); + NodeHttp(ExtendedHttpService httpService, JackrabbitNode node) { + // this.bundleContext = bundleContext; + // this.authenticationManager = authenticationManager; + + this.httpService = httpService; // Filters - // rootFilter = new RootFilter(); + rootFilter = new RootFilter(); + // dosFilter = new CustomDosFilter(); + // qosFilter = new QoSFilter(); // DAV sessionProvider = new OpenInViewSessionProvider(); - publicWebdavServlet = new WebdavServlet(node, sessionProvider); - privateWebdavServlet = new WebdavServlet(node, sessionProvider); - publicRemotingServlet = new RemotingServlet(node, sessionProvider); - privateRemotingServlet = new RemotingServlet(node, sessionProvider); + + registerRepositoryServlets(ALIAS_NODE, node); + try { + httpService.registerFilter("/", rootFilter, null, null); + } catch (Exception e) { + throw new CmsException("Could not register root filter", e); + } + } + + public void destroy() { + sessionProvider.destroy(); + unregisterRepositoryServlets(ALIAS_NODE); } - void publish() { + void registerRepositoryServlets(String alias, Repository repository) { 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); + registerWebdavServlet(alias, repository, true); + registerWebdavServlet(alias, repository, false); + registerRemotingServlet(alias, repository, true); + registerRemotingServlet(alias, repository, false); } catch (Exception e) { - throw new CmsException("Cannot publish HTTP services to OSGi", e); + throw new CmsException( + "Could not register servlets for repository " + alias, e); } } - private void registerWebdavServlet(String pathPrefix, String alias, - Boolean anonymous, WebdavServlet webdavServlet) - throws NamespaceException, ServletException { + void unregisterRepositoryServlets(String alias) { + // FIXME unregister servlets + } + + void registerWebdavServlet(String alias, Repository repository, + boolean anonymous) throws NamespaceException, ServletException { + WebdavServlet webdavServlet = new WebdavServlet(repository, + sessionProvider); + String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE; 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); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG); + ip.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); + httpService.registerServlet(path, (Servlet) webdavServlet, ip, null); } - private void registerRemotingServlet(String pathPrefix, String alias, - Boolean anonymous, RemotingServlet remotingServlet) - throws NamespaceException, ServletException { + void registerRemotingServlet(String alias, Repository repository, + boolean anonymous) throws NamespaceException, ServletException { + String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE; + RemotingServlet remotingServlet = new RemotingServlet(repository, + sessionProvider); String path = pathPrefix + "/" + alias; - Properties initParameters = new Properties(); - initParameters.setProperty( - RemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + Properties ip = new Properties(); + ip.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"); + ip.setProperty(RemotingServlet.INIT_PARAM_HOME, + KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit"); + ip.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); + httpService.registerServlet(path, (Servlet) remotingServlet, ip, null); } - private Boolean isSessionAuthenticated(HttpSession httpSession) { - SecurityContext contextFromSession = (SecurityContext) httpSession - .getAttribute(SPRING_SECURITY_CONTEXT_KEY); - return contextFromSession != 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) { @@ -155,24 +155,35 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE); } - private UsernamePasswordAuthenticationToken basicAuth(String authHeader) { + private CallbackHandler basicAuth(String authHeader) { if (authHeader != null) { StringTokenizer st = new StringTokenizer(authHeader); if (st.hasMoreTokens()) { String basic = st.nextToken(); if (basic.equalsIgnoreCase("Basic")) { try { + // TODO manipulate char[] String credentials = new String(Base64.decodeBase64(st .nextToken()), "UTF-8"); - log.debug("Credentials: " + credentials); + // log.debug("Credentials: " + credentials); int p = credentials.indexOf(":"); if (p != -1) { - String login = credentials.substring(0, p).trim(); - String password = credentials.substring(p + 1) + final String login = credentials.substring(0, p) .trim(); - - return new UsernamePasswordAuthenticationToken( - login, password); + final char[] password = credentials + .substring(p + 1).trim().toCharArray(); + + return new CallbackHandler() { + public void handle(Callback[] callbacks) { + for (Callback cb : callbacks) { + if (cb instanceof NameCallback) + ((NameCallback) cb).setName(login); + else if (cb instanceof PasswordCallback) + ((PasswordCallback) cb) + .setPassword(password); + } + } + }; } else { throw new CmsException( "Invalid authentication token"); @@ -194,92 +205,196 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { 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; + if (log.isTraceEnabled()) { + log.trace(request.getRequestURL().append( + request.getQueryString() != null ? "?" + + request.getQueryString() : "")); + logRequest(request); } - // TODO Kerberos + String servletPath = request.getServletPath(); - // TODO Certificate + // client certificate + X509Certificate clientCert = extractCertificate(request); + if (clientCert != null) { + // TODO authenticate + // if (log.isDebugEnabled()) + // log.debug(clientCert.getSubjectX500Principal().getName()); + } - // 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); + // skip data + if (servletPath.startsWith(PATH_DATA)) { filterChain.doFilter(request, response); return; } - Boolean doBasicAuth = true; - if (doBasicAuth) { - requestBasicAuth(httpSession, response); - // skip filter chain + // skip /ui (workbench) for the time being + if (servletPath.startsWith(PATH_WORKBENCH)) { + filterChain.doFilter(request, response); return; } - // TODO Login page + // redirect long RWT paths to anchor + String path = request.getRequestURI().substring( + servletPath.length()); + int pathLength = path.length(); + if (pathLength != 0 && (path.charAt(0) == '/') + && !servletPath.endsWith("rwt-resources") + && path.lastIndexOf('/') != 0) { + String newLocation = request.getServletPath() + "#" + path; + response.setHeader("Location", newLocation); + response.setStatus(HttpServletResponse.SC_FOUND); + return; + } - // Anonymous - KernelUtils.anonymousLogin(authenticationManager); + // process normally filterChain.doFilter(request, response); } } + private void logRequest(HttpServletRequest request) { + log.debug("contextPath=" + request.getContextPath()); + log.debug("servletPath=" + request.getServletPath()); + log.debug("requestURI=" + request.getRequestURI()); + log.debug("queryString=" + request.getQueryString()); + StringBuilder buf = new StringBuilder(); + // headers + Enumeration en = request.getHeaderNames(); + while (en.hasMoreElements()) { + String header = en.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + buf.append(" " + header + ": " + values.nextElement()); + buf.append('\n'); + } + + // attributed + Enumeration an = request.getAttributeNames(); + while (an.hasMoreElements()) { + String attr = an.nextElement(); + Object value = request.getAttribute(attr); + buf.append(" " + attr + ": " + value); + buf.append('\n'); + } + log.debug("\n" + buf); + } + + private X509Certificate extractCertificate(HttpServletRequest req) { + X509Certificate[] certs = (X509Certificate[]) req + .getAttribute("javax.servlet.request.X509Certificate"); + if (null != certs && certs.length > 0) { + return certs[0]; + } + return null; + } + /** Intercepts all requests. Authenticates. */ - class AnonymousFilter extends HttpFilter { + private class AnonymousFilter extends HttpFilter { @Override public void doFilter(HttpSession httpSession, - HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws IOException, ServletException { + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) throws IOException, + ServletException { // Authenticate from session - if (isSessionAuthenticated(httpSession)) { - filterChain.doFilter(request, response); - return; + // if (isSessionAuthenticated(httpSession)) { + // filterChain.doFilter(request, response); + // return; + // } + + Subject subject = KernelUtils.anonymousLogin(); + try { + Subject.doAs(subject, new PrivilegedExceptionAction() { + public Void run() throws IOException, ServletException { + filterChain.doFilter(request, response); + return null; + } + }); + } catch (PrivilegedActionException e) { + if (e.getCause() instanceof ServletException) + throw (ServletException) e.getCause(); + else if (e.getCause() instanceof IOException) + throw (IOException) e.getCause(); + else + throw new CmsException("Unexpected exception", e.getCause()); } - - KernelUtils.anonymousLogin(authenticationManager); - filterChain.doFilter(request, response); } } /** Intercepts all requests. Authenticates. */ - class DavFilter extends HttpFilter { + private 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; + public void doFilter(final HttpSession httpSession, + final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) throws IOException, + ServletException { + + AccessControlContext acc = (AccessControlContext) httpSession + .getAttribute(KernelHeader.ACCESS_CONTROL_CONTEXT); + final Subject subject; + if (acc != null) { + subject = Subject.getSubject(acc); + } else { + // Process basic auth + String basicAuth = request.getHeader(HEADER_AUTHORIZATION); + if (basicAuth != null) { + CallbackHandler token = basicAuth(basicAuth); + try { + LoginContext lc = new LoginContext( + KernelHeader.LOGIN_CONTEXT_USER, token); + lc.login(); + subject = lc.getSubject(); + } catch (LoginException e) { + throw new CmsException("Could not login", e); + } + } else { + requestBasicAuth(httpSession, 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; + // do filter as subject + try { + Subject.doAs(subject, + new PrivilegedExceptionAction() { + public Void run() throws IOException, + ServletException { + // add security context to session + httpSession.setAttribute( + ACCESS_CONTROL_CONTEXT, + AccessController.getContext()); + filterChain.doFilter(request, response); + return null; + } + }); + } catch (PrivilegedActionException e) { + if (e.getCause() instanceof ServletException) + throw (ServletException) e.getCause(); + else if (e.getCause() instanceof IOException) + throw (IOException) e.getCause(); + else + throw new CmsException("Unexpected exception", + e.getCause()); } - requestBasicAuth(httpSession, response); } } + // class CustomDosFilter extends DoSFilter { + // @Override + // protected String extractUserId(ServletRequest request) { + // HttpSession httpSession = ((HttpServletRequest) request) + // .getSession(); + // if (isSessionAuthenticated(httpSession)) { + // String userId = ((SecurityContext) httpSession + // .getAttribute(SPRING_SECURITY_CONTEXT_KEY)) + // .getAuthentication().getName(); + // return userId; + // } + // return super.extractUserId(request); + // + // } + // } }