From: Mathieu Baudier Date: Thu, 9 Feb 2017 15:44:30 +0000 (+0100) Subject: Refactor http X-Git-Tag: argeo-commons-2.1.60~11 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=02a6354c17ddb160513580e9e3c7826d9475b177 Refactor http --- diff --git a/demo/init/node/.gitignore b/demo/init/node/.gitignore index 288267a14..07b78c238 100644 --- a/demo/init/node/.gitignore +++ b/demo/init/node/.gitignore @@ -1 +1,2 @@ /krb5.keytab +/krb5.keytab.old diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index 2432d9c0a..6dae68d1d 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -3,6 +3,7 @@ package org.argeo.cms.auth; import java.security.Principal; import java.util.Collection; import java.util.Set; +import java.util.UUID; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; @@ -17,8 +18,9 @@ import org.apache.commons.logging.LogFactory; //import org.apache.jackrabbit.core.security.SecurityConstants; //import org.apache.jackrabbit.core.security.principal.AdminPrincipal; import org.argeo.cms.CmsException; +import org.argeo.cms.internal.auth.CmsSessionImpl; import org.argeo.cms.internal.auth.ImpliedByPrincipal; -import org.argeo.cms.internal.kernel.WebCmsSessionImpl; +import org.argeo.cms.internal.http.WebCmsSessionImpl; import org.argeo.node.security.AnonymousPrincipal; import org.argeo.node.security.DataAdminPrincipal; import org.argeo.node.security.NodeSecurityUtils; @@ -121,78 +123,92 @@ class CmsAuthUtils { static void registerSessionAuthorization(BundleContext bc, HttpServletRequest request, Subject subject, Authorization authorization) { - String httpSessId = request.getSession().getId(); + HttpSession httpSession = request.getSession(); + String httpSessId = httpSession.getId(); if (authorization.getName() != null) { request.setAttribute(HttpContext.REMOTE_USER, authorization.getName()); request.setAttribute(HttpContext.AUTHORIZATION, authorization); - HttpSession httpSession = request.getSession(); - if (httpSession.getAttribute(HttpContext.AUTHORIZATION) == null) { - - Collection> sr; - try { - sr = bc.getServiceReferences(WebCmsSession.class, - "(" + WebCmsSession.CMS_SESSION_ID + "=" + httpSessId + ")"); - } catch (InvalidSyntaxException e) { - throw new CmsException("Cannot get CMS session for id " + httpSessId, e); - } - ServiceReference cmsSessionRef; - if (sr.size() == 1) { - cmsSessionRef = sr.iterator().next(); - } else if (sr.size() == 0) { - WebCmsSessionImpl cmsSessionImpl = new WebCmsSessionImpl(httpSessId, authorization); - cmsSessionRef = cmsSessionImpl.getServiceRegistration().getReference(); - if (log.isDebugEnabled()) - log.debug("Initialized " + cmsSessionImpl + " for " + authorization.getName()); - } else - throw new CmsException(sr.size() + " CMS sessions registered for " + httpSessId); - - WebCmsSessionImpl cmsSession = (WebCmsSessionImpl) bc.getService(cmsSessionRef); - cmsSession.addHttpSession(request); - if (log.isTraceEnabled()) - log.trace("Added " + request.getServletPath() + " to " + cmsSession + " (" + request.getRequestURI() - + ")"); - // httpSession.setAttribute(HttpContext.REMOTE_USER, - // authorization.getName()); - // httpSession.setAttribute(HttpContext.AUTHORIZATION, - // authorization); + CmsSession cmsSession = CmsSessionImpl.getByLocalId(httpSessId); + if (cmsSession == null) + cmsSession = new WebCmsSessionImpl(subject, authorization, httpSessId); + request.setAttribute(CmsSession.class.getName(), cmsSession); + // else + // throw new CmsException("Already a CMS session registered for + // "+httpSessId); + + // if (httpSession.getAttribute(HttpContext.AUTHORIZATION) == null) + // { + + // Collection> sr; + // try { + // sr = bc.getServiceReferences(CmsSession.class, + // "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessId + ")"); + // } catch (InvalidSyntaxException e) { + // throw new CmsException("Cannot get CMS session for id " + + // httpSessId, e); + // } + // ServiceReference cmsSessionRef; + // if (sr.size() == 1) { + // cmsSessionRef = sr.iterator().next(); + // } else if (sr.size() == 0) { + // WebCmsSessionImpl cmsSessionImpl = new WebCmsSessionImpl(subject, + // authorization, httpSessId); + // cmsSessionRef = + // cmsSessionImpl.getServiceRegistration().getReference(); + // if (log.isDebugEnabled()) + // log.debug("Initialized " + cmsSessionImpl + " for " + + // authorization.getName()); + // } else + // throw new CmsException(sr.size() + " CMS sessions registered for + // " + httpSessId); + // + // cmsSession = (CmsSession) bc.getService(cmsSessionRef); + // cmsSession.addHttpSession(request); + // if (log.isTraceEnabled()) + // log.trace("Added " + request.getServletPath() + " to " + + // cmsSession + " (" + request.getRequestURI() + // + ")"); + // httpSession.setAttribute(HttpContext.REMOTE_USER, + // authorization.getName()); + // httpSession.setAttribute(HttpContext.AUTHORIZATION, + // authorization); + CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid()); + if (subject.getPrivateCredentials(CmsSessionId.class).size() == 0) + subject.getPrivateCredentials().add(nodeSessionId); + else { + UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); + // if (storedSessionId.equals(httpSessionId.getValue())) + throw new CmsException( + "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")"); } } - HttpSessionId httpSessionId = new HttpSessionId(httpSessId); - if (subject.getPrivateCredentials(HttpSessionId.class).size() == 0) - subject.getPrivateCredentials().add(httpSessionId); - else { - String storedSessionId = subject.getPrivateCredentials(HttpSessionId.class).iterator().next().getValue(); - // if (storedSessionId.equals(httpSessionId.getValue())) - throw new CmsException( - "Subject already logged with session " + storedSessionId + " (not " + httpSessionId + ")"); - } + // } } static boolean logoutSession(BundleContext bc, Subject subject) { - String httpSessionId; - if (subject.getPrivateCredentials(HttpSessionId.class).size() == 1) - httpSessionId = subject.getPrivateCredentials(HttpSessionId.class).iterator().next().getValue(); + UUID nodeSessionId; + if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1) + nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid(); else return false; - Collection> srs; + Collection> srs; try { - srs = bc.getServiceReferences(WebCmsSession.class, - "(" + WebCmsSession.CMS_SESSION_ID + "=" + httpSessionId + ")"); + srs = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + nodeSessionId + ")"); } catch (InvalidSyntaxException e) { - throw new CmsException("Cannot retrieve CMS session #" + httpSessionId, e); + throw new CmsException("Cannot retrieve CMS session #" + nodeSessionId, e); } if (srs.size() == 0) { if (log.isTraceEnabled()) - log.warn("No CMS web session found for http session " + httpSessionId); + log.warn("No CMS web session found for http session " + nodeSessionId); return false; } else if (srs.size() > 1) - throw new CmsException(srs.size() + " CMS web sessions found for http session " + httpSessionId); + throw new CmsException(srs.size() + " CMS web sessions found for http session " + nodeSessionId); WebCmsSessionImpl cmsSession = (WebCmsSessionImpl) bc.getService(srs.iterator().next()); cmsSession.cleanUp(); - subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(HttpSessionId.class)); + subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(CmsSessionId.class)); if (log.isDebugEnabled()) log.debug("Cleaned up " + cmsSession); return true; diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java new file mode 100644 index 000000000..14d6d71f6 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java @@ -0,0 +1,34 @@ +package org.argeo.cms.auth; + +import java.util.UUID; + +import javax.jcr.Repository; +import javax.jcr.Session; +import javax.naming.ldap.LdapName; + +import org.argeo.naming.LdapAttrs; +import org.osgi.service.useradmin.Authorization; + +public interface CmsSession { + public final static String USER_DN = LdapAttrs.DN; + public final static String SESSION_UUID = LdapAttrs.entryUUID.name(); + public final static String SESSION_LOCAL_ID = LdapAttrs.uniqueIdentifier.name(); + + // public String getId(); + + public UUID getUuid(); + + public LdapName getUserDn(); + + public String getLocalId(); + + public Authorization getAuthorization(); + + public Session getDataSession(String cn, String workspace, Repository repository); + + public void releaseDataSession(String cn, Session session); + + // public void addHttpSession(HttpServletRequest request); + + // public void cleanUp(); +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java new file mode 100644 index 000000000..875328974 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java @@ -0,0 +1,35 @@ +package org.argeo.cms.auth; + +import java.util.UUID; + +import org.argeo.cms.CmsException; + +public class CmsSessionId { + private final UUID uuid; + + public CmsSessionId(UUID value) { + if (value == null) + throw new CmsException("value cannot be null"); + this.uuid = value; + } + + public UUID getUuid() { + return uuid; + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof CmsSessionId && ((CmsSessionId) obj).getUuid().equals(uuid); + } + + @Override + public String toString() { + return "Node Session " + uuid; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java deleted file mode 100644 index 9f972ded7..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.auth; - -import org.argeo.cms.CmsException; - -public class HttpSessionId { - private final String value; - - public HttpSessionId(String value) { - if (value == null) - throw new CmsException("value cannot be null"); - this.value = value; - } - - public String getValue() { - return value; - } - - @Override - public int hashCode() { - return value.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof HttpSessionId && ((HttpSessionId) obj).getValue().equals(value); - } - - @Override - public String toString() { - return "HttpSessionId #" + value; - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java index 382d8fe4a..be8a693ba 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java @@ -72,15 +72,15 @@ public class HttpSessionLoginModule implements LoginModule { // authorization = (Authorization) // request.getSession().getAttribute(HttpContext.AUTHORIZATION); // if (authorization == null) { - Collection> sr; + Collection> sr; try { - sr = bc.getServiceReferences(WebCmsSession.class, - "(" + WebCmsSession.CMS_SESSION_ID + "=" + httpSessionId + ")"); + sr = bc.getServiceReferences(CmsSession.class, + "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")"); } catch (InvalidSyntaxException e) { throw new CmsException("Cannot get CMS session for id " + httpSessionId, e); } if (sr.size() == 1) { - WebCmsSession cmsSession = bc.getService(sr.iterator().next()); + CmsSession cmsSession = bc.getService(sr.iterator().next()); authorization = cmsSession.getAuthorization(); if (log.isTraceEnabled()) log.trace("Retrieved authorization from " + cmsSession); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index f5883a54f..b6ab04b21 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -71,11 +71,12 @@ public class UserAdminLoginModule implements LoginModule { final String username; final char[] password; - if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME) + if (callbackHandler == null && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME) && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_PWD)) { username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME); password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD); // TODO locale? + // NB: raw user name is used AuthenticatingUser authenticatingUser = new AuthenticatingUser(username, password); authorization = userAdmin.getAuthorization(authenticatingUser); } else { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java b/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java deleted file mode 100644 index 3e2eb2447..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.cms.auth; - -import org.osgi.service.useradmin.Authorization; - -public interface WebCmsSession { - public final static String CMS_DN = "cms.dn"; - public final static String CMS_SESSION_ID = "cms.sessionId"; - -// public String getId(); - - public Authorization getAuthorization(); - -// public void addHttpSession(HttpServletRequest request); - -// public void cleanUp(); -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java new file mode 100644 index 000000000..71049fac9 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -0,0 +1,208 @@ +package org.argeo.cms.internal.auth; + +import java.security.PrivilegedExceptionAction; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.jcr.Repository; +import javax.jcr.Session; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CmsSession; +import org.argeo.jcr.JcrUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.useradmin.Authorization; + +public class CmsSessionImpl implements CmsSession { + private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext(); + private final static Log log = LogFactory.getLog(CmsSessionImpl.class); + + private final Subject initialSubject; + private final UUID uuid; + private final String localSessionId; + private final Authorization authorization; + private final LdapName userDn; + + private ServiceRegistration serviceRegistration; + + private Map dataSessions = new HashMap<>(); + private Set dataSessionsInUse = new HashSet<>(); + private LinkedHashSet additionalDataSessions = new LinkedHashSet<>(); + + public CmsSessionImpl(Subject initialSubject, Authorization authorization, String localSessionId) { + this.initialSubject = initialSubject; + this.localSessionId = localSessionId; + this.authorization = authorization; + try { + this.userDn = new LdapName(authorization.getName()); + } catch (InvalidNameException e) { + throw new CmsException("Invalid user name " + authorization.getName(), e); + } + this.uuid = UUID.randomUUID(); + // register as service + Hashtable props = new Hashtable<>(); + props.put(CmsSession.USER_DN, authorization.getName()); + props.put(CmsSession.SESSION_UUID, uuid.toString()); + props.put(CmsSession.SESSION_LOCAL_ID, localSessionId); + serviceRegistration = bc.registerService(CmsSession.class, this, props); + } + + public synchronized void cleanUp() { + serviceRegistration.unregister(); + + // TODO check data session in use ? + for (String path : dataSessions.keySet()) + JcrUtils.logoutQuietly(dataSessions.get(path)); + for (Session session : additionalDataSessions) + JcrUtils.logoutQuietly(session); + notifyAll(); + } + + @Override + public synchronized Session getDataSession(String cn, String workspace, Repository repository) { + String path = cn + '/' + workspace; + if (dataSessionsInUse.contains(path)) { + try { + wait(1000); + if (dataSessionsInUse.contains(path)) { + Session session = login(repository, workspace); + additionalDataSessions.add(session); + if (log.isTraceEnabled()) + log.trace("Additional data session " + path + " for " + userDn); + return session; + } + } catch (InterruptedException e) { + // silent + } + } + + Session session = null; + if (dataSessions.containsKey(path)) { + session = dataSessions.get(path); + } else { + session = login(repository, workspace); + dataSessions.put(path, session); + if (log.isTraceEnabled()) + log.trace("New data session " + path + " for " + userDn); + } + dataSessionsInUse.add(path); + return session; + } + + private Session login(Repository repository, String workspace) { + try { + return Subject.doAs(initialSubject, new PrivilegedExceptionAction() { + @Override + public Session run() throws Exception { + return repository.login(workspace); + } + }); + } catch (Exception e) { + throw new CmsException("Cannot log in " + userDn + " to JCR", e); + } + } + + @Override + public synchronized void releaseDataSession(String cn, Session session) { + if (additionalDataSessions.contains(session)) { + JcrUtils.logoutQuietly(session); + additionalDataSessions.remove(session); + return; + } + String path = cn + '/' + session.getWorkspace().getName(); + if (!dataSessionsInUse.contains(path)) + log.warn("Data session " + path + " was not in use for " + userDn); + dataSessionsInUse.remove(path); + Session registeredSession = dataSessions.get(path); + if (session != registeredSession) + log.warn("Data session " + path + " not consistent for " + userDn); + notifyAll(); + } + + @Override + public Authorization getAuthorization() { + return authorization; + } + + @Override + public UUID getUuid() { + return uuid; + } + + public Subject getInitialSubject() { + return initialSubject; + } + + public String getLocalSessionId() { + return localSessionId; + } + + public ServiceRegistration getServiceRegistration() { + return serviceRegistration; + } + + @Override + public LdapName getUserDn() { + return userDn; + } + + @Override + public String getLocalId() { + return localSessionId; + } + + public String toString() { + return "CMS Session #" + localSessionId; + } + + public static CmsSession getByLocalId(String localId) { + Collection> sr; + try { + sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")"); + } catch (InvalidSyntaxException e) { + throw new CmsException("Cannot get CMS session for id " + localId, e); + } + ServiceReference cmsSessionRef; + if (sr.size() == 1) { + cmsSessionRef = sr.iterator().next(); + return bc.getService(cmsSessionRef); + } else if (sr.size() == 0) { + return null; + } else + throw new CmsException(sr.size() + " CMS sessions registered for " + localId); + + } + + public static CmsSession getByUuid(String uuid) { + Collection> sr; + try { + sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")"); + } catch (InvalidSyntaxException e) { + throw new CmsException("Cannot get CMS session for uuid " + uuid, e); + } + ServiceReference cmsSessionRef; + if (sr.size() == 1) { + cmsSessionRef = sr.iterator().next(); + return bc.getService(cmsSessionRef); + } else if (sr.size() == 0) { + return null; + } else + throw new CmsException(sr.size() + " CMS sessions registered for " + uuid); + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java new file mode 100644 index 000000000..375520171 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java @@ -0,0 +1,51 @@ +package org.argeo.cms.internal.http; + +import java.io.Serializable; +import java.util.LinkedHashMap; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.server.SessionProvider; +import org.argeo.cms.auth.CmsSession; + +/** + * Implements an open session in view patter: a new JCR session is created for + * each request + */ +class CmsSessionProvider implements SessionProvider, Serializable { + private static final long serialVersionUID = -1358136599534938466L; + + private final static Log log = LogFactory.getLog(CmsSessionProvider.class); + + private final String alias; + + private LinkedHashMap cmsSessions = new LinkedHashMap<>(); + + public CmsSessionProvider(String alias) { + this.alias = alias; + } + + public Session getSession(HttpServletRequest request, Repository rep, String workspace) + throws javax.jcr.LoginException, ServletException, RepositoryException { + + CmsSession cmsSession = WebCmsSessionImpl.getCmsSession(request); + Session session = cmsSession.getDataSession(alias, workspace, rep); + cmsSessions.put(session, cmsSession); + return session; + } + + public void releaseSession(Session session) { + if (cmsSessions.containsKey(session)) { + CmsSession cmsSession = cmsSessions.get(session); + cmsSession.releaseDataSession(alias, session); + } else { + log.warn("No CMS session for JCR session " + session); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java new file mode 100644 index 000000000..581d6cbc1 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java @@ -0,0 +1,188 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; +import java.net.URL; +import java.util.StringTokenizer; + +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.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.auth.HttpRequestCallback; +import org.argeo.cms.auth.HttpRequestCallbackHandler; +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.service.http.HttpContext; + +class DataHttpContext implements HttpContext { + private final static Log log = LogFactory.getLog(DataHttpContext.class); + + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + // FIXME Make it more unique + private String httpAuthRealm = "Argeo"; + + @Override + public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) throws IOException { + + if (log.isTraceEnabled()) + HttpUtils.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(); + } catch (LoginException e1) { + throw new CmsException("Could not login", e1); + } + } else { + lc = processUnauthorized(request, response); + if (lc == null) + return false; + } + } + request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc); + return true; + } + + @Override + public URL getResource(String name) { + return bc.getBundle().getResource(name); + } + + @Override + public String getMimeType(String name) { + return null; + } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + // anonymous + try { + LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER); + lc.login(); + return lc; + } catch (LoginException e1) { + if (log.isDebugEnabled()) + log.error("Cannot log in as anonymous", e1); + return null; + } + } + + protected CallbackHandler extractHttpAuth(final HttpServletRequest httpRequest, HttpServletResponse httpResponse) { + String authHeader = httpRequest.getHeader(HttpUtils.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; + } + + protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { + response.setStatus(401); + response.setHeader(HttpUtils.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"); + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpFilter.java b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpFilter.java new file mode 100644 index 000000000..142bccb9d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpFilter.java @@ -0,0 +1,40 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** Abstract base class for http filters. */ +abstract class HttpFilter implements Filter { + // private final static Log log = LogFactory.getLog(HttpFilter.class); + + protected abstract void doFilter(HttpSession httpSession, + HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException; + + @Override + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + doFilter(request.getSession(), request, + (HttpServletResponse) servletResponse, filterChain); + } + + @Override + public void destroy() { + } + + @Override + public void init(FilterConfig arg0) throws ServletException { + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java new file mode 100644 index 000000000..efa2d661a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java @@ -0,0 +1,64 @@ +package org.argeo.cms.internal.http; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; + +class HttpUtils { + final static String HEADER_AUTHORIZATION = "Authorization"; + final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/internal/http/protectedHandlers.xml"; + final static String WEBDAV_CONFIG = "/org/argeo/cms/internal/http/webdav-config.xml"; + + static boolean isBrowser(String userAgent) { + return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") + || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") + || userAgent.contains("opera") || userAgent.contains("browser"); + } + + static void logRequestHeaders(Log log, HttpServletRequest request) { + if (!log.isDebugEnabled()) + return; + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { + String headerName = headerNames.nextElement(); + Object headerValue = request.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + log.debug(request.getRequestURI() + "\n"); + } + + static void logRequest(Log log,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 HttpUtils() { + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/LinkServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/LinkServlet.java new file mode 100644 index 000000000..3c290e17a --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/LinkServlet.java @@ -0,0 +1,258 @@ +package org.argeo.cms.internal.http; + +import static javax.jcr.Property.JCR_DESCRIPTION; +import static javax.jcr.Property.JCR_LAST_MODIFIED; +import static javax.jcr.Property.JCR_TITLE; +import static org.argeo.cms.CmsTypes.CMS_IMAGE; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.PrivilegedExceptionAction; +import java.util.Calendar; +import java.util.Collection; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.CmsException; +import org.argeo.jcr.JcrUtils; +import org.argeo.node.NodeConstants; +import org.argeo.node.NodeUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +class LinkServlet extends HttpServlet { + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private static final long serialVersionUID = 3749990143146845708L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String path = request.getPathInfo(); + String userAgent = request.getHeader("User-Agent").toLowerCase(); + boolean isBot = false; + // boolean isCompatibleBrowser = false; + if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) { + isBot = true; + } + // else if (userAgent.contains("webkit") || + // userAgent.contains("gecko") || userAgent.contains("firefox") + // || userAgent.contains("msie") || userAgent.contains("chrome") || + // userAgent.contains("chromium") + // || userAgent.contains("opera") || userAgent.contains("browser")) + // { + // isCompatibleBrowser = true; + // } + + if (isBot) { + // log.warn("# BOT " + request.getHeader("User-Agent")); + canonicalAnswer(request, response, path); + return; + } + + // if (isCompatibleBrowser && log.isTraceEnabled()) + // log.trace("# BWS " + request.getHeader("User-Agent")); + redirectTo(response, "/#" + path); + } + + private void redirectTo(HttpServletResponse response, String location) { + response.setHeader("Location", location); + response.setStatus(HttpServletResponse.SC_FOUND); + } + + // private boolean canonicalAnswerNeededBy(HttpServletRequest request) { + // String userAgent = request.getHeader("User-Agent").toLowerCase(); + // return userAgent.startsWith("facebookexternalhit/"); + // } + + /** For bots which don't understand RWT. */ + private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) { + Session session = null; + try { + PrintWriter writer = response.getWriter(); + session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction() { + + @Override + public Session run() throws Exception { + Collection> srs = bc.getServiceReferences(Repository.class, + "(" + NodeConstants.CN + "=" + NodeConstants.NODE + ")"); + Repository repository = bc.getService(srs.iterator().next()); + return repository.login(); + } + + }); + Node node = session.getNode(path); + String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName(); + String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null; + Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate() + : null; + String url = getCanonicalUrl(node, request); + String imgUrl = null; + loop: for (NodeIterator it = node.getNodes(); it.hasNext();) { + // Takes the first found cms:image + Node child = it.nextNode(); + if (child.isNodeType(CMS_IMAGE)) { + imgUrl = getDataUrl(child, request); + break loop; + } + } + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(""); + writeMeta(buf, "og:title", escapeHTML(title)); + writeMeta(buf, "og:type", "website"); + buf.append(""); + buf.append(""); + writeMeta(buf, "og:url", url); + if (desc != null) + writeMeta(buf, "og:description", escapeHTML(desc)); + if (imgUrl != null) + writeMeta(buf, "og:image", imgUrl); + if (lastUpdate != null) + writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime())); + buf.append(""); + buf.append(""); + buf.append("

