From: Mathieu Baudier Date: Fri, 15 Jul 2016 10:53:14 +0000 (+0000) Subject: - Introduce WebCmsSession X-Git-Tag: argeo-commons-2.1.44~7 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=e7934b53bd71a084dc069f6500f7a168a28efdaf;p=lgpl%2Fargeo-commons.git - Introduce WebCmsSession - Make web login more robust by refactoring login modules - Reactivate kernel keystore git-svn-id: https://svn.argeo.org/commons/trunk@9040 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java index 26b96d43c..d58d7421c 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java @@ -1,5 +1,7 @@ package org.argeo.cms.auth; +import org.osgi.service.http.HttpContext; + /** Public properties of the CMS Kernel */ public interface AuthConstants { // LOGIN CONTEXTS @@ -12,11 +14,17 @@ public interface AuthConstants { public final static String ROLE_KERNEL = "OU=node"; public final static String ROLES_BASEDN = "ou=roles,ou=node"; public final static String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN; - public final static String ROLE_GROUP_ADMIN = "cn=groupAdmin," - + ROLES_BASEDN; + public final static String ROLE_GROUP_ADMIN = "cn=groupAdmin," + ROLES_BASEDN; public final static String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN; // Special system groups that cannot be edited: // user U anonymous = everyone public final static String ROLE_USER = "cn=user," + ROLES_BASEDN; public final static String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN; + + // SHARED STATE KEYS + // compatible with com.sun.security.auth.module.*LoginModule + public static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name"; + public static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password"; + public static final String SHARED_STATE_AUTHORIZATION = HttpContext.AUTHORIZATION; + } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java new file mode 100644 index 000000000..91a2d09aa --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpLoginModule.java @@ -0,0 +1,186 @@ +package org.argeo.cms.auth; + +import java.io.IOException; +import java.util.Collection; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; + +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 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.CmsException; +import org.argeo.cms.internal.kernel.Activator; +import org.argeo.cms.internal.kernel.WebCmsSessionImpl; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.http.HttpContext; +import org.osgi.service.useradmin.Authorization; + +public class HttpLoginModule implements LoginModule, AuthConstants { + private final static Log log = LogFactory.getLog(HttpLoginModule.class); + + private Subject subject = null; + private CallbackHandler callbackHandler = null; + private Map sharedState = null; + + private HttpServletRequest request = null; + + private BundleContext bc; + + @SuppressWarnings("unchecked") + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { + bc = Activator.getBundleContext(); + this.subject = subject; + this.callbackHandler = callbackHandler; + this.sharedState = (Map) sharedState; + } + + @Override + public boolean login() throws LoginException { + 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) + return false; + Authorization authorization = checkHttp(); + if (authorization == null) + return false; + sharedState.put(SHARED_STATE_AUTHORIZATION, authorization); + return true; + } + + private Authorization checkHttp() { + Authorization authorization = null; + if (request != null) { + authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION); + if (authorization == null) { + String sessionId = request.getSession().getId(); + authorization = (Authorization) request.getSession().getAttribute(HttpContext.AUTHORIZATION); + if (authorization == null) { + Collection> sr; + try { + sr = bc.getServiceReferences(WebCmsSession.class, + "(" + WebCmsSession.CMS_SESSION_ID + "=" + sessionId + ")"); + } catch (InvalidSyntaxException e) { + throw new CmsException("Cannot get CMS session for id " + sessionId, e); + } + if (sr.size() == 1) { + WebCmsSession cmsSession = bc.getService(sr.iterator().next()); + authorization = cmsSession.getAuthorization(); + if (log.isTraceEnabled()) + log.trace("Retrieved authorization from " + cmsSession); + } + } + } + } + return authorization; + } + + @Override + public boolean commit() throws LoginException { + Authorization authorization = (Authorization) sharedState.get(SHARED_STATE_AUTHORIZATION); + if (authorization == null) + return false; + if (request == null) + return false; + String sessionId = request.getSession().getId(); + if (authorization.getName() != null) { + request.setAttribute(HttpContext.REMOTE_USER, authorization.getName()); + request.setAttribute(HttpContext.AUTHORIZATION, authorization); + + HttpSession httpSession = request.getSession(); + if (httpSession.getAttribute(HttpContext.AUTHORIZATION) == null) { + + Collection> sr; + try { + sr = bc.getServiceReferences(WebCmsSession.class, + "(" + WebCmsSession.CMS_SESSION_ID + "=" + sessionId + ")"); + } catch (InvalidSyntaxException e) { + throw new CmsException("Cannot get CMS session for id " + sessionId, e); + } + ServiceReference cmsSessionRef; + if (sr.size() == 1) { + cmsSessionRef = sr.iterator().next(); + } else if (sr.size() == 0) { + Hashtable props = new Hashtable<>(); + props.put(WebCmsSession.CMS_DN, authorization.getName()); + props.put(WebCmsSession.CMS_SESSION_ID, sessionId); + WebCmsSessionImpl cmsSessionImpl = new WebCmsSessionImpl(sessionId, authorization); + ServiceRegistration cmSessionReg = bc.registerService(WebCmsSession.class, + cmsSessionImpl, props); + cmsSessionImpl.setServiceRegistration(cmSessionReg); + cmsSessionRef = cmSessionReg.getReference(); + if (log.isDebugEnabled()) + log.debug("Initialized " + cmsSessionImpl + " for " + authorization.getName()); + } else + throw new CmsException(sr.size() + " CMS sessions registered for " + sessionId); + + WebCmsSession cmsSession = bc.getService(cmsSessionRef); + cmsSession.addHttpSession(request); + if (log.isTraceEnabled()) + log.trace("Added " + request.getServletPath() + " to " + cmsSession + " (" + request.getRequestURI() + + ")"); + httpSession.setAttribute(HttpContext.AUTHORIZATION, authorization); + } + } + if (subject.getPrivateCredentials(HttpSessionId.class).size() == 0) + subject.getPrivateCredentials().add(new HttpSessionId(sessionId)); + else { + String storedSessionId = subject.getPrivateCredentials(HttpSessionId.class).iterator().next().getValue(); + if (storedSessionId.equals(sessionId)) + throw new LoginException( + "Subject already logged with session " + storedSessionId + " (not " + sessionId + ")"); + } + return true; + } + + @Override + public boolean abort() throws LoginException { + return false; + } + + @Override + public boolean logout() throws LoginException { + String sessionId; + if (subject.getPrivateCredentials(HttpSessionId.class).size() == 1) + sessionId = subject.getPrivateCredentials(HttpSessionId.class).iterator().next().getValue(); + else + return false; + Collection> srs; + try { + srs = bc.getServiceReferences(WebCmsSession.class, + "(" + WebCmsSession.CMS_SESSION_ID + "=" + sessionId + ")"); + } catch (InvalidSyntaxException e) { + throw new CmsException("Cannot retrieve CMS session #" + sessionId, e); + } + + for (Iterator> it = srs.iterator(); it.hasNext();) { + ServiceReference sr = it.next(); + WebCmsSession cmsSession = bc.getService(sr); + cmsSession.cleanUp(); + if (log.isDebugEnabled()) + log.debug("Cleaned up " + cmsSession); + } + return true; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java new file mode 100644 index 000000000..9f972ded7 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionId.java @@ -0,0 +1,33 @@ +package org.argeo.cms.auth; + +import org.argeo.cms.CmsException; + +public class HttpSessionId { + private final String value; + + public HttpSessionId(String value) { + if (value == null) + throw new CmsException("value cannot be null"); + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof HttpSessionId && ((HttpSessionId) obj).getValue().equals(value); + } + + @Override + public String toString() { + return "HttpSessionId #" + value; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java index 5dce3c61d..79714b16a 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java @@ -3,7 +3,6 @@ package org.argeo.cms.auth; import java.security.Principal; import java.util.Arrays; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -24,11 +23,11 @@ import org.argeo.cms.CmsException; import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.osgi.service.useradmin.Authorization; -public class NodeUserLoginModule implements LoginModule { +public class NodeUserLoginModule implements LoginModule, AuthConstants { private Subject subject; + private Map sharedState = null; - private final static LdapName ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, - ROLE_ANONYMOUS_NAME, ROLE_USER_NAME; + private final static LdapName ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME; private final static List RESERVED_ROLES; private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL; static { @@ -37,13 +36,10 @@ public class NodeUserLoginModule implements LoginModule { ROLE_ADMIN_NAME = new LdapName(AuthConstants.ROLE_ADMIN); ROLE_USER_NAME = new LdapName(AuthConstants.ROLE_USER); ROLE_ANONYMOUS_NAME = new LdapName(AuthConstants.ROLE_ANONYMOUS); - RESERVED_ROLES = Collections.unmodifiableList(Arrays - .asList(new LdapName[] { ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, - ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, - new LdapName(AuthConstants.ROLE_GROUP_ADMIN), - new LdapName(AuthConstants.ROLE_USER_ADMIN) })); - ROLE_ANONYMOUS_PRINCIPAL = new X500Principal( - ROLE_ANONYMOUS_NAME.toString()); + RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList(new LdapName[] { ROLE_KERNEL_NAME, + ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, new LdapName(AuthConstants.ROLE_GROUP_ADMIN), + new LdapName(AuthConstants.ROLE_USER_ADMIN) })); + ROLE_ANONYMOUS_PRINCIPAL = new X500Principal(ROLE_ANONYMOUS_NAME.toString()); } catch (InvalidNameException e) { throw new Error("Cannot initialize login module class", e); } @@ -51,19 +47,24 @@ public class NodeUserLoginModule implements LoginModule { private Authorization authorization; + @SuppressWarnings("unchecked") @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) { + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { this.subject = subject; + this.sharedState = (Map) sharedState; } @Override public boolean login() throws LoginException { - Iterator auth = subject.getPrivateCredentials( - Authorization.class).iterator(); - if (!auth.hasNext()) + authorization = (Authorization) sharedState.get(SHARED_STATE_AUTHORIZATION); + if (authorization == null) throw new FailedLoginException("No authorization available"); - authorization = auth.next(); + // Iterator auth = subject.getPrivateCredentials( + // Authorization.class).iterator(); + // if (!auth.hasNext()) + // throw new FailedLoginException("No authorization available"); + // authorization = auth.next(); return true; } @@ -71,6 +72,9 @@ public class NodeUserLoginModule implements LoginModule { public boolean commit() throws LoginException { if (authorization == null) throw new LoginException("Authorization should not be null"); + // required for display name: + subject.getPrivateCredentials().add(authorization); + Set principals = subject.getPrincipals(); try { String authName = authorization.getName(); @@ -88,8 +92,7 @@ public class NodeUserLoginModule implements LoginModule { checkUserName(name); userPrincipal = new X500Principal(name.toString()); principals.add(userPrincipal); - principals.add(new ImpliedByPrincipal(ROLE_USER_NAME, - userPrincipal)); + principals.add(new ImpliedByPrincipal(ROLE_USER_NAME, userPrincipal)); } // Add roles provided by authorization @@ -99,11 +102,9 @@ public class NodeUserLoginModule implements LoginModule { // skip } else { checkImpliedPrincipalName(roleName); - principals.add(new ImpliedByPrincipal(roleName.toString(), - userPrincipal)); + principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal)); if (roleName.equals(ROLE_ADMIN_NAME)) - principals.add(new AdminPrincipal( - SecurityConstants.ADMIN_ID)); + principals.add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); } } @@ -124,15 +125,11 @@ public class NodeUserLoginModule implements LoginModule { if (subject == null) throw new LoginException("Subject should not be null"); // Argeo - subject.getPrincipals().removeAll( - subject.getPrincipals(X500Principal.class)); - subject.getPrincipals().removeAll( - subject.getPrincipals(ImpliedByPrincipal.class)); + subject.getPrincipals().removeAll(subject.getPrincipals(X500Principal.class)); + subject.getPrincipals().removeAll(subject.getPrincipals(ImpliedByPrincipal.class)); // Jackrabbit - subject.getPrincipals().removeAll( - subject.getPrincipals(AdminPrincipal.class)); - subject.getPrincipals().removeAll( - subject.getPrincipals(AnonymousPrincipal.class)); + subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class)); + subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class)); cleanUp(); return true; } @@ -148,8 +145,7 @@ public class NodeUserLoginModule implements LoginModule { } private void checkImpliedPrincipalName(LdapName roleName) { - if (ROLE_USER_NAME.equals(roleName) - || ROLE_ANONYMOUS_NAME.equals(roleName) + if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName) || ROLE_KERNEL_NAME.equals(roleName)) throw new CmsException(roleName + " cannot be listed as role"); } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java index 2faee6fa1..29eb8bf49 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -1,10 +1,8 @@ package org.argeo.cms.auth; import java.io.IOException; -import java.util.Iterator; import java.util.Locale; import java.util.Map; -import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -17,39 +15,39 @@ import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; -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.ArgeoException; import org.argeo.cms.internal.kernel.Activator; import org.argeo.eclipse.ui.specific.UiContext; import org.osgi.framework.BundleContext; -import org.osgi.service.http.HttpContext; import org.osgi.service.useradmin.Authorization; import org.osgi.service.useradmin.User; import org.osgi.service.useradmin.UserAdmin; public class UserAdminLoginModule implements LoginModule, AuthConstants { - private final static Log log = LogFactory - .getLog(UserAdminLoginModule.class); - - private Subject subject; + // private final static Log log = + // LogFactory.getLog(UserAdminLoginModule.class); + // + // private Subject subject; private CallbackHandler callbackHandler; + private Map sharedState = null; + private boolean isAnonymous = false; - private HttpServletRequest request = null; + // private HttpServletRequest request = null; + private BundleContext bc; + @SuppressWarnings("unchecked") @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) { + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { try { - this.subject = subject; + bc = Activator.getBundleContext(); + // this.subject = subject; this.callbackHandler = callbackHandler; + this.sharedState = (Map) sharedState; if (options.containsKey("anonymous")) - isAnonymous = Boolean.parseBoolean(options.get("anonymous") - .toString()); + isAnonymous = Boolean.parseBoolean(options.get("anonymous").toString()); } catch (Exception e) { throw new ArgeoException("Cannot initialize login module", e); } @@ -57,39 +55,29 @@ public class UserAdminLoginModule implements LoginModule, AuthConstants { @Override public boolean login() throws LoginException { - BundleContext bc = Activator.getBundleContext(); - UserAdmin userAdmin = bc.getService(bc - .getServiceReference(UserAdmin.class)); + UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class)); Authorization authorization = null; if (isAnonymous) { authorization = userAdmin.getAuthorization(null); } else { - HttpRequestCallback httpCallback = new HttpRequestCallback(); + // HttpRequestCallback httpCallback = new HttpRequestCallback(); // ask for username and password NameCallback nameCallback = new NameCallback("User"); - PasswordCallback passwordCallback = new PasswordCallback( - "Password", false); + PasswordCallback passwordCallback = new PasswordCallback("Password", false); LanguageCallback langCallback = new LanguageCallback(); try { - callbackHandler.handle(new Callback[] { httpCallback, - nameCallback, passwordCallback, langCallback }); + callbackHandler.handle(new Callback[] { nameCallback, passwordCallback, langCallback }); } catch (IOException e) { - throw new LoginException("Cannot handle http callback: " - + e.getMessage()); + throw new LoginException("Cannot handle callback: " + e.getMessage()); } catch (ThreadDeath e) { - throw new ThreadDeathLoginException( - "Callbackhandler thread died", e); + throw new ThreadDeathLoginException("Callbackhandler thread died", e); } catch (UnsupportedCallbackException e) { return false; } - request = httpCallback.getRequest(); - if (request != null) { - authorization = (Authorization) request - .getAttribute(HttpContext.AUTHORIZATION); - if (authorization == null) - authorization = (Authorization) request.getSession() - .getAttribute(HttpContext.AUTHORIZATION); - } + + // check http + // request = httpCallback.getRequest(); + // authorization = checkHttp(); // i18n Locale locale = langCallback.getLocale(); @@ -97,20 +85,20 @@ public class UserAdminLoginModule implements LoginModule, AuthConstants { locale = Locale.getDefault(); UiContext.setLocale(locale); + authorization = (Authorization) sharedState.get(SHARED_STATE_AUTHORIZATION); + if (authorization == null) { // create credentials final String username = nameCallback.getName(); if (username == null || username.trim().equals("")) { // authorization = userAdmin.getAuthorization(null); - throw new CredentialNotFoundException( - "No credentials provided"); + throw new CredentialNotFoundException("No credentials provided"); } else { char[] password = {}; if (passwordCallback.getPassword() != null) password = passwordCallback.getPassword(); else - throw new CredentialNotFoundException( - "No credentials provided"); + throw new CredentialNotFoundException("No credentials provided"); User user = userAdmin.getUser(null, username); if (user == null) @@ -120,60 +108,124 @@ public class UserAdminLoginModule implements LoginModule, AuthConstants { // return false; // Log and monitor new login - if (log.isDebugEnabled()) - log.debug("Logged in to CMS with username [" + username+"]"); + // if (log.isDebugEnabled()) + // log.debug("Logged in to CMS with username [" + username + + // "]"); authorization = userAdmin.getAuthorization(user); } } - // } else { - // authorization = userAdmin.getAuthorization(null); - // } } - subject.getPrivateCredentials().add(authorization); + if (!sharedState.containsKey(SHARED_STATE_AUTHORIZATION)) + sharedState.put(SHARED_STATE_AUTHORIZATION, authorization); + // subject.getPrivateCredentials().add(authorization); return true; } + // private Authorization checkHttp() { + // Authorization authorization = null; + // if (request != null) { + // authorization = (Authorization) + // request.getAttribute(HttpContext.AUTHORIZATION); + // if (authorization == null) { + // String sessionId = request.getSession().getId(); + // authorization = (Authorization) + // request.getSession().getAttribute(HttpContext.AUTHORIZATION); + // if (authorization == null) { + // Collection> sr; + // try { + // sr = bc.getServiceReferences(CmsSession.class, + // "(" + CmsSession.CMS_SESSION_ID + "=" + sessionId + ")"); + // } catch (InvalidSyntaxException e) { + // throw new CmsException("Cannot get CMS session for id " + sessionId, e); + // } + // if (sr.size() == 1) { + // CmsSession cmsSession = bc.getService(sr.iterator().next()); + // authorization = cmsSession.getAuthorization(); + // if (log.isTraceEnabled()) + // log.trace("Retrieved authorization from " + cmsSession); + // } + // } + // } + // } + // return authorization; + // } + @Override public boolean commit() throws LoginException { - Authorization authorization = subject - .getPrivateCredentials(Authorization.class).iterator().next(); - if (request != null && authorization.getName() != null) { - request.setAttribute(HttpContext.REMOTE_USER, - authorization.getName()); - request.setAttribute(HttpContext.AUTHORIZATION, authorization); - request.getSession().setAttribute(HttpContext.AUTHORIZATION, - authorization); - subject.getPrivateCredentials().add(request.getSession()); - } + // Authorization authorization = + // subject.getPrivateCredentials(Authorization.class).iterator().next(); + // if (request != null && 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) { + // + // String sessionId = request.getSession().getId(); + // Collection> sr; + // try { + // sr = bc.getServiceReferences(CmsSession.class, + // "(" + CmsSession.CMS_SESSION_ID + "=" + sessionId + ")"); + // } catch (InvalidSyntaxException e) { + // throw new CmsException("Cannot get CMS session for id " + sessionId, + // e); + // } + // CmsSession cmsSession; + // if (sr.size() == 1) { + // cmsSession = bc.getService(sr.iterator().next()); + // } else if (sr.size() == 0) { + // Hashtable props = new Hashtable<>(); + // props.put(CmsSession.CMS_DN, authorization.getName()); + // props.put(CmsSession.CMS_SESSION_ID, sessionId); + // cmsSession = new CmsSessionImpl(sessionId, authorization); + // bc.registerService(CmsSession.class, cmsSession, props); + // if (log.isDebugEnabled()) + // log.debug("Initialized " + cmsSession + " for " + + // authorization.getName()); + // } else + // throw new CmsException(sr.size() + " CMS sessions registered for " + + // sessionId); + // cmsSession.addHttpSession(request); + // if (log.isTraceEnabled()) + // log.trace("Added " + request.getServletPath() + " to " + cmsSession + + // " (" + request.getRequestURI() + // + ")"); + // httpSession.setAttribute(HttpContext.AUTHORIZATION, authorization); + // } + // subject.getPrivateCredentials().add(request.getSession()); + // } return true; } @Override public boolean abort() throws LoginException { - cleanUp(); + // cleanUp(); return true; } @Override public boolean logout() throws LoginException { - Set httpSession = subject - .getPrivateCredentials(HttpSession.class); - Iterator it = httpSession.iterator(); - while (it.hasNext()) { - HttpSession sess = it.next(); - sess.setAttribute(HttpContext.AUTHORIZATION, null); - // sess.setMaxInactiveInterval(1);// invalidate session - } - subject.getPrivateCredentials().removeAll(httpSession); - cleanUp(); + // Set httpSession = + // subject.getPrivateCredentials(HttpSession.class); + // Iterator it = httpSession.iterator(); + // while (it.hasNext()) { + // HttpSession sess = it.next(); + // sess.setAttribute(HttpContext.AUTHORIZATION, null); + // // sess.setMaxInactiveInterval(1);// invalidate session + // + // // TODO log out CMS session + // } + // subject.getPrivateCredentials().removeAll(httpSession); + // + // cleanUp(); return true; } - private void cleanUp() { - subject.getPrivateCredentials().removeAll( - subject.getPrivateCredentials(Authorization.class)); - subject = null; - } + // private void cleanUp() { + // subject.getPrivateCredentials().removeAll(subject.getPrivateCredentials(Authorization.class)); + // subject = null; + // } } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java b/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java new file mode 100644 index 000000000..5352223ce --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/WebCmsSession.java @@ -0,0 +1,18 @@ +package org.argeo.cms.auth; + +import javax.servlet.http.HttpServletRequest; + +import org.osgi.service.useradmin.Authorization; + +public interface WebCmsSession { + public final static String CMS_DN = "cms.dn"; + public final static String CMS_SESSION_ID = "cms.sessionId"; + + public String getId(); + + public Authorization getAuthorization(); + + public void addHttpSession(HttpServletRequest request); + + public void cleanUp(); +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java index 199356198..a1988a218 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java @@ -256,6 +256,26 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { props.put("contextName", "user"); bc.registerService(ApplicationConfiguration.class, userUi, props); + // Bundle rapWorkbenchBundle = + // findBundle("org.eclipse.rap.ui.workbench"); + // if (rapWorkbenchBundle != null) + // try { + // Class clss = rapWorkbenchBundle + // .loadClass("org.eclipse.rap.ui.internal.servlet.WorkbenchApplicationConfiguration"); + // + // Hashtable rapWorkbenchProps = new Hashtable(); + // rapWorkbenchProps.put("contextName", "ui"); + // ApplicationConfiguration workbenchApplicationConfiguration = + // (ApplicationConfiguration) clss + // .newInstance(); + // bc.registerService(ApplicationConfiguration.class, + // workbenchApplicationConfiguration, + // rapWorkbenchProps); + // } catch (Exception e) { + // log.error("Cannot initalize RAP workbench", e); + // } + // Kernel thread kernelThread = new KernelThread(this); kernelThread.setContextClassLoader(Kernel.class.getClassLoader()); @@ -425,6 +445,14 @@ final class Kernel implements KernelHeader, KernelConstants, ServiceListener { return bc.getService(configurationAdmin); } + /** Can be null */ + Bundle findBundle(String symbolicName) { + for (Bundle b : bc.getBundles()) + if (b.getSymbolicName().equals(symbolicName)) + return b; + return null; + } + private void initTransactionManager() { bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration(); tmConf.setServerId(getFrameworkProp(FRAMEWORK_UUID)); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java index e06afa829..b179ec045 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java @@ -46,6 +46,7 @@ public interface KernelConstants { final static String DEFAULT_SECURITY_KEY = "argeo"; final static String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg"; final static String LOGIN_CONTEXT_KERNEL = "KERNEL"; + final static String LOGIN_CONTEXT_HARDENED_KERNEL = "HARDENED_KERNEL"; // DAV final static String WEBDAV_CONFIG = "/org/argeo/cms/internal/kernel/webdav-config.xml"; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java index bb33daecf..87ba07024 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java @@ -18,11 +18,15 @@ import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.auth.x500.X500Principal; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; import org.argeo.cms.auth.AuthConstants; /** Low-level kernel security */ class NodeSecurity implements KernelConstants { + private final static Log log = LogFactory.getLog(NodeSecurity.class); + public final static int HARDENED = 3; public final static int STAGING = 2; public final static int DEV = 1; @@ -36,10 +40,8 @@ class NodeSecurity implements KernelConstants { public NodeSecurity() { // Configure JAAS first - URL url = getClass().getClassLoader().getResource( - KernelConstants.JAAS_CONFIG); - System.setProperty("java.security.auth.login.config", - url.toExternalForm()); + URL url = getClass().getClassLoader().getResource(KernelConstants.JAAS_CONFIG); + System.setProperty("java.security.auth.login.config", url.toExternalForm()); // log.debug("JASS config: " + url.toExternalForm()); // disable Jetty autostart // System.setProperty("org.eclipse.equinox.http.jetty.autostart", @@ -47,34 +49,43 @@ class NodeSecurity implements KernelConstants { firstInit = !new File(getOsgiInstanceDir(), DIR_NODE).exists(); - this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(), - "node.p12"); - this.kernelSubject = logInKernel(); + this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(), "node.p12"); + createKeyStoreIfNeeded(); + if (keyStoreFile.exists()) + this.kernelSubject = logInHardenedKernel(); + else + this.kernelSubject = logInKernel(); } private Subject logInKernel() { final Subject kernelSubject = new Subject(); - // createKeyStoreIfNeeded(); + try { + LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); + kernelLc.login(); + } catch (LoginException e) { + throw new CmsException("Cannot log in kernel", e); + } + return kernelSubject; + } + + private Subject logInHardenedKernel() { + final Subject kernelSubject = new Subject(); + createKeyStoreIfNeeded(); CallbackHandler cbHandler = new CallbackHandler() { @Override - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { // alias - ((NameCallback) callbacks[1]) - .setName(AuthConstants.ROLE_KERNEL); + ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL); // store pwd - ((PasswordCallback) callbacks[2]).setPassword("changeit" - .toCharArray()); + ((PasswordCallback) callbacks[2]).setPassword("changeit".toCharArray()); // key pwd - ((PasswordCallback) callbacks[3]).setPassword("changeit" - .toCharArray()); + ((PasswordCallback) callbacks[3]).setPassword("changeit".toCharArray()); } }; try { - LoginContext kernelLc = new LoginContext( - KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject, + LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_HARDENED_KERNEL, kernelSubject, cbHandler); kernelLc.login(); } catch (LoginException e) { @@ -86,8 +97,7 @@ class NodeSecurity implements KernelConstants { void destroy() { // Logout kernel try { - LoginContext kernelLc = new LoginContext( - KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); + LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject); kernelLc.logout(); } catch (LoginException e) { throw new CmsException("Cannot log out kernel", e); @@ -110,28 +120,31 @@ class NodeSecurity implements KernelConstants { public void setSecurityLevel(int newValue) { if (newValue != STAGING || newValue != DEV) - throw new CmsException("Invalid value for security level " - + newValue); + throw new CmsException("Invalid value for security level " + newValue); if (newValue >= securityLevel) throw new CmsException( - "Impossible to increase security level (from " - + securityLevel + " to " + newValue + ")"); + "Impossible to increase security level (from " + securityLevel + " to " + newValue + ")"); securityLevel = newValue; } private void createKeyStoreIfNeeded() { + // for (Provider provider : Security.getProviders()) + // System.out.println(provider.getName()); + char[] ksPwd = "changeit".toCharArray(); char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length); if (!keyStoreFile.exists()) { try { keyStoreFile.getParentFile().mkdirs(); KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); - PkiUtils.generateSelfSignedCertificate(keyStore, - new X500Principal(AuthConstants.ROLE_KERNEL), keyPwd); + PkiUtils.generateSelfSignedCertificate(keyStore, new X500Principal(AuthConstants.ROLE_KERNEL), keyPwd); PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore); + if (log.isDebugEnabled()) + log.debug("Created keystore " + keyStoreFile); } catch (Exception e) { - throw new CmsException("Cannot create key store " - + keyStoreFile, e); + if (keyStoreFile.length() == 0) + keyStoreFile.delete(); + log.error("Cannot create keystore " + keyStoreFile, e); } } } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java index d8b39cdbc..f36fc89f5 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java @@ -8,6 +8,7 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.SecureRandom; +import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Date; @@ -18,6 +19,7 @@ import org.argeo.ArgeoException; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; @@ -25,47 +27,39 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; * Utilities around private keys and certificate, mostly wrapping BouncyCastle * implementations. */ -public class PkiUtils { +class PkiUtils { private final static String SECURITY_PROVIDER; static { - // Security.addProvider(new BouncyCastleProvider()); + Security.addProvider(new BouncyCastleProvider()); SECURITY_PROVIDER = "BC"; } - public static X509Certificate generateSelfSignedCertificate( - KeyStore keyStore, X500Principal x500Principal, char[] keyPassword) { + public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal, + char[] keyPassword) { try { - KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", - SECURITY_PROVIDER); + KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER); kpGen.initialize(1024, new SecureRandom()); KeyPair pair = kpGen.generateKeyPair(); Date notBefore = new Date(System.currentTimeMillis() - 10000); - Date notAfter = new Date( - System.currentTimeMillis() + 24L * 3600 * 1000); + Date notAfter = new Date(System.currentTimeMillis() + 24L * 3600 * 1000); BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); - X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder( - x500Principal, serial, notBefore, notAfter, x500Principal, - pair.getPublic()); - ContentSigner sigGen = new JcaContentSignerBuilder( - "SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) + X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore, + notAfter, x500Principal, pair.getPublic()); + ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER) .build(pair.getPrivate()); - X509Certificate cert = new JcaX509CertificateConverter() - .setProvider(SECURITY_PROVIDER).getCertificate( - certGen.build(sigGen)); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER) + .getCertificate(certGen.build(sigGen)); cert.checkValidity(new Date()); cert.verify(cert.getPublicKey()); - keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), - keyPassword, new Certificate[] { cert }); + keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert }); return cert; } catch (Exception e) { - throw new ArgeoException("Cannot generate self-signed certificate", - e); + throw new ArgeoException("Cannot generate self-signed certificate", e); } } - public static KeyStore getKeyStore(File keyStoreFile, - char[] keyStorePassword) { + public static KeyStore getKeyStore(File keyStoreFile, char[] keyStorePassword) { try { KeyStore store = KeyStore.getInstance("PKCS12", SECURITY_PROVIDER); if (keyStoreFile.exists()) { @@ -81,8 +75,7 @@ public class PkiUtils { } } - public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword, - KeyStore keyStore) { + public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword, KeyStore keyStore) { try { try (FileOutputStream fis = new FileOutputStream(keyStoreFile)) { keyStore.store(fis, keyStorePassword); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java new file mode 100644 index 000000000..f5203c940 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/WebCmsSessionImpl.java @@ -0,0 +1,89 @@ +package org.argeo.cms.internal.kernel; + +import java.util.ArrayList; +import java.util.Date; +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.ServiceRegistration; +import org.osgi.service.http.HttpContext; +import org.osgi.service.useradmin.Authorization; + +public class WebCmsSessionImpl implements WebCmsSession { + private final static Log log = LogFactory.getLog(WebCmsSessionImpl.class); + + private final String id; + private final Authorization authorization; + + private List subHttpSessions = new ArrayList<>(); + + private ServiceRegistration serviceRegistration; + + public WebCmsSessionImpl(String id, Authorization authorization) { + this.id = id; + this.authorization = authorization; + } + + public void cleanUp() { + for (SubHttpSession subSession : subHttpSessions) + subSession.cleanUp(); + serviceRegistration.unregister(); + } + + @Override + public Authorization getAuthorization() { + return authorization; + } + + @Override + public void addHttpSession(HttpServletRequest request) { + subHttpSessions.add(new SubHttpSession(request)); + } + + public String getId() { + return id; + } + + public void setServiceRegistration(ServiceRegistration serviceRegistration) { + this.serviceRegistration = serviceRegistration; + } + + 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.AUTHORIZATION, null); + httpSession.setMaxInactiveInterval(1); + } catch (Exception e) { + log.warn("Could not clean up " + sessionId, e); + } + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg index 539aeb9e6..2e63d52e1 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg @@ -1,4 +1,5 @@ USER { + org.argeo.cms.auth.HttpLoginModule requisite; org.argeo.cms.auth.UserAdminLoginModule requisite; org.argeo.cms.auth.NodeUserLoginModule requisite; }; diff --git a/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java index b342d18bd..2e102c712 100644 --- a/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java +++ b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java @@ -38,7 +38,7 @@ import org.osgi.service.packageadmin.PackageAdmin; public class JackrabbitDataModel { private final static Log log = LogFactory.getLog(JackrabbitDataModel.class); private final static String DIGEST_ALGORITHM = "MD5"; - final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd", "/org/argeo/cms/cms.cnd" }; + final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd", "/org/argeo/cms/cms.cnd", "/org/argeo/jcr/docbook/docbook.cnd" }; // data model /** Node type definitions in CND format */ diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/docbook/DocBookNames.java b/org.argeo.server.jcr/src/org/argeo/jcr/docbook/DocBookNames.java new file mode 100644 index 000000000..f4edb12b3 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/docbook/DocBookNames.java @@ -0,0 +1,7 @@ +package org.argeo.jcr.docbook; + +public interface DocBookNames { + public final static String DBK_ = "dbk:"; + public final static String DBK_PARA = DBK_ + "para"; + public final static String DBK_SECTION = DBK_ + "section"; +} diff --git a/pom.xml b/pom.xml index 66f01c11e..3481788f2 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ /srv/rpmfactory/argeo-osgi-2-staging/7/x86_64 UTF-8 + code.argeo.org