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;
// FIXME Make it more unique
private String httpAuthRealm = "Argeo";
- // WebDav / JCR remoting
- private OpenInViewSessionProvider sessionProvider;
-
DataHttp(HttpService httpService) {
this.bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
- sessionProvider = new OpenInViewSessionProvider();
this.httpService = httpService;
repositories = new ServiceTracker<>(bc, Repository.class, new RepositoriesStc());
repositories.open();
void registerRepositoryServlets(String alias, Repository repository) {
try {
- registerWebdavServlet(alias, repository, true);
- registerWebdavServlet(alias, repository, false);
- registerRemotingServlet(alias, repository, true);
- registerRemotingServlet(alias, repository, false);
+ registerWebdavServlet(alias, repository);
+ registerRemotingServlet(alias, repository);
+ registerFilesServlet(alias, repository);
if (log.isDebugEnabled())
log.debug("Registered servlets for repository '" + alias + "'");
} catch (Exception e) {
void unregisterRepositoryServlets(String alias) {
try {
- httpService.unregister(webdavPath(alias, true));
- httpService.unregister(webdavPath(alias, false));
- httpService.unregister(remotingPath(alias, true));
- httpService.unregister(remotingPath(alias, false));
+ 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) {
}
}
- void registerWebdavServlet(String alias, Repository repository, boolean anonymous)
- throws NamespaceException, ServletException {
- WebdavServlet webdavServlet = new WebdavServlet(repository, sessionProvider);
- String path = webdavPath(alias, anonymous);
+ 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(anonymous));
+ httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext());
}
- void registerRemotingServlet(String alias, Repository repository, boolean anonymous)
- throws NamespaceException, ServletException {
- RemotingServlet remotingServlet = new RemotingServlet(repository, sessionProvider);
- String path = remotingPath(alias, anonymous);
+ void registerFilesServlet(String alias, Repository repository) throws NamespaceException, ServletException {
+ WebdavServlet filesServlet = new WebdavServlet(repository, new OpenInViewSessionProvider(alias));
+ String path = filesPath(alias);
+ Properties ip = new Properties();
+ ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
+ ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
+ httpService.registerServlet(path, filesServlet, ip, new FilesHttpContext());
+ }
+
+ void registerRemotingServlet(String alias, Repository repository) throws NamespaceException, ServletException {
+ RemotingServlet remotingServlet = new RemotingServlet(repository, new OpenInViewSessionProvider(alias));
+ String path = remotingPath(alias);
Properties ip = new Properties();
ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
// Looks like a bug in Jackrabbit remoting init
- ip.setProperty(RemotingServlet.INIT_PARAM_HOME, KernelUtils.getOsgiInstanceDir() + "/tmp/jackrabbit");
- ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting");
+ 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);
- httpService.registerServlet(path, remotingServlet, ip, new DataHttpContext(anonymous));
+ 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 webdavPath(String alias, boolean anonymous) {
- String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE;
- return pathPrefix + "/" + alias;
+ private String remotingPath(String alias) {
+ return NodeConstants.PATH_JCR + "/" + alias;
}
- private String remotingPath(String alias, boolean anonymous) {
- String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE;
- return pathPrefix + "/" + alias;
+ private String filesPath(String alias) {
+ return NodeConstants.PATH_FILES + "/" + alias;
}
- private Subject subjectFromRequest(HttpServletRequest request) {
+ private Subject subjectFromRequest(HttpServletRequest request, HttpServletResponse response) {
Authorization authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
if (authorization == null)
throw new CmsException("Not authenticated");
try {
LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
- new HttpRequestCallbackHandler(request));
+ new HttpRequestCallbackHandler(request, response));
lc.login();
return lc.getSubject();
} catch (LoginException e) {
}
}
+ 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.JCR_REPOSITORY_ALIAS);
+ Object jcrRepoAlias = reference.getProperty(NodeConstants.CN);
if (jcrRepoAlias != null) {
String alias = jcrRepoAlias.toString();
registerRepositoryServlets(alias, repository);
@Override
public void removedService(ServiceReference<Repository> reference, Repository service) {
- Object jcrRepoAlias = reference.getProperty(NodeConstants.JCR_REPOSITORY_ALIAS);
+ Object jcrRepoAlias = reference.getProperty(NodeConstants.CN);
if (jcrRepoAlias != null) {
String alias = jcrRepoAlias.toString();
unregisterRepositoryServlets(alias);
}
private class DataHttpContext implements HttpContext {
- private final boolean anonymous;
+ @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);
+ }
- DataHttpContext(boolean anonymous) {
- this.anonymous = anonymous;
+ @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 (anonymous) {
- Subject subject = KernelUtils.anonymousLogin();
- Authorization authorization = subject.getPrivateCredentials(Authorization.class).iterator().next();
- request.setAttribute(REMOTE_USER, NodeConstants.ROLE_ANONYMOUS);
- request.setAttribute(AUTHORIZATION, authorization);
- return true;
- }
-
if (log.isTraceEnabled())
KernelUtils.logRequestHeaders(log, request);
+ LoginContext lc;
try {
- new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request)).login();
- return true;
- } catch (CredentialNotFoundException e) {
- CallbackHandler token = basicAuth(request);
+ 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 {
- LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
+ lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
lc.login();
// Note: this is impossible to reliably clear the
// authorization header when access from a browser.
- return true;
} catch (LoginException e1) {
throw new CmsException("Could not login", e1);
}
} else {
- String path = request.getServletPath();
- if (path.startsWith(REMOTING_PRIVATE))
- requestBasicAuth(request, response);
+ askForWwwAuth(request, response);
+ lc = null;
return false;
}
- } catch (LoginException e) {
- throw new CmsException("Could not login", e);
}
+ request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc);
+ return true;
}
@Override
return null;
}
- private void requestBasicAuth(HttpServletRequest request, HttpServletResponse response) {
- response.setStatus(401);
- response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + httpAuthRealm + "\"");
- // request.getSession().setAttribute(ATTR_AUTH, Boolean.TRUE);
+ }
+
+ private class RemotingHttpContext implements HttpContext {
+ // private final boolean anonymous;
+
+ RemotingHttpContext() {
+ // this.anonymous = anonymous;
}
- private CallbackHandler basicAuth(final HttpServletRequest httpRequest) {
- String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION);
- if (authHeader != null) {
- StringTokenizer st = new StringTokenizer(authHeader);
- if (st.hasMoreTokens()) {
- String basic = st.nextToken();
- if (basic.equalsIgnoreCase("Basic")) {
- try {
- // TODO manipulate char[]
- String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
- // log.debug("Credentials: " + credentials);
- int p = credentials.indexOf(":");
- if (p != -1) {
- final String login = credentials.substring(0, p).trim();
- final char[] password = credentials.substring(p + 1).trim().toCharArray();
- return new CallbackHandler() {
- public void handle(Callback[] callbacks) {
- for (Callback cb : callbacks) {
- if (cb instanceof NameCallback)
- ((NameCallback) cb).setName(login);
- else if (cb instanceof PasswordCallback)
- ((PasswordCallback) cb).setPassword(password);
- else if (cb instanceof HttpRequestCallback)
- ((HttpRequestCallback) cb).setRequest(httpRequest);
- }
- }
- };
- } else {
- throw new CmsException("Invalid authentication token");
- }
- } catch (Exception e) {
- throw new CmsException("Couldn't retrieve authentication", e);
- }
+ @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;
}
*/
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 {
protected Session login(HttpServletRequest request, Repository repository, String workspace)
throws RepositoryException {
if (log.isTraceEnabled())
- log.trace("Login to workspace " + (workspace == null ? "<default>" : workspace) + " in web session "
- + request.getSession().getId());
- return repository.login(workspace);
+ 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) {
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
- 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());
- }
+ 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());
+ // }
}
}
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
try {
- Subject subject = subjectFromRequest(request);
+ Subject subject = subjectFromRequest(request, response);
Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {