/krb5.keytab
+/krb5.keytab.old
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;
//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;
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<ServiceReference<WebCmsSession>> 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<WebCmsSession> 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<ServiceReference<CmsSession>> 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<CmsSession> 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<ServiceReference<WebCmsSession>> srs;
+ Collection<ServiceReference<CmsSession>> 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;
--- /dev/null
+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();
+}
--- /dev/null
+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;
+ }
+
+}
+++ /dev/null
-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;
- }
-
-}
// authorization = (Authorization)
// request.getSession().getAttribute(HttpContext.AUTHORIZATION);
// if (authorization == null) {
- Collection<ServiceReference<WebCmsSession>> sr;
+ Collection<ServiceReference<CmsSession>> 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);
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 {
+++ /dev/null
-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();
-}
--- /dev/null
+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<CmsSession> serviceRegistration;
+
+ private Map<String, Session> dataSessions = new HashMap<>();
+ private Set<String> dataSessionsInUse = new HashSet<>();
+ private LinkedHashSet<Session> 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<String, String> 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<Session>() {
+ @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<CmsSession> 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<ServiceReference<CmsSession>> 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<CmsSession> 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<ServiceReference<CmsSession>> 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<CmsSession> 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);
+
+ }
+}
--- /dev/null
+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<Session, CmsSession> 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);
+ }
+ }
+}
--- /dev/null
+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");
+
+ }
+
+}
--- /dev/null
+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 {
+ }
+
+}
--- /dev/null
+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<String> 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<String> en = request.getHeaderNames();
+ while (en.hasMoreElements()) {
+ String header = en.nextElement();
+ Enumeration<String> values = request.getHeaders(header);
+ while (values.hasMoreElements())
+ buf.append(" " + header + ": " + values.nextElement());
+ buf.append('\n');
+ }
+
+ // attributed
+ Enumeration<String> 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() {
+
+ }
+}
--- /dev/null
+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<Session>() {
+
+ @Override
+ public Session run() throws Exception {
+ Collection<ServiceReference<Repository>> 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("<html>");
+ buf.append("<head>");
+ writeMeta(buf, "og:title", escapeHTML(title));
+ writeMeta(buf, "og:type", "website");
+ buf.append("<meta name='twitter:card' content='summary' />");
+ buf.append("<meta name='twitter:site' content='@argeo_org' />");
+ 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("</head>");
+ buf.append("<body>");
+ buf.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
+ .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
+ writeCanonical(buf, node);
+ buf.append("</body>");
+ buf.append("</html>");
+ 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("<meta property='").append(tag).append("' content='").append(value).append("'/>");
+ }
+
+ private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
+ buf.append("<div>");
+ if (node.hasProperty(JCR_TITLE))
+ buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
+ if (node.hasProperty(JCR_DESCRIPTION))
+ buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ writeCanonical(buf, children.nextNode());
+ }
+ buf.append("</div>");
+ }
+
+ // 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);
+ }
+ }
+
+}
--- /dev/null
+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<Repository, Repository> repositories;
+ private final ServiceTracker<HttpService, HttpService> 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<Repository, Repository> {
+ private final HttpService httpService;
+
+ public RepositoriesStc(HttpService httpService) {
+ super(bc, Repository.class, null);
+ this.httpService = httpService;
+ }
+
+ @Override
+ public Repository addingService(ServiceReference<Repository> 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<Repository> reference, Repository service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<Repository> reference, Repository service) {
+ Object jcrRepoAlias = reference.getProperty(NodeConstants.CN);
+ if (jcrRepoAlias != null) {
+ String alias = jcrRepoAlias.toString();
+ unregisterRepositoryServlets(httpService, alias);
+ }
+ }
+ }
+
+ private class PrepareHttpStc extends ServiceTracker<HttpService, HttpService> {
+ // private DataHttp dataHttp;
+ // private NodeHttp nodeHttp;
+
+ public PrepareHttpStc() {
+ super(bc, HttpService.class, null);
+ }
+
+ @Override
+ public HttpService addingService(ServiceReference<HttpService> reference) {
+ HttpService httpService = addHttpService(reference);
+ return httpService;
+ }
+
+ @Override
+ public void removedService(ServiceReference<HttpService> 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<HttpService> 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<Void>() {
+ // @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<Void>() {
+ // @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());
+ // }
+ }
+ }
+
+}
--- /dev/null
+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 ? "<default>" : 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<Session>() {
+ @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);
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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());
+ }
+}
--- /dev/null
+<config>
+ <protecteditemremovehandler>
+ <class name="org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler" />
+ </protecteditemremovehandler>
+</config>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!--
+<!DOCTYPE config [
+ <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
+
+ <!ELEMENT iomanager (class, iohandler*) >
+ <!ELEMENT iohandler (class) >
+
+ <!ELEMENT propertymanager (class, propertyhandler*) >
+ <!ELEMENT propertyhandler (class) >
+
+ <!ELEMENT collection (nodetypes) >
+ <!ELEMENT noncollection (nodetypes) >
+
+ <!ELEMENT filter (class, namespaces?, nodetypes?) >
+
+ <!ELEMENT class >
+ <!ATTLIST class
+ name CDATA #REQUIRED
+ >
+ <!ELEMENT namespaces (prefix | uri)* >
+ <!ELEMENT prefix (CDATA) >
+ <!ELEMENT uri (CDATA) >
+
+ <!ELEMENT nodetypes (nodetype)* >
+ <!ELEMENT nodetype (CDATA) >
+
+ <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
+
+ <!ELEMENT mimemapping >
+ <!ATTLIST mimemapping
+ extension CDATA #REQUIRED
+ mimetype CDATA #REQUIRED
+ >
+
+ <!ELEMENT defaultmimetype (CDATA) >
+]>
+-->
+
+<config>
+ <!--
+ Defines the IOManager implementation that is responsible for passing
+ import/export request to the individual IO-handlers.
+ -->
+ <iomanager>
+ <!-- class element defines the manager to be used. The specified class
+ must implement the IOManager interface.
+ Note, that the handlers are being added and called in the order
+ they appear in the configuration.
+ -->
+ <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+ </iohandler>
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+ </iohandler>
+<!-- <iohandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!-- </iohandler> -->
+<!-- <iohandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!-- </iohandler> -->
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
+ </iohandler>
+ <iohandler>
+ <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+ </iohandler>
+ </iomanager>
+ <!--
+ Example config for iomanager that populates its list of handlers with
+ default values. Therefore the 'iohandler' elements are omited.
+ -->
+ <!--
+ <iomanager>
+ <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
+ </iomanager>
+ -->
+ <!--
+ Defines the PropertyManager implementation that is responsible for export
+ and import of resource properties.
+ -->
+ <propertymanager>
+ <!-- class element defines the manager to be used. The specified class
+ must implement the PropertyManager interface.
+ Note, that the handlers are being added and called in the order
+ they appear in the configuration.
+ -->
+ <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHandler" />
+ </propertyhandler>
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
+ </propertyhandler>
+<!-- <propertyhandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
+<!-- </propertyhandler> -->
+<!-- <propertyhandler> -->
+<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
+<!-- </propertyhandler> -->
+ <propertyhandler>
+ <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
+ </propertyhandler>
+ </propertymanager>
+ <!--
+ Define nodetypes, that should never by displayed as 'collection'
+ -->
+ <noncollection>
+ <nodetypes>
+ <nodetype>nt:file</nodetype>
+ <nodetype>nt:resource</nodetype>
+ </nodetypes>
+ </noncollection>
+ <!--
+ Example: Defines nodetypes, that should always be displayed as 'collection'.
+ -->
+ <!--
+ <collection>
+ <nodetypes>
+ <nodetype>nt:folder</nodetype>
+ <nodetype>rep:root</nodetype>
+ </nodetypes>
+ </collection>
+ -->
+ <!--
+ Filter that allows to prevent certain items from being displayed.
+ Please note, that this has an effect on PROPFIND calls only and does not
+ provide limited access to those items matching any of the filters.
+
+ However specifying a filter may cause problems with PUT or MKCOL if the
+ resource to be created is being filtered out, thus resulting in inconsistent
+ responses (e.g. PUT followed by PROPFIND on parent).
+ -->
+ <filter>
+ <!-- class element defines the resource filter to be used. The specified class
+ must implement the ItemFilter interface -->
+ <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
+ <!--
+ Nodetype names to be used to filter child nodes.
+ A child node can be filtered if the declaring nodetype of its definition
+ is one of the nodetype names specified in the nodetypes Element.
+ E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
+ being hidden but no other child node of the root node, since those
+ are defined by the nodetype nt:unstructered.
+ -->
+ <!--
+ <nodetypes>
+ <nodetype>rep:root</nodetype>
+ </nodetypes>
+ -->
+ <!--
+ Namespace prefixes or uris. Items having a name that matches any of the
+ entries will be filtered.
+ -->
+ <namespaces>
+ <prefix>rep</prefix>
+ <prefix>jcr</prefix>
+ <!--
+ <uri>internal</uri>
+ <uri>http://www.jcp.org/jcr/1.0</uri>
+ -->
+ </namespaces>
+ </filter>
+
+ <!--
+ Optional 'mimetypeproperties' element.
+ It defines additional or replaces existing mappings for the MimeResolver
+ instance created by the ResourceConfig.
+ The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
+ If the default mime type defined by MimeResolver is 'application/octet-stream'.
+ -->
+ <!--
+ <mimetypeproperties>
+ <mimemapping extension="rtf" mimetype="application/rtf" />
+ <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
+ <defaultmimetype>text/html</defaultmimetype>
+ </mimetypeproperties>
+ -->
+</config>
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;
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();
private Long availableSince;
private final boolean cleanState;
+
+ private NodeHttp nodeHttp;
+
// Readiness
private boolean nodeAvailable = false;
private boolean userAdminAvailable = false;
NodeState nodeState = bc.getService(nodeStateSr);
cleanState = nodeState.isClean();
+ nodeHttp = new NodeHttp();
initTrackers();
}
private void initTrackers() {
- new PrepareHttpStc().open();
+ new ServiceTracker<NodeHttp, NodeHttp>(bc, NodeHttp.class, null) {
+
+ @Override
+ public NodeHttp addingService(ServiceReference<NodeHttp> reference) {
+ httpAvailable = true;
+ checkReadiness();
+ return super.addingService(reference);
+ }
+ }.open();
new RepositoryContextStc().open();
new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
@Override
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);
}
public void shutdown() {
+ if(nodeHttp!=null)
+ nodeHttp.destroy();
if (deployConfig != null)
deployConfig.save();
}
prepareHomeRepository(nodeRepo.getRepository());
nodeAvailable = true;
checkReadiness();
- }else{
+ } else {
// TODO standalone
}
}
}
- private class PrepareHttpStc extends ServiceTracker<HttpService, HttpService> {
- private DataHttp dataHttp;
- private NodeHttp nodeHttp;
-
- public PrepareHttpStc() {
- super(bc, HttpService.class, null);
- }
-
- @Override
- public HttpService addingService(ServiceReference<HttpService> reference) {
- HttpService httpService = addHttpService(reference);
- return httpService;
- }
-
- @Override
- public void removedService(ServiceReference<HttpService> reference, HttpService service) {
- if (dataHttp != null)
- dataHttp.destroy();
- dataHttp = null;
- if (nodeHttp != null)
- nodeHttp.destroy();
- nodeHttp = null;
- }
-
- private HttpService addHttpService(ServiceReference<HttpService> 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 : "");
- }
- }
-
}
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);
+++ /dev/null
-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<Repository, Repository> 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<Repository, Repository> {
-
- @Override
- public Repository addingService(ServiceReference<Repository> 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<Repository> reference, Repository service) {
- }
-
- @Override
- public void removedService(ServiceReference<Repository> 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 ? "<default>" : 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<Session>() {
- @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<Void>() {
- // @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<Void>() {
- @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());
- }
- }
- }
-}
+++ /dev/null
-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 {
- }
-
-}
package org.argeo.cms.internal.kernel;
/** The available Jackrabbit node types */
+@Deprecated
public enum JackrabbitType {
localfs, h2, postgresql, postgresql_ds,postgresql_cluster, memory;
}
// 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";
}
// 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<String> 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();
// for (String key : keys)
// log.debug(key + "=" + bc.getProperty(key));
}
-
- static void printSystemProperties(PrintStream out){
+
+ static void printSystemProperties(PrintStream out) {
TreeMap<String, String> display = new TreeMap<>();
for (Object key : System.getProperties().keySet())
display.put(key.toString(), System.getProperty(key.toString()));
}
}
-
private KernelUtils() {
}
+++ /dev/null
-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<Session>() {
-
- @Override
- public Session run() throws Exception {
- Collection<ServiceReference<Repository>> 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("<html>");
- buf.append("<head>");
- writeMeta(buf, "og:title", escapeHTML(title));
- writeMeta(buf, "og:type", "website");
- buf.append("<meta name='twitter:card' content='summary' />");
- buf.append("<meta name='twitter:site' content='@argeo_org' />");
- 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("</head>");
- buf.append("<body>");
- buf.append(
- "<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
- .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
- writeCanonical(buf, node);
- buf.append("</body>");
- buf.append("</html>");
- 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("<meta property='").append(tag).append("' content='").append(value).append("'/>");
- }
-
- private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
- buf.append("<div>");
- if (node.hasProperty(JCR_TITLE))
- buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
- if (node.hasProperty(JCR_DESCRIPTION))
- buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
- NodeIterator children = node.getNodes();
- while (children.hasNext()) {
- writeCanonical(buf, children.nextNode());
- }
- buf.append("</div>");
- }
-
- // 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<String> en = request.getHeaderNames();
- while (en.hasMoreElements()) {
- String header = en.nextElement();
- Enumeration<String> values = request.getHeaders(header);
- while (values.hasMoreElements())
- buf.append(" " + header + ": " + values.nextElement());
- buf.append('\n');
- }
-
- // attributed
- Enumeration<String> 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);
- //
- // }
- // }
-}
+++ /dev/null
-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<SubHttpSession> subHttpSessions = new ArrayList<>();
-
- private ServiceRegistration<WebCmsSession> serviceRegistration;
-
- public WebCmsSessionImpl(String sessionId, Authorization authorization) {
- this.id = sessionId;
- this.authorization = authorization;
- // register as service
- Hashtable<String, String> 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<WebCmsSession> 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);
- }
- }
-
- }
-}
+++ /dev/null
-<config>
- <protecteditemremovehandler>
- <class name="org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler" />
- </protecteditemremovehandler>
-</config>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-<!--
-<!DOCTYPE config [
- <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
-
- <!ELEMENT iomanager (class, iohandler*) >
- <!ELEMENT iohandler (class) >
-
- <!ELEMENT propertymanager (class, propertyhandler*) >
- <!ELEMENT propertyhandler (class) >
-
- <!ELEMENT collection (nodetypes) >
- <!ELEMENT noncollection (nodetypes) >
-
- <!ELEMENT filter (class, namespaces?, nodetypes?) >
-
- <!ELEMENT class >
- <!ATTLIST class
- name CDATA #REQUIRED
- >
- <!ELEMENT namespaces (prefix | uri)* >
- <!ELEMENT prefix (CDATA) >
- <!ELEMENT uri (CDATA) >
-
- <!ELEMENT nodetypes (nodetype)* >
- <!ELEMENT nodetype (CDATA) >
-
- <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
-
- <!ELEMENT mimemapping >
- <!ATTLIST mimemapping
- extension CDATA #REQUIRED
- mimetype CDATA #REQUIRED
- >
-
- <!ELEMENT defaultmimetype (CDATA) >
-]>
--->
-
-<config>
- <!--
- Defines the IOManager implementation that is responsible for passing
- import/export request to the individual IO-handlers.
- -->
- <iomanager>
- <!-- class element defines the manager to be used. The specified class
- must implement the IOManager interface.
- Note, that the handlers are being added and called in the order
- they appear in the configuration.
- -->
- <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.VersionHandler" />
- </iohandler>
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
- </iohandler>
-<!-- <iohandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!-- </iohandler> -->
-<!-- <iohandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!-- </iohandler> -->
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
- </iohandler>
- <iohandler>
- <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
- </iohandler>
- </iomanager>
- <!--
- Example config for iomanager that populates its list of handlers with
- default values. Therefore the 'iohandler' elements are omited.
- -->
- <!--
- <iomanager>
- <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
- </iomanager>
- -->
- <!--
- Defines the PropertyManager implementation that is responsible for export
- and import of resource properties.
- -->
- <propertymanager>
- <!-- class element defines the manager to be used. The specified class
- must implement the PropertyManager interface.
- Note, that the handlers are being added and called in the order
- they appear in the configuration.
- -->
- <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.VersionHandler" />
- </propertyhandler>
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
- </propertyhandler>
-<!-- <propertyhandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!-- </propertyhandler> -->
-<!-- <propertyhandler> -->
-<!-- <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!-- </propertyhandler> -->
- <propertyhandler>
- <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
- </propertyhandler>
- </propertymanager>
- <!--
- Define nodetypes, that should never by displayed as 'collection'
- -->
- <noncollection>
- <nodetypes>
- <nodetype>nt:file</nodetype>
- <nodetype>nt:resource</nodetype>
- </nodetypes>
- </noncollection>
- <!--
- Example: Defines nodetypes, that should always be displayed as 'collection'.
- -->
- <!--
- <collection>
- <nodetypes>
- <nodetype>nt:folder</nodetype>
- <nodetype>rep:root</nodetype>
- </nodetypes>
- </collection>
- -->
- <!--
- Filter that allows to prevent certain items from being displayed.
- Please note, that this has an effect on PROPFIND calls only and does not
- provide limited access to those items matching any of the filters.
-
- However specifying a filter may cause problems with PUT or MKCOL if the
- resource to be created is being filtered out, thus resulting in inconsistent
- responses (e.g. PUT followed by PROPFIND on parent).
- -->
- <filter>
- <!-- class element defines the resource filter to be used. The specified class
- must implement the ItemFilter interface -->
- <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
- <!--
- Nodetype names to be used to filter child nodes.
- A child node can be filtered if the declaring nodetype of its definition
- is one of the nodetype names specified in the nodetypes Element.
- E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
- being hidden but no other child node of the root node, since those
- are defined by the nodetype nt:unstructered.
- -->
- <!--
- <nodetypes>
- <nodetype>rep:root</nodetype>
- </nodetypes>
- -->
- <!--
- Namespace prefixes or uris. Items having a name that matches any of the
- entries will be filtered.
- -->
- <namespaces>
- <prefix>rep</prefix>
- <prefix>jcr</prefix>
- <!--
- <uri>internal</uri>
- <uri>http://www.jcp.org/jcr/1.0</uri>
- -->
- </namespaces>
- </filter>
-
- <!--
- Optional 'mimetypeproperties' element.
- It defines additional or replaces existing mappings for the MimeResolver
- instance created by the ResourceConfig.
- The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
- If the default mime type defined by MimeResolver is 'application/octet-stream'.
- -->
- <!--
- <mimetypeproperties>
- <mimemapping extension="rtf" mimetype="application/rtf" />
- <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
- <defaultmimetype>text/html</defaultmimetype>
- </mimetypeproperties>
- -->
-</config>