X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.cms%2Fsrc%2Forg%2Fargeo%2Fcms%2Finternal%2Fkernel%2FDataHttp.java;h=bd444466871bcf3d8be4c9efc2e4f30c20e6b6a5;hb=b8da6ff850049dd39531c1e50f2eef38c4e3298e;hp=d9d9c84808bf27d8b143e359c86a6659a0dff21e;hpb=12809b284d8daacd3284ba6e9336c863067681af;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java index d9d9c8480..bd4444668 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java @@ -34,6 +34,12 @@ import org.argeo.cms.auth.HttpRequestCallback; import org.argeo.cms.auth.HttpRequestCallbackHandler; import org.argeo.jcr.JcrUtils; import org.argeo.node.NodeConstants; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -64,12 +70,8 @@ class DataHttp implements KernelConstants { // FIXME Make it more unique private String httpAuthRealm = "Argeo"; - // WebDav / JCR remoting - private OpenInViewSessionProvider sessionProvider; - DataHttp(HttpService httpService) { this.bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - sessionProvider = new OpenInViewSessionProvider(); this.httpService = httpService; repositories = new ServiceTracker<>(bc, Repository.class, new RepositoriesStc()); repositories.open(); @@ -82,9 +84,8 @@ class DataHttp implements KernelConstants { void registerRepositoryServlets(String alias, Repository repository) { try { registerWebdavServlet(alias, repository); - // registerWebdavServlet(alias, repository, false); - registerRemotingServlet(alias, repository, true); - registerRemotingServlet(alias, repository, false); + registerRemotingServlet(alias, repository); + registerFilesServlet(alias, repository); if (log.isDebugEnabled()) log.debug("Registered servlets for repository '" + alias + "'"); } catch (Exception e) { @@ -95,9 +96,8 @@ class DataHttp implements KernelConstants { void unregisterRepositoryServlets(String alias) { try { httpService.unregister(webdavPath(alias)); - // httpService.unregister(webdavPath(alias, false)); - httpService.unregister(remotingPath(alias, true)); - httpService.unregister(remotingPath(alias, false)); + httpService.unregister(remotingPath(alias)); + httpService.unregister(filesPath(alias)); if (log.isDebugEnabled()) log.debug("Unregistered servlets for repository '" + alias + "'"); } catch (Exception e) { @@ -106,7 +106,7 @@ class DataHttp implements KernelConstants { } void registerWebdavServlet(String alias, Repository repository) throws NamespaceException, ServletException { - WebdavServlet webdavServlet = new WebdavServlet(repository, sessionProvider); + WebdavServlet webdavServlet = new WebdavServlet(repository, new OpenInViewSessionProvider(alias)); String path = webdavPath(alias); Properties ip = new Properties(); ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG); @@ -114,39 +114,48 @@ class DataHttp implements KernelConstants { httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext()); } - void registerRemotingServlet(String alias, Repository repository, boolean anonymous) - throws NamespaceException, ServletException { - RemotingServlet remotingServlet = new RemotingServlet(repository, sessionProvider); - String path = remotingPath(alias, anonymous); + void registerFilesServlet(String alias, Repository repository) throws NamespaceException, ServletException { + WebdavServlet filesServlet = new WebdavServlet(repository, new OpenInViewSessionProvider(alias)); + String path = filesPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, filesServlet, ip, new FilesHttpContext()); + } + + void registerRemotingServlet(String alias, Repository repository) throws NamespaceException, ServletException { + RemotingServlet remotingServlet = new RemotingServlet(repository, new OpenInViewSessionProvider(alias)); + String path = remotingPath(alias); Properties ip = new Properties(); ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); // Looks like a bug in Jackrabbit remoting init - ip.setProperty(RemotingServlet.INIT_PARAM_HOME, KernelUtils.getOsgiInstanceDir() + "/tmp/remoting_"+alias); - ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting_"+alias); + ip.setProperty(RemotingServlet.INIT_PARAM_HOME, KernelUtils.getOsgiInstanceDir() + "/tmp/remoting_" + alias); + ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting_" + alias); ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, DEFAULT_PROTECTED_HANDLERS); ip.setProperty(RemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); - httpService.registerServlet(path, remotingServlet, ip, new RemotingHttpContext(anonymous)); + httpService.registerServlet(path, remotingServlet, ip, new RemotingHttpContext()); } private String webdavPath(String alias) { return NodeConstants.PATH_DATA + "/" + alias; - // String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE; - // return pathPrefix + "/" + alias; } - private String remotingPath(String alias, boolean anonymous) { - String pathPrefix = anonymous ? NodeConstants.PATH_JCR_PUB : NodeConstants.PATH_JCR; - return pathPrefix + "/" + alias; + private String remotingPath(String alias) { + return NodeConstants.PATH_JCR + "/" + alias; + } + + private String filesPath(String alias) { + return NodeConstants.PATH_FILES + "/" + alias; } - private Subject subjectFromRequest(HttpServletRequest request) { + private Subject subjectFromRequest(HttpServletRequest request, HttpServletResponse response) { Authorization authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION); if (authorization == null) throw new CmsException("Not authenticated"); try { LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, - new HttpRequestCallbackHandler(request)); + new HttpRequestCallbackHandler(request, response)); lc.login(); return lc.getSubject(); } catch (LoginException e) { @@ -154,6 +163,101 @@ class DataHttp implements KernelConstants { } } + private void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(401); + response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + httpAuthRealm + "\""); + + // SPNEGO + // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate"); + // response.setDateHeader("Date", System.currentTimeMillis()); + // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * + // 60 * 60 * 1000)); + // response.setHeader("Accept-Ranges", "bytes"); + // response.setHeader("Connection", "Keep-Alive"); + // response.setHeader("Keep-Alive", "timeout=5, max=97"); + // response.setContentType("text/html; charset=UTF-8"); + + } + + private CallbackHandler extractHttpAuth(final HttpServletRequest httpRequest, HttpServletResponse httpResponse) { + String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION); + 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); + int p = credentials.indexOf(":"); + if (p != -1) { + final String login = credentials.substring(0, p).trim(); + 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 if (cb instanceof HttpRequestCallback) { + ((HttpRequestCallback) cb).setRequest(httpRequest); + ((HttpRequestCallback) cb).setResponse(httpResponse); + } + } + } + }; + } else { + throw new CmsException("Invalid authentication token"); + } + } catch (Exception e) { + throw new CmsException("Couldn't retrieve authentication", e); + } + } else if (basic.equalsIgnoreCase("Negotiate")) { + // FIXME generalise + String _targetName = "HTTP/mostar.desktop.argeo.pro"; + String spnegoToken = st.nextToken(); + byte[] authToken = Base64.decodeBase64(spnegoToken); + GSSManager manager = GSSManager.getInstance(); + try { + Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html + GSSName gssName = manager.createName(_targetName, null); + GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, + krb5Oid, GSSCredential.ACCEPT_ONLY); + GSSContext gContext = manager.createContext(serverCreds); + + if (gContext == null) { + log.debug("SpnegoUserRealm: failed to establish GSSContext"); + } else { + while (!gContext.isEstablished()) { + byte[] outToken = gContext.acceptSecContext(authToken, 0, authToken.length); + String outTokenStr = Base64.encodeBase64String(outToken); + httpResponse.setHeader("WWW-Authenticate", "Negotiate " + outTokenStr); + } + if (gContext.isEstablished()) { + String clientName = gContext.getSrcName().toString(); + String role = clientName.substring(clientName.indexOf('@') + 1); + + log.debug("SpnegoUserRealm: established a security context"); + log.debug("Client Principal is: " + gContext.getSrcName()); + log.debug("Server Principal is: " + gContext.getTargName()); + log.debug("Client Default Role: " + role); + + // TODO log in + } + } + + } catch (GSSException gsse) { + log.warn(gsse, gsse); + } + + } + } + } + return null; + } + private class RepositoriesStc implements ServiceTrackerCustomizer { @Override @@ -182,81 +286,87 @@ class DataHttp implements KernelConstants { } private class DataHttpContext implements HttpContext { - // private final boolean anonymous; + @Override + public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) + throws IOException { - DataHttpContext() { - // this.anonymous = anonymous; + if (log.isTraceEnabled()) + KernelUtils.logRequestHeaders(log, request); + LoginContext lc; + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + new HttpRequestCallbackHandler(request, response)); + lc.login(); + // return true; + } catch (LoginException e) { + CallbackHandler token = extractHttpAuth(request, response); + if (token != null) { + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token); + lc.login(); + // Note: this is impossible to reliably clear the + // authorization header when access from a browser. + return true; + } catch (LoginException e1) { + throw new CmsException("Could not login", e1); + } + } else { + // anonymous + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER); + lc.login(); + } catch (LoginException e1) { + if (log.isDebugEnabled()) + log.error("Cannot log in anonynous", e1); + return false; + } + } + } + request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc); + return true; } @Override - public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) - throws IOException { + public URL getResource(String name) { + return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name); + } - // optimization - // HttpSession httpSession = request.getSession(); - // Object remoteUser = httpSession.getAttribute(REMOTE_USER); - // Object authorization = httpSession.getAttribute(AUTHORIZATION); - // if (remoteUser != null && authorization != null) { - // request.setAttribute(REMOTE_USER, remoteUser); - // request.setAttribute(AUTHORIZATION, authorization); - // return true; - // } + @Override + public String getMimeType(String name) { + return null; + } - // if (anonymous) { - // Subject subject = KernelUtils.anonymousLogin(); - // Authorization authorization = - // subject.getPrivateCredentials(Authorization.class).iterator().next(); - // request.setAttribute(REMOTE_USER, NodeConstants.ROLE_ANONYMOUS); - // request.setAttribute(AUTHORIZATION, authorization); - // return true; - // } + } - // if (log.isTraceEnabled()) - // KernelUtils.logRequestHeaders(log, request); + private class FilesHttpContext implements HttpContext { + @Override + public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) + throws IOException { + + if (log.isTraceEnabled()) + KernelUtils.logRequestHeaders(log, request); LoginContext lc; try { - lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request)); + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + new HttpRequestCallbackHandler(request, response)); lc.login(); // return true; - } catch (CredentialNotFoundException e) { - try { - lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER); - lc.login(); - } catch (LoginException e1) { - if (log.isDebugEnabled()) - log.error("Cannot log in anonynous", e1); + } catch (LoginException e) { + CallbackHandler token = extractHttpAuth(request, response); + if (token != null) { + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token); + lc.login(); + // Note: this is impossible to reliably clear the + // authorization header when access from a browser. + } catch (LoginException e1) { + throw new CmsException("Could not login", e1); + } + } else { + askForWwwAuth(request, response); + lc = null; return false; } - // Subject subject = KernelUtils.anonymousLogin(); - // authorization = - // subject.getPrivateCredentials(Authorization.class).iterator().next(); - // request.setAttribute(REMOTE_USER, - // NodeConstants.ROLE_ANONYMOUS); - // request.setAttribute(AUTHORIZATION, authorization); - // httpSession.setAttribute(REMOTE_USER, - // NodeConstants.ROLE_ANONYMOUS); - // httpSession.setAttribute(AUTHORIZATION, authorization); - // return true; - // CallbackHandler token = basicAuth(request); - // if (token != null) { - // try { - // LoginContext lc = new - // LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token); - // lc.login(); - // // Note: this is impossible to reliably clear the - // // authorization header when access from a browser. - // return true; - // } catch (LoginException e1) { - // throw new CmsException("Could not login", e1); - // } - // } else { - // String path = request.getServletPath(); - // if (path.startsWith(REMOTING_PRIVATE)) - // requestBasicAuth(request, response); - // return false; - // } - } catch (LoginException e) { - throw new CmsException("Could not login", e); } request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc); return true; @@ -275,48 +385,57 @@ class DataHttp implements KernelConstants { } private class RemotingHttpContext implements HttpContext { - private final boolean anonymous; + // private final boolean anonymous; - RemotingHttpContext(boolean anonymous) { - this.anonymous = anonymous; + RemotingHttpContext() { + // this.anonymous = anonymous; } @Override public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) throws IOException { - if (anonymous) { - Subject subject = KernelUtils.anonymousLogin(); - Authorization authorization = subject.getPrivateCredentials(Authorization.class).iterator().next(); - request.setAttribute(REMOTE_USER, NodeConstants.ROLE_ANONYMOUS); - request.setAttribute(AUTHORIZATION, authorization); - return true; - } + // if (anonymous) { + // Subject subject = KernelUtils.anonymousLogin(); + // Authorization authorization = + // subject.getPrivateCredentials(Authorization.class).iterator().next(); + // request.setAttribute(REMOTE_USER, NodeConstants.ROLE_ANONYMOUS); + // request.setAttribute(AUTHORIZATION, authorization); + // return true; + // } if (log.isTraceEnabled()) KernelUtils.logRequestHeaders(log, request); + LoginContext lc; try { - new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request)).login(); - return true; + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + new HttpRequestCallbackHandler(request, response)); + lc.login(); } catch (CredentialNotFoundException e) { - CallbackHandler token = basicAuth(request); + CallbackHandler token = extractHttpAuth(request, response); if (token != null) { try { - LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token); + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token); lc.login(); // Note: this is impossible to reliably clear the // authorization header when access from a browser. - return true; } catch (LoginException e1) { throw new CmsException("Could not login", e1); } } else { - requestBasicAuth(request, response); - return false; + askForWwwAuth(request, response); + lc = null; } } catch (LoginException e) { throw new CmsException("Could not login", e); } + + if (lc != null) { + request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc); + return true; + } else { + return false; + } } @Override @@ -329,51 +448,6 @@ class DataHttp implements KernelConstants { return null; } - private void requestBasicAuth(HttpServletRequest request, HttpServletResponse response) { - response.setStatus(401); - response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + httpAuthRealm + "\""); - // request.getSession().setAttribute(ATTR_AUTH, Boolean.TRUE); - } - - private CallbackHandler basicAuth(final HttpServletRequest httpRequest) { - String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION); - 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); - int p = credentials.indexOf(":"); - if (p != -1) { - final String login = credentials.substring(0, p).trim(); - 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 if (cb instanceof HttpRequestCallback) - ((HttpRequestCallback) cb).setRequest(httpRequest); - } - } - }; - } else { - throw new CmsException("Invalid authentication token"); - } - } catch (Exception e) { - throw new CmsException("Couldn't retrieve authentication", e); - } - } - } - } - return null; - } - } /** @@ -382,6 +456,11 @@ class DataHttp implements KernelConstants { */ private class OpenInViewSessionProvider implements SessionProvider, Serializable { private static final long serialVersionUID = 2270957712453841368L; + private final String alias; + + public OpenInViewSessionProvider(String alias) { + this.alias = alias; + } public Session getSession(HttpServletRequest request, Repository rep, String workspace) throws javax.jcr.LoginException, ServletException, RepositoryException { @@ -391,14 +470,16 @@ class DataHttp implements KernelConstants { protected Session login(HttpServletRequest request, Repository repository, String workspace) throws RepositoryException { if (log.isTraceEnabled()) - log.trace("Login to workspace " + (workspace == null ? "" : workspace) + " in web session " - + request.getSession().getId()); -// LoginContext lc = (LoginContext) request.getAttribute(NodeConstants.LOGIN_CONTEXT_USER); -// if (lc == null) -// throw new CmsException("No login context available"); + log.trace("Repo " + alias + ", login to workspace " + (workspace == null ? "" : workspace) + + " in web session " + request.getSession().getId()); + LoginContext lc = (LoginContext) request.getAttribute(NodeConstants.LOGIN_CONTEXT_USER); + if (lc == null) + throw new CmsException("No login context available"); try { - LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request)); - lc.login(); + // LoginContext lc = new + // LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + // new HttpRequestCallbackHandler(request)); + // lc.login(); return Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction() { @Override public Session run() throws Exception { @@ -482,7 +563,7 @@ class DataHttp implements KernelConstants { protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { try { - Subject subject = subjectFromRequest(request); + Subject subject = subjectFromRequest(request, response); Subject.doAs(subject, new PrivilegedExceptionAction() { @Override public Void run() throws Exception {