!! This page is meant for indexing robots, not for real people," + " visit ").append(escapeHTML(title)).append(" instead.

"); + writeCanonical(buf, node); + buf.append(""); + buf.append(""); + writer.print(buf.toString()); + + response.setHeader("Content-Type", "text/html"); + writer.flush(); + } catch (Exception e) { + throw new CmsException("Cannot write canonical answer", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** + * From http://stackoverflow.com/questions/1265282/recommended-method-for- + * escaping-html-in-java (+ escaping '). TODO Use + * org.apache.commons.lang.StringEscapeUtils + */ + private String escapeHTML(String s) { + StringBuilder out = new StringBuilder(Math.max(16, s.length())); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') { + out.append("&#"); + out.append((int) c); + out.append(';'); + } else { + out.append(c); + } + } + return out.toString(); + } + + private void writeMeta(StringBuilder buf, String tag, String value) { + buf.append(""); + } + + private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException { + buf.append("
"); + if (node.hasProperty(JCR_TITLE)) + buf.append("

").append(node.getProperty(JCR_TITLE).getString()).append("

"); + if (node.hasProperty(JCR_DESCRIPTION)) + buf.append("

").append(node.getProperty(JCR_DESCRIPTION).getString()).append("

"); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + writeCanonical(buf, children.nextNode()); + } + buf.append("
"); + } + + // DATA + private StringBuilder getServerBaseUrl(HttpServletRequest request) { + try { + URL url = new URL(request.getRequestURL().toString()); + StringBuilder buf = new StringBuilder(); + buf.append(url.getProtocol()).append("://").append(url.getHost()); + if (url.getPort() != -1) + buf.append(':').append(url.getPort()); + return buf; + } catch (MalformedURLException e) { + throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); + } + } + + private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append(NodeUtils.getDataPath(NodeConstants.NODE, node)); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + } + + // public static String getDataPath(Node node) throws + // RepositoryException { + // assert node != null; + // String userId = node.getSession().getUserID(); + //// if (log.isTraceEnabled()) + //// log.trace(userId + " : " + node.getPath()); + // StringBuilder buf = new StringBuilder(); + // boolean isAnonymous = + // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS); + // if (isAnonymous) + // buf.append(WEBDAV_PUBLIC); + // else + // buf.append(WEBDAV_PRIVATE); + // Session session = node.getSession(); + // Repository repository = session.getRepository(); + // String cn; + // if (repository.isSingleValueDescriptor(NodeConstants.CN)) { + // cn = repository.getDescriptor(NodeConstants.CN); + // } else { + //// log.warn("No cn defined in repository, using " + + // NodeConstants.NODE); + // cn = NodeConstants.NODE; + // } + // return + // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath()) + // .toString(); + // } + + private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append('/').append('!').append(node.getPath()); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + // return request.getRequestURL().append('!').append(node.getPath()) + // .toString(); + } + + private Subject anonymousLogin() { + Subject subject = new Subject(); + LoginContext lc; + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); + lc.login(); + return subject; + } catch (LoginException e) { + throw new CmsException("Cannot login as anonymous", e); + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/http/NodeHttp.java new file mode 100644 index 000000000..f67c342ec --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/NodeHttp.java @@ -0,0 +1,344 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import javax.jcr.Repository; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.server.SessionProvider; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; +import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; +import org.argeo.cms.CmsException; +import org.argeo.cms.internal.kernel.KernelConstants; +import org.argeo.node.NodeConstants; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Intercepts and enriches http access, mainly focusing on security and + * transactionality. + */ +public class NodeHttp implements KernelConstants { + private final static Log log = LogFactory.getLog(NodeHttp.class); + + // Filters + // private final RootFilter rootFilter; + + // private final DoSFilter dosFilter; + // private final QoSFilter qosFilter; + + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private ServiceTracker repositories; + private final ServiceTracker httpServiceTracker; + + public NodeHttp() { + // rootFilter = new RootFilter(); + // dosFilter = new CustomDosFilter(); + // qosFilter = new QoSFilter(); + + httpServiceTracker = new PrepareHttpStc(); + httpServiceTracker.open(); + } + + // 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); + // + // } + // } + + public void destroy() { + repositories.close(); + } + + void registerRepositoryServlets(HttpService httpService, String alias, Repository repository) { + if (httpService == null) + throw new CmsException("No HTTP service available"); + try { + registerWebdavServlet(httpService, alias, repository); + registerRemotingServlet(httpService, alias, repository); + if (NodeConstants.HOME.equals(alias)) + registerFilesServlet(httpService, alias, repository); + if (log.isDebugEnabled()) + log.debug("Registered servlets for repository '" + alias + "'"); + } catch (Exception e) { + throw new CmsException("Could not register servlets for repository '" + alias + "'", e); + } + } + + void unregisterRepositoryServlets(HttpService httpService, String alias) { + if (httpService == null) + return; + try { + httpService.unregister(webdavPath(alias)); + httpService.unregister(remotingPath(alias)); + httpService.unregister(filesPath(alias)); + if (log.isDebugEnabled()) + log.debug("Unregistered servlets for repository '" + alias + "'"); + } catch (Exception e) { + log.error("Could not unregister servlets for repository '" + alias + "'", e); + } + } + + void registerWebdavServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + // WebdavServlet webdavServlet = new WebdavServlet(repository, new + // OpenInViewSessionProvider(alias)); + WebdavServlet webdavServlet = new WebdavServlet(repository, new CmsSessionProvider(alias)); + String path = webdavPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, HttpUtils.WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext()); + } + + void registerFilesServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + WebdavServlet filesServlet = new WebdavServlet(repository, new CmsSessionProvider(alias)); + String path = filesPath(alias); + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, HttpUtils.WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + httpService.registerServlet(path, filesServlet, ip, new PrivateHttpContext()); + } + + void registerRemotingServlet(HttpService httpService, String alias, Repository repository) + throws NamespaceException, ServletException { + RemotingServlet remotingServlet = new RemotingServlet(repository, new CmsSessionProvider(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 + Path tmpDir; + try { + tmpDir = Files.createTempDirectory("remoting_" + alias); + } catch (IOException e) { + throw new CmsException("Cannot create temp directory for remoting servlet", e); + } + ip.setProperty(RemotingServlet.INIT_PARAM_HOME, tmpDir.toString()); + ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting_" + alias); + ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, HttpUtils.DEFAULT_PROTECTED_HANDLERS); + ip.setProperty(RemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); + httpService.registerServlet(path, remotingServlet, ip, new PrivateHttpContext()); + } + + private String webdavPath(String alias) { + return NodeConstants.PATH_DATA + "/" + 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, + // 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, response)); + // lc.login(); + // return lc.getSubject(); + // } catch (LoginException e) { + // throw new CmsException("Cannot login", e); + // } + // } + + private class RepositoriesStc extends ServiceTracker { + private final HttpService httpService; + + public RepositoriesStc(HttpService httpService) { + super(bc, Repository.class, null); + this.httpService = httpService; + } + + @Override + public Repository addingService(ServiceReference reference) { + Repository repository = bc.getService(reference); + Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + registerRepositoryServlets(httpService, alias, repository); + } + return repository; + } + + @Override + public void modifiedService(ServiceReference reference, Repository service) { + } + + @Override + public void removedService(ServiceReference reference, Repository service) { + Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); + if (jcrRepoAlias != null) { + String alias = jcrRepoAlias.toString(); + unregisterRepositoryServlets(httpService, alias); + } + } + } + + private class PrepareHttpStc extends ServiceTracker { + // private DataHttp dataHttp; + // private NodeHttp nodeHttp; + + public PrepareHttpStc() { + super(bc, HttpService.class, null); + } + + @Override + public HttpService addingService(ServiceReference reference) { + HttpService httpService = addHttpService(reference); + return httpService; + } + + @Override + public void removedService(ServiceReference reference, HttpService service) { + // if (dataHttp != null) + // dataHttp.destroy(); + // dataHttp = null; + // if (nodeHttp != null) + // nodeHttp.destroy(); + // nodeHttp = null; + // destroy(); + repositories.close(); + repositories = null; + } + + private HttpService addHttpService(ServiceReference sr) { + HttpService httpService = bc.getService(sr); + // TODO find constants + Object httpPort = sr.getProperty("http.port"); + Object httpsPort = sr.getProperty("https.port"); + + try { + httpService.registerServlet("/!", new LinkServlet(), null, null); + httpService.registerServlet("/robots.txt", new RobotServlet(), null, null); + } catch (Exception e) { + throw new CmsException("Cannot register filters", e); + } + // track repositories + if (repositories != null) + throw new CmsException("An http service is already configured"); + repositories = new RepositoriesStc(httpService); + repositories.open(); + log.info(httpPortsMsg(httpPort, httpsPort)); + // httpAvailable = true; + // checkReadiness(); + + bc.registerService(NodeHttp.class, NodeHttp.this, null); + return httpService; + } + + private String httpPortsMsg(Object httpPort, Object httpsPort) { + return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : ""); + } + } + + private class WebdavServlet extends SimpleWebdavServlet { + private static final long serialVersionUID = -4687354117811443881L; + private final Repository repository; + + public WebdavServlet(Repository repository, SessionProvider sessionProvider) { + this.repository = repository; + setSessionProvider(sessionProvider); + } + + public Repository getRepository() { + return repository; + } + + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { + WebdavServlet.super.service(request, response); + // try { + // Subject subject = subjectFromRequest(request); + // // TODO make it stronger, with eTags. + // // if (CurrentUser.isAnonymous(subject) && + // // request.getMethod().equals("GET")) { + // // response.setHeader("Cache-Control", "no-transform, public, + // // max-age=300, s-maxage=900"); + // // } + // + // Subject.doAs(subject, new PrivilegedExceptionAction() { + // @Override + // public Void run() throws Exception { + // WebdavServlet.super.service(request, response); + // return null; + // } + // }); + // } catch (PrivilegedActionException e) { + // throw new CmsException("Cannot process webdav request", + // e.getException()); + // } + } + } + + private class RemotingServlet extends JcrRemotingServlet { + private static final long serialVersionUID = 4605238259548058883L; + private final Repository repository; + private final SessionProvider sessionProvider; + + public RemotingServlet(Repository repository, SessionProvider sessionProvider) { + this.repository = repository; + this.sessionProvider = sessionProvider; + } + + @Override + protected Repository getRepository() { + return repository; + } + + @Override + protected SessionProvider getSessionProvider() { + return sessionProvider; + } + + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException { + // try { + // Subject subject = subjectFromRequest(request, response); + // Subject.doAs(subject, new PrivilegedExceptionAction() { + // @Override + // public Void run() throws Exception { + RemotingServlet.super.service(request, response); + // return null; + // } + // }); + // } catch (PrivilegedActionException e) { + // throw new CmsException("Cannot process JCR remoting request", + // e.getException()); + // } + } + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/OpenInViewSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/OpenInViewSessionProvider.java new file mode 100644 index 000000000..8fcb3db66 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/OpenInViewSessionProvider.java @@ -0,0 +1,70 @@ +package org.argeo.cms.internal.http; + +import java.io.Serializable; +import java.security.PrivilegedExceptionAction; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.server.SessionProvider; +import org.argeo.cms.CmsException; +import org.argeo.jcr.JcrUtils; +import org.argeo.node.NodeConstants; + +/** + * Implements an open session in view patter: a new JCR session is created for + * each request + */ +class OpenInViewSessionProvider implements SessionProvider, Serializable { + private final static Log log = LogFactory.getLog(OpenInViewSessionProvider.class); + + 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 { + return login(request, rep, workspace); + } + + protected Session login(HttpServletRequest request, Repository repository, String workspace) + throws RepositoryException { + if (log.isTraceEnabled()) + 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(); + return Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction() { + @Override + public Session run() throws Exception { + return repository.login(workspace); + } + }); + } catch (Exception e) { + throw new CmsException("Cannot log in to JCR", e); + } + // return repository.login(workspace); + } + + public void releaseSession(Session session) { + JcrUtils.logoutQuietly(session); + if (log.isTraceEnabled()) + log.trace("Logged out remote JCR session " + session); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java new file mode 100644 index 000000000..e0d713224 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java @@ -0,0 +1,15 @@ +package org.argeo.cms.internal.http; + +import javax.security.auth.login.LoginContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class PrivateHttpContext extends DataHttpContext { + + @Override + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + askForWwwAuth(request, response); + return null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java new file mode 100644 index 000000000..92d9eb78c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java @@ -0,0 +1,24 @@ +package org.argeo.cms.internal.http; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class RobotServlet extends HttpServlet { + private static final long serialVersionUID = 7935661175336419089L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + PrintWriter writer = response.getWriter(); + writer.append("User-agent: *\n"); + writer.append("Disallow:\n"); + response.setHeader("Content-Type", "text/plain"); + writer.flush(); + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java new file mode 100644 index 000000000..139e23ba7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java @@ -0,0 +1,22 @@ +package org.argeo.cms.internal.http; + +import javax.security.auth.Subject; +import javax.servlet.http.HttpServletRequest; + +import org.argeo.cms.auth.CmsSession; +import org.argeo.cms.internal.auth.CmsSessionImpl; +import org.osgi.service.useradmin.Authorization; + +public class WebCmsSessionImpl extends CmsSessionImpl { + + public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, String httpSessionId) { + super(initialSubject, authorization, httpSessionId); + } + + public static CmsSession getCmsSession(HttpServletRequest request) { + CmsSession cmsSession = (CmsSession) request.getAttribute(CmsSession.class.getName()); + if (cmsSession != null) + return cmsSession; + return CmsSessionImpl.getByLocalId(request.getSession().getId()); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/protectedHandlers.xml b/org.argeo.cms/src/org/argeo/cms/internal/http/protectedHandlers.xml new file mode 100644 index 000000000..59f22cd5e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/protectedHandlers.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/webdav-config.xml b/org.argeo.cms/src/org/argeo/cms/internal/http/webdav-config.xml new file mode 100644 index 000000000..da4e18b11 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/webdav-config.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nt:file + nt:resource + + + + + + + + + + + + + rep + jcr + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java index 1d1734d36..6faa4c9e2 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.commons.cnd.CndImporter; import org.apache.jackrabbit.core.RepositoryContext; import org.argeo.cms.CmsException; +import org.argeo.cms.internal.http.NodeHttp; import org.argeo.jcr.JcrUtils; import org.argeo.node.DataModelNamespace; import org.argeo.node.NodeConstants; @@ -39,13 +40,12 @@ import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ManagedService; -import org.osgi.service.http.HttpService; import org.osgi.service.useradmin.UserAdmin; import org.osgi.util.tracker.ServiceTracker; public class CmsDeployment implements NodeDeployment { private final static String LEGACY_JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; - + private final Log log = LogFactory.getLog(getClass()); private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); @@ -55,6 +55,9 @@ public class CmsDeployment implements NodeDeployment { private Long availableSince; private final boolean cleanState; + + private NodeHttp nodeHttp; + // Readiness private boolean nodeAvailable = false; private boolean userAdminAvailable = false; @@ -69,11 +72,20 @@ public class CmsDeployment implements NodeDeployment { NodeState nodeState = bc.getService(nodeStateSr); cleanState = nodeState.isClean(); + nodeHttp = new NodeHttp(); initTrackers(); } private void initTrackers() { - new PrepareHttpStc().open(); + new ServiceTracker(bc, NodeHttp.class, null) { + + @Override + public NodeHttp addingService(ServiceReference reference) { + httpAvailable = true; + checkReadiness(); + return super.addingService(reference); + } + }.open(); new RepositoryContextStc().open(); new ServiceTracker(bc, UserAdmin.class, null) { @Override @@ -90,10 +102,11 @@ public class CmsDeployment implements NodeDeployment { deployConfig = new DeployConfig(configurationAdmin, cleanState); httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null; try { - Configuration[] configs= configurationAdmin.listConfigurations("(service.factoryPid="+NodeConstants.NODE_REPOS_FACTORY_PID+")"); - for(Configuration config:configs){ + Configuration[] configs = configurationAdmin + .listConfigurations("(service.factoryPid=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); + for (Configuration config : configs) { Object cn = config.getProperties().get(NodeConstants.CN); - log.debug("Standalone repo cn: "+cn); + log.debug("Standalone repo cn: " + cn); } } catch (Exception e) { throw new CmsException("Cannot initialize config", e); @@ -104,6 +117,8 @@ public class CmsDeployment implements NodeDeployment { } public void shutdown() { + if(nodeHttp!=null) + nodeHttp.destroy(); if (deployConfig != null) deployConfig.save(); } @@ -265,7 +280,7 @@ public class CmsDeployment implements NodeDeployment { prepareHomeRepository(nodeRepo.getRepository()); nodeAvailable = true; checkReadiness(); - }else{ + } else { // TODO standalone } } @@ -282,46 +297,4 @@ public class CmsDeployment implements NodeDeployment { } - private class PrepareHttpStc extends ServiceTracker { - private DataHttp dataHttp; - private NodeHttp nodeHttp; - - public PrepareHttpStc() { - super(bc, HttpService.class, null); - } - - @Override - public HttpService addingService(ServiceReference reference) { - HttpService httpService = addHttpService(reference); - return httpService; - } - - @Override - public void removedService(ServiceReference reference, HttpService service) { - if (dataHttp != null) - dataHttp.destroy(); - dataHttp = null; - if (nodeHttp != null) - nodeHttp.destroy(); - nodeHttp = null; - } - - private HttpService addHttpService(ServiceReference sr) { - HttpService httpService = bc.getService(sr); - // TODO find constants - Object httpPort = sr.getProperty("http.port"); - Object httpsPort = sr.getProperty("https.port"); - dataHttp = new DataHttp(httpService); - nodeHttp = new NodeHttp(httpService, bc); - log.info(httpPortsMsg(httpPort, httpsPort)); - httpAvailable = true; - checkReadiness(); - return httpService; - } - - private String httpPortsMsg(Object httpPort, Object httpsPort) { - return "HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : ""); - } - } - } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java index 04db97833..f3d91ee45 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java @@ -40,8 +40,8 @@ public class CmsFsProvider extends AbstractJackrabbitFsProvider { throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username); try { - Repository repository = bc - .getService(bc.getServiceReferences(Repository.class, "(cn=node)").iterator().next()); + Repository repository = bc.getService( + bc.getServiceReferences(Repository.class, "(cn=" + NodeConstants.HOME + ")").iterator().next()); Session session = repository.login(); JcrFileSystem fileSystem = new JcrFileSystem(this, session); fileSystems.put(username, fileSystem); 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 deleted file mode 100644 index bd4444668..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java +++ /dev/null @@ -1,579 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import java.io.IOException; -import java.io.Serializable; -import java.net.URL; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Properties; -import java.util.StringTokenizer; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -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.CredentialNotFoundException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.jackrabbit.server.SessionProvider; -import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; -import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; -import org.argeo.cms.CmsException; -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; -import org.osgi.service.http.HttpContext; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; -import org.osgi.service.useradmin.Authorization; -import org.osgi.util.tracker.ServiceTracker; -import org.osgi.util.tracker.ServiceTrackerCustomizer; - -/** - * Intercepts and enriches http access, mainly focusing on security and - * transactionality. - */ -class DataHttp implements KernelConstants { - private final static Log log = LogFactory.getLog(DataHttp.class); - - // private final static String ATTR_AUTH = "auth"; - private final static String HEADER_AUTHORIZATION = "Authorization"; - private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - private final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/internal/kernel/protectedHandlers.xml"; - - private final BundleContext bc; - private final HttpService httpService; - private final ServiceTracker repositories; - - // FIXME Make it more unique - private String httpAuthRealm = "Argeo"; - - DataHttp(HttpService httpService) { - this.bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - this.httpService = httpService; - repositories = new ServiceTracker<>(bc, Repository.class, new RepositoriesStc()); - repositories.open(); - } - - public void destroy() { - repositories.close(); - } - - void registerRepositoryServlets(String alias, Repository repository) { - try { - registerWebdavServlet(alias, repository); - registerRemotingServlet(alias, repository); - registerFilesServlet(alias, repository); - if (log.isDebugEnabled()) - log.debug("Registered servlets for repository '" + alias + "'"); - } catch (Exception e) { - throw new CmsException("Could not register servlets for repository '" + alias + "'", e); - } - } - - void unregisterRepositoryServlets(String alias) { - try { - httpService.unregister(webdavPath(alias)); - httpService.unregister(remotingPath(alias)); - httpService.unregister(filesPath(alias)); - if (log.isDebugEnabled()) - log.debug("Unregistered servlets for repository '" + alias + "'"); - } catch (Exception e) { - log.error("Could not unregister servlets for repository '" + alias + "'", e); - } - } - - void registerWebdavServlet(String alias, Repository repository) throws NamespaceException, ServletException { - 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); - ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); - httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext()); - } - - 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_PROTECTED_HANDLERS_CONFIG, DEFAULT_PROTECTED_HANDLERS); - ip.setProperty(RemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); - httpService.registerServlet(path, remotingServlet, ip, new RemotingHttpContext()); - } - - private String webdavPath(String alias) { - return NodeConstants.PATH_DATA + "/" + 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, 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, response)); - lc.login(); - return lc.getSubject(); - } catch (LoginException e) { - throw new CmsException("Cannot login", e); - } - } - - 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 - public Repository addingService(ServiceReference reference) { - Repository repository = bc.getService(reference); - Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); - if (jcrRepoAlias != null) { - String alias = jcrRepoAlias.toString(); - registerRepositoryServlets(alias, repository); - } - return repository; - } - - @Override - public void modifiedService(ServiceReference reference, Repository service) { - } - - @Override - public void removedService(ServiceReference reference, Repository service) { - Object jcrRepoAlias = reference.getProperty(NodeConstants.CN); - if (jcrRepoAlias != null) { - String alias = jcrRepoAlias.toString(); - unregisterRepositoryServlets(alias); - } - } - } - - private class DataHttpContext 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, 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 URL getResource(String name) { - return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name); - } - - @Override - public String getMimeType(String name) { - return null; - } - - } - - 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, 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. - } catch (LoginException e1) { - throw new CmsException("Could not login", e1); - } - } else { - askForWwwAuth(request, response); - lc = null; - return false; - } - } - request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc); - return true; - } - - @Override - public URL getResource(String name) { - return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name); - } - - @Override - public String getMimeType(String name) { - return null; - } - - } - - private class RemotingHttpContext implements HttpContext { - // private final boolean 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 (log.isTraceEnabled()) - KernelUtils.logRequestHeaders(log, request); - LoginContext lc; - try { - lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, - new HttpRequestCallbackHandler(request, response)); - lc.login(); - } catch (CredentialNotFoundException 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; - } - } 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 - public URL getResource(String name) { - return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name); - } - - @Override - public String getMimeType(String name) { - return null; - } - - } - - /** - * Implements an open session in view patter: a new JCR session is created - * for each request - */ - 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 { - return login(request, rep, workspace); - } - - protected Session login(HttpServletRequest request, Repository repository, String workspace) - throws RepositoryException { - if (log.isTraceEnabled()) - 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(); - return Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction() { - @Override - public Session run() throws Exception { - return repository.login(workspace); - } - }); - } catch (Exception e) { - throw new CmsException("Cannot log in to JCR", e); - } - // return repository.login(workspace); - } - - public void releaseSession(Session session) { - JcrUtils.logoutQuietly(session); - if (log.isTraceEnabled()) - log.trace("Logged out remote JCR session " + session); - } - } - - private class WebdavServlet extends SimpleWebdavServlet { - private static final long serialVersionUID = -4687354117811443881L; - private final Repository repository; - - public WebdavServlet(Repository repository, SessionProvider sessionProvider) { - this.repository = repository; - setSessionProvider(sessionProvider); - } - - public Repository getRepository() { - return repository; - } - - @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) - throws ServletException, IOException { - WebdavServlet.super.service(request, response); - // try { - // Subject subject = subjectFromRequest(request); - // // TODO make it stronger, with eTags. - // // if (CurrentUser.isAnonymous(subject) && - // // request.getMethod().equals("GET")) { - // // response.setHeader("Cache-Control", "no-transform, public, - // // max-age=300, s-maxage=900"); - // // } - // - // Subject.doAs(subject, new PrivilegedExceptionAction() { - // @Override - // public Void run() throws Exception { - // WebdavServlet.super.service(request, response); - // return null; - // } - // }); - // } catch (PrivilegedActionException e) { - // throw new CmsException("Cannot process webdav request", - // e.getException()); - // } - } - } - - private class RemotingServlet extends JcrRemotingServlet { - private static final long serialVersionUID = 4605238259548058883L; - private final Repository repository; - private final SessionProvider sessionProvider; - - public RemotingServlet(Repository repository, SessionProvider sessionProvider) { - this.repository = repository; - this.sessionProvider = sessionProvider; - } - - @Override - protected Repository getRepository() { - return repository; - } - - @Override - protected SessionProvider getSessionProvider() { - return sessionProvider; - } - - @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) - throws ServletException, IOException { - try { - Subject subject = subjectFromRequest(request, response); - Subject.doAs(subject, new PrivilegedExceptionAction() { - @Override - public Void run() throws Exception { - RemotingServlet.super.service(request, response); - return null; - } - }); - } catch (PrivilegedActionException e) { - throw new CmsException("Cannot process JCR remoting request", e.getException()); - } - } - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java deleted file mode 100644 index b8d2ae529..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HttpFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -/** Abstract base class for http filters. */ -abstract class HttpFilter implements Filter { - // private final static Log log = LogFactory.getLog(HttpFilter.class); - - protected abstract void doFilter(HttpSession httpSession, - HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws IOException, ServletException; - - @Override - public void doFilter(ServletRequest servletRequest, - ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) servletRequest; - doFilter(request.getSession(), request, - (HttpServletResponse) servletResponse, filterChain); - } - - @Override - public void destroy() { - } - - @Override - public void init(FilterConfig arg0) throws ServletException { - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitType.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitType.java index 2e0c371e0..b9671f2a4 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitType.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitType.java @@ -1,6 +1,7 @@ package org.argeo.cms.internal.kernel; /** The available Jackrabbit node types */ +@Deprecated public enum JackrabbitType { localfs, h2, postgresql, postgresql_ds,postgresql_cluster, memory; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java index cbe46b453..c3cf0d1bf 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java @@ -24,7 +24,7 @@ public interface KernelConstants { // String LOGIN_CONTEXT_HARDENED_KERNEL = "HARDENED_KERNEL"; // DAV - String WEBDAV_CONFIG = "/org/argeo/cms/internal/kernel/webdav-config.xml"; +// String WEBDAV_CONFIG = "/org/argeo/cms/internal/http/webdav-config.xml"; // String PATH_DATA = "/data"; // String WEBDAV_PUBLIC = PATH_DATA + "/public"; // String WEBDAV_PRIVATE = PATH_DATA + "/files"; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index e3bbdb796..5b1df1708 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -111,29 +111,17 @@ class KernelUtils implements KernelConstants { } // Security - static Subject anonymousLogin() { - Subject subject = new Subject(); - LoginContext lc; - try { - lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); - lc.login(); - return subject; - } catch (LoginException e) { - throw new CmsException("Cannot login as anonymous", e); - } - } - - // HTTP - static void logRequestHeaders(Log log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { - String headerName = headerNames.nextElement(); - Object headerValue = request.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - log.debug(request.getRequestURI() + "\n"); - } +// static Subject anonymousLogin() { +// Subject subject = new Subject(); +// LoginContext lc; +// try { +// lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); +// lc.login(); +// return subject; +// } catch (LoginException e) { +// throw new CmsException("Cannot login as anonymous", e); +// } +// } static void logFrameworkProperties(Log log) { BundleContext bc = getBundleContext(); @@ -150,8 +138,8 @@ class KernelUtils implements KernelConstants { // for (String key : keys) // log.debug(key + "=" + bc.getProperty(key)); } - - static void printSystemProperties(PrintStream out){ + + static void printSystemProperties(PrintStream out) { TreeMap display = new TreeMap<>(); for (Object key : System.getProperties().keySet()) display.put(key.toString(), System.getProperty(key.toString())); @@ -217,7 +205,6 @@ class KernelUtils implements KernelConstants { } } - private KernelUtils() { } 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 deleted file mode 100644 index f17e15797..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ /dev/null @@ -1,395 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import static javax.jcr.Property.JCR_DESCRIPTION; -import static javax.jcr.Property.JCR_LAST_MODIFIED; -import static javax.jcr.Property.JCR_TITLE; -import static org.argeo.cms.CmsTypes.CMS_IMAGE; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import java.security.cert.X509Certificate; -import java.util.Calendar; -import java.util.Collection; -import java.util.Enumeration; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.CmsException; -import org.argeo.jcr.JcrUtils; -import org.argeo.node.NodeConstants; -import org.argeo.node.NodeUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; - -/** - * Intercepts and enriches http access, mainly focusing on security and - * transactionality. - */ -class NodeHttp implements KernelConstants { - private final static Log log = LogFactory.getLog(NodeHttp.class); - - // Filters - // private final RootFilter rootFilter; - - // private final DoSFilter dosFilter; - // private final QoSFilter qosFilter; - - private BundleContext bc; - - NodeHttp(HttpService httpService, BundleContext bc) { - this.bc = bc; - // rootFilter = new RootFilter(); - // dosFilter = new CustomDosFilter(); - // qosFilter = new QoSFilter(); - - try { - httpService.registerServlet("/!", new LinkServlet(), null, null); - httpService.registerServlet("/robots.txt", new RobotServlet(), null, null); - } catch (Exception e) { - throw new CmsException("Cannot register filters", e); - } - } - - public void destroy() { - } - - class LinkServlet extends HttpServlet { - private static final long serialVersionUID = 3749990143146845708L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - String path = request.getPathInfo(); - String userAgent = request.getHeader("User-Agent").toLowerCase(); - boolean isBot = false; - boolean isCompatibleBrowser = false; - if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) { - isBot = true; - } else if (userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser")) { - isCompatibleBrowser = true; - } - - if (isBot) { - log.warn("# BOT " + request.getHeader("User-Agent")); - canonicalAnswer(request, response, path); - return; - } - - if (isCompatibleBrowser && log.isTraceEnabled()) - log.trace("# BWS " + request.getHeader("User-Agent")); - redirectTo(response, "/#" + path); - } - - private void redirectTo(HttpServletResponse response, String location) { - response.setHeader("Location", location); - response.setStatus(HttpServletResponse.SC_FOUND); - } - - // private boolean canonicalAnswerNeededBy(HttpServletRequest request) { - // String userAgent = request.getHeader("User-Agent").toLowerCase(); - // return userAgent.startsWith("facebookexternalhit/"); - // } - - /** For bots which don't understand RWT. */ - private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) { - Session session = null; - try { - PrintWriter writer = response.getWriter(); - session = Subject.doAs(KernelUtils.anonymousLogin(), new PrivilegedExceptionAction() { - - @Override - public Session run() throws Exception { - Collection> srs = bc.getServiceReferences(Repository.class, - "(" + NodeConstants.CN + "=" + NodeConstants.NODE + ")"); - Repository repository = bc.getService(srs.iterator().next()); - return repository.login(); - } - - }); - Node node = session.getNode(path); - String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName(); - String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null; - Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) - ? node.getProperty(JCR_LAST_MODIFIED).getDate() : null; - String url = getCanonicalUrl(node, request); - String imgUrl = null; - loop: for (NodeIterator it = node.getNodes(); it.hasNext();) { - // Takes the first found cms:image - Node child = it.nextNode(); - if (child.isNodeType(CMS_IMAGE)) { - imgUrl = getDataUrl(child, request); - break loop; - } - } - StringBuilder buf = new StringBuilder(); - buf.append(""); - buf.append(""); - writeMeta(buf, "og:title", escapeHTML(title)); - writeMeta(buf, "og:type", "website"); - buf.append(""); - buf.append(""); - writeMeta(buf, "og:url", url); - if (desc != null) - writeMeta(buf, "og:description", escapeHTML(desc)); - if (imgUrl != null) - writeMeta(buf, "og:image", imgUrl); - if (lastUpdate != null) - writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime())); - buf.append(""); - buf.append(""); - buf.append( - "

