org.osgi.service.http;version=0.0.0,\
org.osgi.service.http.whiteboard;version=0.0.0,\
org.osgi.framework.namespace;version=0.0.0,\
+org.argeo.cms.osgi,\
org.argeo.api,\
*
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.api.NodeConstants;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
import org.argeo.cms.servlet.internal.HttpUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
LoginContext lc;
try {
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
- new HttpRequestCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
lc.login();
} catch (LoginException e) {
lc = processUnauthorized(request, response);
@Override
public Void run() {
// TODO also set login context in order to log out ?
- ServletAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
+ RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
return null;
}
@Override
public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
- ServletAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+ RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
}
protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
// anonymous
try {
LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS,
- new HttpRequestCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
lc.login();
return lc;
} catch (LoginException e1) {
+++ /dev/null
-package org.argeo.cms.servlet;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.function.Supplier;
-
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.HttpRequest;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Authentications utilities when using servlets. */
-public class ServletAuthUtils {
- static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
- private static BundleContext bundleContext = FrameworkUtil.getBundle(ServletAuthUtils.class).getBundleContext();
-
- /**
- * Execute this supplier, using the CMS class loader as context classloader.
- * Useful to log in to JCR.
- */
- public final static <T> T doAs(Supplier<T> supplier, HttpRequest req) {
- ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(ServletAuthUtils.class.getClassLoader());
- try {
- return Subject.doAs(
- Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
- new PrivilegedAction<T>() {
-
- @Override
- public T run() {
- return supplier.get();
- }
-
- });
- } finally {
- Thread.currentThread().setContextClassLoader(currentContextCl);
- }
- }
-
- public final static void configureRequestSecurity(HttpRequest req) {
- if (req.getAttribute(AccessControlContext.class.getName()) != null)
- throw new IllegalStateException("Request already authenticated.");
- AccessControlContext acc = AccessController.getContext();
- req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
- req.setAttribute(AccessControlContext.class.getName(), acc);
- }
-
- public final static void clearRequestSecurity(HttpRequest req) {
- if (req.getAttribute(AccessControlContext.class.getName()) == null)
- throw new IllegalStateException("Cannot clear non-authenticated request.");
- req.setAttribute(REMOTE_USER, null);
- req.setAttribute(AccessControlContext.class.getName(), null);
- }
-
- public static CmsSession getCmsSession(HttpRequest req) {
- Subject subject = Subject
- .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
- CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject);
- return cmsSession;
- }
-}
import javax.servlet.http.HttpServletRequest;
-import org.argeo.cms.auth.HttpRequest;
-import org.argeo.cms.auth.HttpSession;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
-public class ServletHttpRequest implements HttpRequest {
+public class ServletHttpRequest implements RemoteAuthRequest {
private final HttpServletRequest request;
public ServletHttpRequest(HttpServletRequest request) {
}
@Override
- public HttpSession getSession() {
+ public RemoteAuthSession getSession() {
return new ServletHttpSession(request.getSession(false));
}
@Override
- public HttpSession createSession() {
+ public RemoteAuthSession createSession() {
return new ServletHttpSession(request.getSession(true));
}
import javax.servlet.http.HttpServletResponse;
-import org.argeo.cms.auth.HttpResponse;
+import org.argeo.cms.auth.RemoteAuthResponse;
-public class ServletHttpResponse implements HttpResponse {
+public class ServletHttpResponse implements RemoteAuthResponse {
private final HttpServletResponse response;
public ServletHttpResponse(HttpServletResponse response) {
package org.argeo.cms.servlet;
-import org.argeo.cms.auth.HttpSession;
+import org.argeo.cms.auth.RemoteAuthSession;
-public class ServletHttpSession implements HttpSession {
+public class ServletHttpSession implements RemoteAuthSession {
private javax.servlet.http.HttpSession session;
public ServletHttpSession(javax.servlet.http.HttpSession session) {
import org.argeo.api.cms.CmsView;
import org.argeo.cms.CmsMsg;
import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.HttpRequestCallback;
+import org.argeo.cms.auth.RemoteAuthCallback;
import org.argeo.cms.servlet.ServletHttpRequest;
import org.argeo.cms.servlet.ServletHttpResponse;
import org.argeo.cms.swt.CmsStyles;
((NameCallback) callback).setName(usernameT.getText());
else if (callback instanceof PasswordCallback && passwordT != null)
((PasswordCallback) callback).setPassword(passwordT.getTextChars());
- else if (callback instanceof HttpRequestCallback) {
- ((HttpRequestCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
- ((HttpRequestCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+ else if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+ ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
} else if (callback instanceof LanguageCallback) {
Locale toUse = null;
if (localeChoice != null)
public boolean commit() throws LoginException {
UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class));
Authorization authorization = userAdmin.getAuthorization(null);
- HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+ RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
Locale locale = Locale.getDefault();
if (request != null)
locale = request.getLocale();
}
@SuppressWarnings("unused")
- synchronized static void registerSessionAuthorization(HttpRequest request, Subject subject,
+ synchronized static void registerSessionAuthorization(RemoteAuthRequest request, Subject subject,
Authorization authorization, Locale locale) {
// synchronized in order to avoid multiple registrations
// TODO move it to a service in order to avoid static synchronization
if (request != null) {
- HttpSession httpSession = request.getSession();
+ RemoteAuthSession httpSession = request.getSession();
assert httpSession != null;
String httpSessId = httpSession.getId();
boolean anonymous = authorization.getName() == null;
+++ /dev/null
-package org.argeo.cms.auth;
-
-import java.util.Locale;
-
-/** Transitional interface to decouple from the Servlet API. */
-public interface HttpRequest {
- HttpSession getSession();
-
- HttpSession createSession();
-
- Locale getLocale();
-
- Object getAttribute(String key);
-
- void setAttribute(String key, Object object);
-
- String getHeader(String key);
-
- String getRemoteAddr();
-
- int getLocalPort();
-
- int getRemotePort();
-
-}
+++ /dev/null
-package org.argeo.cms.auth;
-
-import javax.security.auth.callback.Callback;
-
-/** Retrieves credentials from an HTTP request. */
-public class HttpRequestCallback implements Callback {
- private HttpRequest request;
- private HttpResponse response;
- private HttpSession httpSession;
-
- public HttpRequest getRequest() {
- return request;
- }
-
- public void setRequest(HttpRequest request) {
- this.request = request;
- }
-
- public HttpResponse getResponse() {
- return response;
- }
-
- public void setResponse(HttpResponse response) {
- this.response = response;
- }
-
- public HttpSession getHttpSession() {
- return httpSession;
- }
-
- public void setHttpSession(HttpSession httpSession) {
- this.httpSession = httpSession;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.auth;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.LanguageCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-/**
- * Callback handler populating {@link HttpRequestCallback}s with the provided
- * {@link HttpServletRequest}, and ignoring any other callback.
- */
-public class HttpRequestCallbackHandler implements CallbackHandler {
- final private HttpRequest request;
- final private HttpResponse response;
- final private HttpSession httpSession;
-
- public HttpRequestCallbackHandler(HttpRequest request, HttpResponse response) {
- this.request = request;
- this.httpSession = request.getSession();
- this.response = response;
- }
-
- public HttpRequestCallbackHandler(HttpSession httpSession) {
- this.httpSession = httpSession;
- this.request = null;
- this.response = null;
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks)
- if (callback instanceof HttpRequestCallback) {
- ((HttpRequestCallback) callback).setRequest(request);
- ((HttpRequestCallback) callback).setResponse(response);
- ((HttpRequestCallback) callback).setHttpSession(httpSession);
- } else if (callback instanceof LanguageCallback) {
- ((LanguageCallback) callback).setLocale(request.getLocale());
- }
- }
-
-}
+++ /dev/null
-package org.argeo.cms.auth;
-
-/** Transitional interface to decouple from the Servlet API. */
-public interface HttpResponse {
- void setHeader(String keys, String value);
-
-}
+++ /dev/null
-package org.argeo.cms.auth;
-
-/** Transitional interface to decouple from the Servlet API. */
-public interface HttpSession {
- boolean isValid();
-
- String getId();
-}
+++ /dev/null
-package org.argeo.cms.auth;
-
-import java.io.IOException;
-import java.security.cert.X509Certificate;
-import java.util.Base64;
-import java.util.Locale;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.kernel.Activator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.useradmin.Authorization;
-
-/** Use the HTTP session as the basis for authentication. */
-public class HttpSessionLoginModule implements LoginModule {
- private final static Log log = LogFactory.getLog(HttpSessionLoginModule.class);
-
- private Subject subject = null;
- private CallbackHandler callbackHandler = null;
- private Map<String, Object> sharedState = null;
-
- private HttpRequest request = null;
- private HttpResponse response = null;
-
- private BundleContext bc;
-
- private Authorization authorization;
- private Locale locale;
-
- @SuppressWarnings("unchecked")
- @Override
- public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
- Map<String, ?> options) {
- bc = FrameworkUtil.getBundle(HttpSessionLoginModule.class).getBundleContext();
- assert bc != null;
- this.subject = subject;
- this.callbackHandler = callbackHandler;
- this.sharedState = (Map<String, Object>) sharedState;
- }
-
- @Override
- public boolean login() throws LoginException {
- if (callbackHandler == null)
- return false;
- HttpRequestCallback httpCallback = new HttpRequestCallback();
- try {
- callbackHandler.handle(new Callback[] { httpCallback });
- } catch (IOException e) {
- throw new LoginException("Cannot handle http callback: " + e.getMessage());
- } catch (UnsupportedCallbackException e) {
- return false;
- }
- request = httpCallback.getRequest();
- if (request == null) {
- HttpSession httpSession = httpCallback.getHttpSession();
- if (httpSession == null)
- return false;
- // TODO factorize with below
- String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
- CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
- if (cmsSession != null) {
- authorization = cmsSession.getAuthorization();
- locale = cmsSession.getLocale();
- if (log.isTraceEnabled())
- log.trace("Retrieved authorization from " + cmsSession);
- }
- } else {
- authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
- if (authorization == null) {// search by session ID
- HttpSession httpSession = request.getSession();
- if (httpSession == null) {
- // TODO make sure this is always safe
- if (log.isTraceEnabled())
- log.trace("Create http session");
- httpSession = request.createSession();
- }
- String httpSessionId = httpSession.getId();
-// if (log.isTraceEnabled())
-// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
- CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
- if (cmsSession != null) {
- authorization = cmsSession.getAuthorization();
- locale = cmsSession.getLocale();
- if (log.isTraceEnabled())
- log.trace("Retrieved authorization from " + cmsSession);
- }
- }
- sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
- extractHttpAuth(request);
- extractClientCertificate(request);
- }
- if (authorization == null) {
- if (log.isTraceEnabled())
- log.trace("HTTP login: " + false);
- return false;
- } else {
- if (log.isTraceEnabled())
- log.trace("HTTP login: " + true);
- request.setAttribute(HttpContext.AUTHORIZATION, authorization);
- return true;
- }
- }
-
- @Override
- public boolean commit() throws LoginException {
- byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
- if (outToken != null) {
- response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
- "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
- }
-
- if (authorization != null) {
- // Locale locale = request.getLocale();
- if (locale == null && request != null)
- locale = request.getLocale();
- if (locale != null)
- subject.getPublicCredentials().add(locale);
- CmsAuthUtils.addAuthorization(subject, authorization);
- CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
- cleanUp();
- return true;
- } else {
- cleanUp();
- return false;
- }
- }
-
- @Override
- public boolean abort() throws LoginException {
- cleanUp();
- return false;
- }
-
- private void cleanUp() {
- authorization = null;
- request = null;
- }
-
- @Override
- public boolean logout() throws LoginException {
- cleanUp();
- return true;
- }
-
- private void extractHttpAuth(final HttpRequest httpRequest) {
- String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
- extractHttpAuth(authHeader);
- }
-
- private void extractHttpAuth(String authHeader) {
- if (authHeader != null) {
- StringTokenizer st = new StringTokenizer(authHeader);
- if (st.hasMoreTokens()) {
- String basic = st.nextToken();
- if (basic.equalsIgnoreCase("Basic")) {
- try {
- // TODO manipulate char[]
- Base64.Decoder decoder = Base64.getDecoder();
- String credentials = new String(decoder.decode(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();
- sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
- sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
- } else {
- throw new IllegalStateException("Invalid authentication token");
- }
- } catch (Exception e) {
- throw new IllegalStateException("Couldn't retrieve authentication", e);
- }
- } else if (basic.equalsIgnoreCase("Negotiate")) {
- String spnegoToken = st.nextToken();
- Base64.Decoder decoder = Base64.getDecoder();
- byte[] authToken = decoder.decode(spnegoToken);
- sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
- }
- }
- }
-
- // auth token
- // String mail = request.getParameter(LdapAttrs.mail.name());
- // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
- // if (authPassword != null) {
- // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
- // if (mail != null)
- // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
- // }
- }
-
- private void extractClientCertificate(HttpRequest req) {
- X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
- if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
- String certDn = certs[0].getSubjectX500Principal().getName();
- sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
- sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
- if (log.isDebugEnabled())
- log.debug("Client certificate " + certDn + " verified by servlet container");
- } // Reverse proxy verified the client certificate
- String clientDnHttpHeader = Activator.getHttpProxySslHeader();
- if (clientDnHttpHeader != null) {
- String certDn = req.getHeader(clientDnHttpHeader);
- // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
- // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
- if (certDn != null && !certDn.trim().equals("(null)")) {
- sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
- sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
- if (log.isDebugEnabled())
- log.debug("Client certificate " + certDn + " verified by reverse proxy");
- }
- }
- }
-
-}
public boolean login() throws LoginException {
if (callbackHandler == null)
return false;
- HttpRequestCallback httpCallback = new HttpRequestCallback();
+ RemoteAuthCallback httpCallback = new RemoteAuthCallback();
try {
callbackHandler.handle(new Callback[] { httpCallback });
} catch (IOException e) {
} catch (UnsupportedCallbackException e) {
return false;
}
- HttpRequest request = httpCallback.getRequest();
+ RemoteAuthRequest request = httpCallback.getRequest();
if (request == null)
return false;
IdentClient identClient = Activator.getIdentClient(request.getRemoteAddr());
--- /dev/null
+package org.argeo.cms.auth;
+
+import javax.security.auth.callback.Callback;
+
+/** Retrieves credentials from an HTTP request. */
+public class RemoteAuthCallback implements Callback {
+ private RemoteAuthRequest request;
+ private RemoteAuthResponse response;
+ private RemoteAuthSession httpSession;
+
+ public RemoteAuthRequest getRequest() {
+ return request;
+ }
+
+ public void setRequest(RemoteAuthRequest request) {
+ this.request = request;
+ }
+
+ public RemoteAuthResponse getResponse() {
+ return response;
+ }
+
+ public void setResponse(RemoteAuthResponse response) {
+ this.response = response;
+ }
+
+ public RemoteAuthSession getHttpSession() {
+ return httpSession;
+ }
+
+ public void setHttpSession(RemoteAuthSession httpSession) {
+ this.httpSession = httpSession;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+/**
+ * Callback handler populating {@link RemoteAuthCallback}s with the provided
+ * {@link HttpServletRequest}, and ignoring any other callback.
+ */
+public class RemoteAuthCallbackHandler implements CallbackHandler {
+ final private RemoteAuthRequest request;
+ final private RemoteAuthResponse response;
+ final private RemoteAuthSession httpSession;
+
+ public RemoteAuthCallbackHandler(RemoteAuthRequest request, RemoteAuthResponse response) {
+ this.request = request;
+ this.httpSession = request.getSession();
+ this.response = response;
+ }
+
+ public RemoteAuthCallbackHandler(RemoteAuthSession httpSession) {
+ this.httpSession = httpSession;
+ this.request = null;
+ this.response = null;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks)
+ if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback).setRequest(request);
+ ((RemoteAuthCallback) callback).setResponse(response);
+ ((RemoteAuthCallback) callback).setHttpSession(httpSession);
+ } else if (callback instanceof LanguageCallback) {
+ ((LanguageCallback) callback).setLocale(request.getLocale());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.util.Locale;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthRequest {
+ RemoteAuthSession getSession();
+
+ RemoteAuthSession createSession();
+
+ Locale getLocale();
+
+ Object getAttribute(String key);
+
+ void setAttribute(String key, Object object);
+
+ String getHeader(String key);
+
+ String getRemoteAddr();
+
+ int getLocalPort();
+
+ int getRemotePort();
+
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthResponse {
+ void setHeader(String keys, String value);
+
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthSession {
+ boolean isValid();
+
+ String getId();
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.function.Supplier;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Remote authentication utilities. */
+public class RemoteAuthUtils {
+ static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+ private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext();
+
+ /**
+ * Execute this supplier, using the CMS class loader as context classloader.
+ * Useful to log in to JCR.
+ */
+ public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
+ ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+ try {
+ return Subject.doAs(
+ Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
+ new PrivilegedAction<T>() {
+
+ @Override
+ public T run() {
+ return supplier.get();
+ }
+
+ });
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentContextCl);
+ }
+ }
+
+ public final static void configureRequestSecurity(RemoteAuthRequest req) {
+ if (req.getAttribute(AccessControlContext.class.getName()) != null)
+ throw new IllegalStateException("Request already authenticated.");
+ AccessControlContext acc = AccessController.getContext();
+ req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
+ req.setAttribute(AccessControlContext.class.getName(), acc);
+ }
+
+ public final static void clearRequestSecurity(RemoteAuthRequest req) {
+ if (req.getAttribute(AccessControlContext.class.getName()) == null)
+ throw new IllegalStateException("Cannot clear non-authenticated request.");
+ req.setAttribute(REMOTE_USER, null);
+ req.setAttribute(AccessControlContext.class.getName(), null);
+ }
+
+ public static CmsSession getCmsSession(RemoteAuthRequest req) {
+ Subject subject = Subject
+ .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
+ CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject);
+ return cmsSession;
+ }
+}
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
+import org.argeo.cms.internal.kernel.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.useradmin.Authorization;
+
+/** Use the HTTP session as the basis for authentication. */
+public class RemoteSessionLoginModule implements LoginModule {
+ private final static Log log = LogFactory.getLog(RemoteSessionLoginModule.class);
+
+ private Subject subject = null;
+ private CallbackHandler callbackHandler = null;
+ private Map<String, Object> sharedState = null;
+
+ private RemoteAuthRequest request = null;
+ private RemoteAuthResponse response = null;
+
+ private BundleContext bc;
+
+ private Authorization authorization;
+ private Locale locale;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+ Map<String, ?> options) {
+ bc = FrameworkUtil.getBundle(RemoteSessionLoginModule.class).getBundleContext();
+ assert bc != null;
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = (Map<String, Object>) sharedState;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ if (callbackHandler == null)
+ return false;
+ RemoteAuthCallback httpCallback = new RemoteAuthCallback();
+ try {
+ callbackHandler.handle(new Callback[] { httpCallback });
+ } catch (IOException e) {
+ throw new LoginException("Cannot handle http callback: " + e.getMessage());
+ } catch (UnsupportedCallbackException e) {
+ return false;
+ }
+ request = httpCallback.getRequest();
+ if (request == null) {
+ RemoteAuthSession httpSession = httpCallback.getHttpSession();
+ if (httpSession == null)
+ return false;
+ // TODO factorize with below
+ String httpSessionId = httpSession.getId();
+// if (log.isTraceEnabled())
+// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
+ CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
+ if (cmsSession != null) {
+ authorization = cmsSession.getAuthorization();
+ locale = cmsSession.getLocale();
+ if (log.isTraceEnabled())
+ log.trace("Retrieved authorization from " + cmsSession);
+ }
+ } else {
+ authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
+ if (authorization == null) {// search by session ID
+ RemoteAuthSession httpSession = request.getSession();
+ if (httpSession == null) {
+ // TODO make sure this is always safe
+ if (log.isTraceEnabled())
+ log.trace("Create http session");
+ httpSession = request.createSession();
+ }
+ String httpSessionId = httpSession.getId();
+// if (log.isTraceEnabled())
+// log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
+ CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
+ if (cmsSession != null) {
+ authorization = cmsSession.getAuthorization();
+ locale = cmsSession.getLocale();
+ if (log.isTraceEnabled())
+ log.trace("Retrieved authorization from " + cmsSession);
+ }
+ }
+ sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
+ extractHttpAuth(request);
+ extractClientCertificate(request);
+ }
+ if (authorization == null) {
+ if (log.isTraceEnabled())
+ log.trace("HTTP login: " + false);
+ return false;
+ } else {
+ if (log.isTraceEnabled())
+ log.trace("HTTP login: " + true);
+ request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
+ if (outToken != null) {
+ response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
+ "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
+ }
+
+ if (authorization != null) {
+ // Locale locale = request.getLocale();
+ if (locale == null && request != null)
+ locale = request.getLocale();
+ if (locale != null)
+ subject.getPublicCredentials().add(locale);
+ CmsAuthUtils.addAuthorization(subject, authorization);
+ CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
+ cleanUp();
+ return true;
+ } else {
+ cleanUp();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ cleanUp();
+ return false;
+ }
+
+ private void cleanUp() {
+ authorization = null;
+ request = null;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ cleanUp();
+ return true;
+ }
+
+ private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
+ String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
+ extractHttpAuth(authHeader);
+ }
+
+ private void extractHttpAuth(String authHeader) {
+ if (authHeader != null) {
+ StringTokenizer st = new StringTokenizer(authHeader);
+ if (st.hasMoreTokens()) {
+ String basic = st.nextToken();
+ if (basic.equalsIgnoreCase("Basic")) {
+ try {
+ // TODO manipulate char[]
+ Base64.Decoder decoder = Base64.getDecoder();
+ String credentials = new String(decoder.decode(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();
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
+ } else {
+ throw new IllegalStateException("Invalid authentication token");
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Couldn't retrieve authentication", e);
+ }
+ } else if (basic.equalsIgnoreCase("Negotiate")) {
+ String spnegoToken = st.nextToken();
+ Base64.Decoder decoder = Base64.getDecoder();
+ byte[] authToken = decoder.decode(spnegoToken);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
+ }
+ }
+ }
+
+ // auth token
+ // String mail = request.getParameter(LdapAttrs.mail.name());
+ // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
+ // if (authPassword != null) {
+ // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
+ // if (mail != null)
+ // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
+ // }
+ }
+
+ private void extractClientCertificate(RemoteAuthRequest req) {
+ X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+ if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
+ String certDn = certs[0].getSubjectX500Principal().getName();
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
+ if (log.isDebugEnabled())
+ log.debug("Client certificate " + certDn + " verified by servlet container");
+ } // Reverse proxy verified the client certificate
+ String clientDnHttpHeader = Activator.getHttpProxySslHeader();
+ if (clientDnHttpHeader != null) {
+ String certDn = req.getHeader(clientDnHttpHeader);
+ // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
+ // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
+ if (certDn != null && !certDn.trim().equals("(null)")) {
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
+ if (log.isDebugEnabled())
+ log.debug("Client certificate " + certDn + " verified by reverse proxy");
+ }
+ }
+ }
+
+}
authorizationName = principal.getName();
}
- HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+ RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
Locale locale = Locale.getDefault();
if (request != null)
locale = request.getLocale();
}
// Log and monitor new login
- HttpRequest request = (HttpRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+ RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
CmsAuthUtils.addAuthorization(subject, authorization);
// Unlock keyring (underlying login to the JCR repository)
import javax.security.auth.Subject;
-import org.argeo.cms.auth.HttpRequest;
-import org.argeo.cms.auth.HttpSession;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
import org.argeo.cms.internal.auth.CmsSessionImpl;
import org.osgi.service.useradmin.Authorization;
/** CMS session implementation in a web context. */
public class WebCmsSessionImpl extends CmsSessionImpl {
private static final long serialVersionUID = -5178883380637048025L;
- private HttpSession httpSession;
+ private RemoteAuthSession httpSession;
public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale,
- HttpRequest request) {
+ RemoteAuthRequest request) {
super(initialSubject, authorization, locale, request.getSession().getId());
httpSession = request.getSession();
}
return httpSession.isValid();
}
- public static CmsSessionImpl getCmsSession(HttpRequest request) {
+ public static CmsSessionImpl getCmsSession(RemoteAuthRequest request) {
return CmsSessionImpl.getByLocalId(request.getSession().getId());
}
}
USER {
- org.argeo.cms.auth.HttpSessionLoginModule sufficient;
+ org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.SpnegoLoginModule optional;
com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
org.argeo.cms.auth.UserAdminLoginModule sufficient;
};
ANONYMOUS {
- org.argeo.cms.auth.HttpSessionLoginModule sufficient;
+ org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.AnonymousLoginModule sufficient;
};
USER {
- org.argeo.cms.auth.HttpSessionLoginModule sufficient;
+ org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.IdentLoginModule optional;
org.argeo.cms.auth.UserAdminLoginModule requisite;
};
ANONYMOUS {
- org.argeo.cms.auth.HttpSessionLoginModule sufficient;
+ org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
org.argeo.cms.auth.AnonymousLoginModule requisite;
};
import org.argeo.api.cms.CmsView;
import org.argeo.cms.CmsException;
import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.HttpRequestCallback;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
import org.argeo.cms.servlet.ServletHttpRequest;
import org.argeo.cms.servlet.ServletHttpResponse;
import org.argeo.cms.swt.CmsStyles;
LoginContext lc;
try {
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
- new HttpRequestCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
new ServletHttpResponse(UiContext.getHttpResponse())));
lc.login();
} catch (LoginException e) {
super.handle(callbacks);
// handle HTTP context
for (Callback callback : callbacks) {
- if (callback instanceof HttpRequestCallback) {
- ((HttpRequestCallback) callback)
+ if (callback instanceof RemoteAuthCallback) {
+ ((RemoteAuthCallback) callback)
.setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
- ((HttpRequestCallback) callback)
+ ((RemoteAuthCallback) callback)
.setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
}
}
import org.argeo.api.cms.UxContext;
import org.argeo.cms.LocaleUtils;
import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
import org.argeo.cms.osgi.CmsOsgiUtils;
import org.argeo.cms.servlet.ServletHttpRequest;
import org.argeo.cms.servlet.ServletHttpResponse;
LoginContext lc;
try {
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
- new HttpRequestCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+ new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
new ServletHttpResponse(UiContext.getHttpResponse())));
lc.login();
} catch (LoginException e) {