!! This page is meant for indexing robots, not for real people," + " visit ").append(escapeHTML(title)).append(" instead.

"); - writeCanonical(buf, node); - buf.append(""); - buf.append(""); - writer.print(buf.toString()); - - response.setHeader("Content-Type", "text/html"); - writer.flush(); - } catch (Exception e) { - throw new CmsException("Cannot write canonical answer", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** - * From - * http://stackoverflow.com/questions/1265282/recommended-method-for- - * escaping-html-in-java (+ escaping '). TODO Use - * org.apache.commons.lang.StringEscapeUtils - */ - private String escapeHTML(String s) { - StringBuilder out = new StringBuilder(Math.max(16, s.length())); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') { - out.append("&#"); - out.append((int) c); - out.append(';'); - } else { - out.append(c); - } - } - return out.toString(); - } - - private void writeMeta(StringBuilder buf, String tag, String value) { - buf.append(""); - } - - private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException { - buf.append("
"); - if (node.hasProperty(JCR_TITLE)) - buf.append("

").append(node.getProperty(JCR_TITLE).getString()).append("

"); - if (node.hasProperty(JCR_DESCRIPTION)) - buf.append("

").append(node.getProperty(JCR_DESCRIPTION).getString()).append("

"); - NodeIterator children = node.getNodes(); - while (children.hasNext()) { - writeCanonical(buf, children.nextNode()); - } - buf.append("
"); - } - - // DATA - private StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(NodeUtils.getDataPath(NodeConstants.NODE, node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - } - - // public static String getDataPath(Node node) throws - // RepositoryException { - // assert node != null; - // String userId = node.getSession().getUserID(); - //// if (log.isTraceEnabled()) - //// log.trace(userId + " : " + node.getPath()); - // StringBuilder buf = new StringBuilder(); - // boolean isAnonymous = - // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS); - // if (isAnonymous) - // buf.append(WEBDAV_PUBLIC); - // else - // buf.append(WEBDAV_PRIVATE); - // Session session = node.getSession(); - // Repository repository = session.getRepository(); - // String cn; - // if (repository.isSingleValueDescriptor(NodeConstants.CN)) { - // cn = repository.getDescriptor(NodeConstants.CN); - // } else { - //// log.warn("No cn defined in repository, using " + - // NodeConstants.NODE); - // cn = NodeConstants.NODE; - // } - // return - // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath()) - // .toString(); - // } - - private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append('/').append('!').append(node.getPath()); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - // return request.getRequestURL().append('!').append(node.getPath()) - // .toString(); - } - - } - - class RobotServlet extends HttpServlet { - private static final long serialVersionUID = 7935661175336419089L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - PrintWriter writer = response.getWriter(); - writer.append("User-agent: *\n"); - writer.append("Disallow:\n"); - response.setHeader("Content-Type", "text/plain"); - writer.flush(); - } - - } - - /** Intercepts all requests. Authenticates. */ - class RootFilter extends HttpFilter { - - @Override - public void doFilter(HttpSession httpSession, HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws IOException, ServletException { - if (log.isTraceEnabled()) { - log.trace(request.getRequestURL() - .append(request.getQueryString() != null ? "?" + request.getQueryString() : "")); - logRequest(request); - } - - String servletPath = request.getServletPath(); - - // client certificate - X509Certificate clientCert = extractCertificate(request); - if (clientCert != null) { - // TODO authenticate - // if (log.isDebugEnabled()) - // log.debug(clientCert.getSubjectX500Principal().getName()); - } - - // skip data - if (servletPath.startsWith(NodeConstants.PATH_DATA)) { - filterChain.doFilter(request, response); - return; - } - - // skip /ui (workbench) for the time being - if (servletPath.startsWith(PATH_WORKBENCH)) { - filterChain.doFilter(request, response); - return; - } - - // 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.startsWith(KernelConstants.PATH_WORKBENCH) && path.lastIndexOf('/') != 0) { - String newLocation = request.getServletPath() + "#" + path; - response.setHeader("Location", newLocation); - response.setStatus(HttpServletResponse.SC_FOUND); - return; - } - - // 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; - } - - // 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); - // - // } - // } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java deleted file mode 100644 index cff723e13..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.internal.kernel; - -import java.util.ArrayList; -import java.util.Date; -import java.util.Hashtable; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.cms.auth.WebCmsSession; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceRegistration; -import org.osgi.service.http.HttpContext; -import org.osgi.service.useradmin.Authorization; - -public class WebCmsSessionImpl implements WebCmsSession { - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - private final static Log log = LogFactory.getLog(WebCmsSessionImpl.class); - - private final String id; - private final Authorization authorization; - - private List subHttpSessions = new ArrayList<>(); - - private ServiceRegistration serviceRegistration; - - public WebCmsSessionImpl(String sessionId, Authorization authorization) { - this.id = sessionId; - this.authorization = authorization; - // register as service - Hashtable props = new Hashtable<>(); - props.put(WebCmsSession.CMS_DN, authorization.getName()); - props.put(WebCmsSession.CMS_SESSION_ID, sessionId); - serviceRegistration = bc.registerService(WebCmsSession.class, this, props); - } - - public void cleanUp() { - for (SubHttpSession subSession : subHttpSessions) - subSession.cleanUp(); - serviceRegistration.unregister(); - } - - @Override - public Authorization getAuthorization() { - return authorization; - } - - public ServiceRegistration getServiceRegistration() { - return serviceRegistration; - } - - public void addHttpSession(HttpServletRequest request) { - subHttpSessions.add(new SubHttpSession(request)); - } - - public String getId() { - return id; - } - - public String toString() { - return "CMS Session #" + id; - } - - static class SubHttpSession { - private final HttpSession httpSession; - private final String sessionId; - // private final String originalURI; - // private final String servletPath; - - private final Date start = new Date(); - - public SubHttpSession(HttpServletRequest request) { - this.httpSession = request.getSession(); - this.sessionId = httpSession.getId(); - // this.originalURI = request.getRequestURI(); - // this.servletPath = request.getServletPath(); - } - - public Date getStart() { - return start; - } - - public void cleanUp() { - try { - httpSession.setAttribute(HttpContext.REMOTE_USER, null); - httpSession.setAttribute(HttpContext.AUTHORIZATION, null); - // httpSession.setMaxInactiveInterval(1); - } catch (Exception e) { - log.warn("Could not clean up " + sessionId, e); - } - } - - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/protectedHandlers.xml b/org.argeo.cms/src/org/argeo/cms/internal/kernel/protectedHandlers.xml deleted file mode 100644 index 59f22cd5e..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/protectedHandlers.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml b/org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml deleted file mode 100644 index da4e18b11..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/webdav-config.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - nt:file - nt:resource - - - - - - - - - - - - - rep - jcr - - - - - - -