From 9dba7b01008499bdaf15c754190906d3200713fe Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 8 Oct 2015 17:09:29 +0000 Subject: [PATCH] Work on authentication git-svn-id: https://svn.argeo.org/commons/trunk@8465 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- org.argeo.cms/bnd.bnd | 2 + .../org/argeo/cms/AbstractCmsEntryPoint.java | 26 +- .../org/argeo/cms/auth/ArgeoLoginContext.java | 65 --- .../AuthConstants.java} | 14 +- .../cms/auth/LoginCanceledException.java | 8 - .../cms/auth/LoginRequiredException.java | 21 - .../cms/auth/NodeContextLoginModule.java | 72 ++++ .../NodeUserLoginModule.java} | 90 +--- .../argeo/cms/auth/UserAdminLoginModule.java | 114 ++++++ .../cms/internal/auth/KernelLoginModule.java | 6 +- .../internal/auth/SimpleJcrSecurityModel.java | 180 ++++++++ .../argeo/cms/internal/kernel/DataHttp.java | 387 ++++++++++++++++++ .../org/argeo/cms/internal/kernel/Kernel.java | 28 +- .../cms/internal/kernel/KernelUtils.java | 4 +- .../internal/kernel/NodeAuthorization.java | 4 +- .../argeo/cms/internal/kernel/NodeHttp.java | 9 +- .../cms/internal/kernel/NodeSecurity.java | 39 +- .../cms/internal/kernel/NodeUserAdmin.java | 183 ++++++++- .../org/argeo/cms/internal/kernel/jaas.cfg | 24 +- .../src/org/argeo/cms/util/UserMenu.java | 54 +-- .../src/org/argeo/cms/util/UserMenuLink.java | 16 +- .../commands/AddRemoteRepository.java | 2 +- .../jcr/internal/model/RepositoryElem.java | 2 +- .../osgi/auth/BundleContextCallback.java | 20 - .../auth/BundleContextCallbackHandler.java | 29 -- .../src/org/argeo/security/SecurityUtils.java | 21 +- org.argeo.security.ui.rap/bnd.bnd | 1 + .../security/ui/rap/AnonymousEntryPoint.java | 7 +- .../security/ui/rap/SecureEntryPoint.java | 19 +- .../security/ui/rap/SecureRapActivator.java | 15 +- .../security/ui/internal/CurrentUser.java | 2 +- 31 files changed, 1069 insertions(+), 395 deletions(-) delete mode 100644 org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java rename org.argeo.cms/src/org/argeo/cms/{KernelHeader.java => auth/AuthConstants.java} (75%) delete mode 100644 org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java delete mode 100644 org.argeo.cms/src/org/argeo/cms/auth/LoginRequiredException.java create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/NodeContextLoginModule.java rename org.argeo.cms/src/org/argeo/cms/{internal/auth/UserAdminLoginModule.java => auth/NodeUserLoginModule.java} (59%) create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/auth/SimpleJcrSecurityModel.java create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java delete mode 100644 org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallback.java delete mode 100644 org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHandler.java diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index e98fea919..6525a72bf 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -18,4 +18,6 @@ org.apache.jackrabbit.*;resolution:=optional,\ org.joda.time.*;resolution:=optional,\ org.springframework.context,\ org.springframework.core.io,\ +org.apache.jackrabbit.webdav.server,\ +org.apache.jackrabbit.webdav.jcr,\ * diff --git a/org.argeo.cms/src/org/argeo/cms/AbstractCmsEntryPoint.java b/org.argeo.cms/src/org/argeo/cms/AbstractCmsEntryPoint.java index a9ad03a12..9f3fee857 100644 --- a/org.argeo.cms/src/org/argeo/cms/AbstractCmsEntryPoint.java +++ b/org.argeo.cms/src/org/argeo/cms/AbstractCmsEntryPoint.java @@ -3,9 +3,7 @@ package org.argeo.cms; import java.security.AccessControlContext; import java.security.PrivilegedAction; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import java.util.ResourceBundle; import javax.jcr.Node; import javax.jcr.Property; @@ -14,6 +12,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.auth.x500.X500Principal; import javax.servlet.http.HttpServletRequest; @@ -22,9 +21,7 @@ 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.auth.ArgeoLoginContext; -import org.argeo.cms.auth.LoginRequiredException; -import org.argeo.cms.i18n.Msg; +import org.argeo.cms.auth.AuthConstants; import org.argeo.jcr.JcrUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.application.AbstractEntryPoint; @@ -71,7 +68,7 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint HttpServletRequest httpRequest = RWT.getRequest(); final HttpSession httpSession = httpRequest.getSession(); AccessControlContext acc = (AccessControlContext) httpSession - .getAttribute(KernelHeader.ACCESS_CONTROL_CONTEXT); + .getAttribute(AuthConstants.ACCESS_CONTROL_CONTEXT); if (acc != null && Subject.getSubject(acc).getPrincipals(X500Principal.class) .size() == 1) { @@ -81,13 +78,13 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint // Initial login try { - new ArgeoLoginContext(KernelHeader.LOGIN_CONTEXT_USER, subject) + new LoginContext(AuthConstants.LOGIN_CONTEXT_USER, subject) .login(); } catch (LoginException e) { // if (log.isTraceEnabled()) // log.trace("Cannot authenticate user", e); try { - new ArgeoLoginContext(KernelHeader.LOGIN_CONTEXT_ANONYMOUS, + new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject).login(); } catch (LoginException eAnonymous) { throw new ArgeoException("Cannot initialize subject", @@ -149,8 +146,9 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint */ protected Node getDefaultNode(Session session) throws RepositoryException { if (!session.hasPermission(defaultPath, "read")) { - if (session.getUserID().equals("anonymous")) - throw new LoginRequiredException(); + if (session.getUserID().equals(AuthConstants.ROLE_ANONYMOUS)) + // TODO throw a special exception + throw new CmsException("Login required"); else throw new CmsException("Unauthorized"); } @@ -193,11 +191,11 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint } catch (Exception e) { try { // TODO find a less hacky way to log out - new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_ANONYMOUS, + new LoginContext( + AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject).logout(); - new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_ANONYMOUS, + new LoginContext( + AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject).login(); } catch (LoginException eAnonymous) { throw new ArgeoException( diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java b/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java deleted file mode 100644 index 474cfcc0a..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/ArgeoLoginContext.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.argeo.cms.auth; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -/** - * Integrates JAAS with the Argeo platform, by using the Argeo CMS bundle - * classloader as context classloader. - */ -public class ArgeoLoginContext extends LoginContext { - private static ThreadLocal currentContextClassLoader = new ThreadLocal() { - @Override - protected ClassLoader initialValue() { - return Thread.currentThread().getContextClassLoader(); - } - - @Override - public void set(ClassLoader value) { - throw new IllegalAccessError("Current class loader is read-only"); - } - }; - - public ArgeoLoginContext(String name, Subject subject, - CallbackHandler callbackHandler) throws LoginException { - super(setContextClassLoaderForName(name), subject, callbackHandler); - // reset current context classloader - Thread.currentThread().setContextClassLoader( - currentContextClassLoader.get()); - currentContextClassLoader.remove(); - } - - public ArgeoLoginContext(String name, Subject subject) - throws LoginException { - super(setContextClassLoaderForName(name), subject); - // reset current context classloader - Thread.currentThread().setContextClassLoader( - currentContextClassLoader.get()); - currentContextClassLoader.remove(); - } - - /** - * Set the context classloader - * - * @return the passed name, in order to chain calls in the constructor - */ - private static String setContextClassLoaderForName(String name) { - // store current context class loader; - currentContextClassLoader.get(); - Thread.currentThread().setContextClassLoader( - ArgeoLoginContext.class.getClassLoader()); - return name; - } - - @Override - public void login() throws LoginException { - super.login(); - } - - @Override - public void logout() throws LoginException { - super.logout(); - } -} diff --git a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java similarity index 75% rename from org.argeo.cms/src/org/argeo/cms/KernelHeader.java rename to org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java index 2e4049192..b5b3c776c 100644 --- a/org.argeo.cms/src/org/argeo/cms/KernelHeader.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/AuthConstants.java @@ -1,9 +1,7 @@ -package org.argeo.cms; +package org.argeo.cms.auth; /** Public properties of the CMS Kernel */ -public interface KernelHeader { - final static String SECURITY_PROVIDER = "BC";// Bouncy Castle - +public interface AuthConstants { // LOGIN CONTEXTS final static String LOGIN_CONTEXT_USER = "USER"; final static String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS"; @@ -25,9 +23,7 @@ public interface KernelHeader { public final static String ROLE_USER = "cn=user," + ROLES_BASEDN; public final static String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN; - // RESERVED USERNAMES - public final static String USERNAME_ADMIN = "root"; - public final static String USERNAME_DEMO = "demo"; - @Deprecated - public final static String USERNAME_ANONYMOUS = "anonymous"; + // SHARED STATE KEYS + public final static String BUNDLE_CONTEXT_KEY = "org.argeo.security.bundleContext"; + public final static String AUTHORIZATION_KEY = "org.argeo.security.authorization"; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java b/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java deleted file mode 100644 index 731cdd170..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/LoginCanceledException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.auth; - -import javax.security.auth.login.LoginException; - -public class LoginCanceledException extends LoginException { - private static final long serialVersionUID = 8289162094013471043L; - -} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/LoginRequiredException.java b/org.argeo.cms/src/org/argeo/cms/auth/LoginRequiredException.java deleted file mode 100644 index 8b082d054..000000000 --- a/org.argeo.cms/src/org/argeo/cms/auth/LoginRequiredException.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.auth; - -import org.argeo.cms.CmsException; - -/** Throwing this exception triggers redirection to a login page. */ -public class LoginRequiredException extends CmsException { - private static final long serialVersionUID = 7009402894657958151L; - - public LoginRequiredException() { - super("Login is required"); - } - - public LoginRequiredException(String message, Throwable e) { - super(message, e); - } - - public LoginRequiredException(String message) { - super(message); - } - -} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/NodeContextLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/NodeContextLoginModule.java new file mode 100644 index 000000000..d898e6c48 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/NodeContextLoginModule.java @@ -0,0 +1,72 @@ +package org.argeo.cms.auth; + +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.argeo.cms.internal.kernel.Activator; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.Authorization; + +/** Populates the shared state with this node context. */ +public class NodeContextLoginModule implements LoginModule, AuthConstants { + private Subject subject; + private Map sharedState; + + @SuppressWarnings("unchecked") + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + this.subject = subject; + this.sharedState = (Map) sharedState; + } + + @Override + public boolean login() throws LoginException { + sharedState.put(AuthConstants.BUNDLE_CONTEXT_KEY, Activator.getBundleContext()); + Display display = Display.getCurrent(); + if (display != null) { + Authorization authorization = (Authorization) display + .getData(AuthConstants.AUTHORIZATION_KEY); + if (authorization != null) + sharedState.put(AuthConstants.AUTHORIZATION_KEY, authorization); + } + return true; + } + + @Override + public boolean commit() throws LoginException { + Display display = Display.getCurrent(); + if (display != null) { + Authorization authorization = subject + .getPrivateCredentials(Authorization.class).iterator() + .next(); + display.setData(AuthConstants.AUTHORIZATION_KEY, authorization); + } + return true; + } + + @Override + public boolean abort() throws LoginException { + sharedState.remove(AuthConstants.BUNDLE_CONTEXT_KEY); + sharedState.remove(AuthConstants.AUTHORIZATION_KEY); + Display display = Display.getCurrent(); + if (display != null) + display.setData(AuthConstants.AUTHORIZATION_KEY, null); + return true; + } + + @Override + public boolean logout() throws LoginException { + sharedState.remove(AuthConstants.BUNDLE_CONTEXT_KEY); + sharedState.remove(AuthConstants.AUTHORIZATION_KEY); + Display display = Display.getCurrent(); + if (display != null) + display.setData(AuthConstants.AUTHORIZATION_KEY, null); + return true; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java similarity index 59% rename from org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java rename to org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java index 5fca43be3..74fe4e421 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/UserAdminLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/NodeUserLoginModule.java @@ -1,8 +1,9 @@ -package org.argeo.cms.internal.auth; +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; @@ -10,11 +11,7 @@ import java.util.Set; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.security.auth.x500.X500Principal; @@ -23,17 +20,11 @@ import org.apache.jackrabbit.core.security.AnonymousPrincipal; import org.apache.jackrabbit.core.security.SecurityConstants; import org.apache.jackrabbit.core.security.principal.AdminPrincipal; import org.argeo.cms.CmsException; -import org.argeo.cms.KernelHeader; -import org.argeo.cms.internal.kernel.Activator; -import org.osgi.framework.BundleContext; +import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -public class UserAdminLoginModule implements LoginModule { +public class NodeUserLoginModule implements LoginModule { private Subject subject; - private CallbackHandler callbackHandler; - private boolean isAnonymous = false; private final static LdapName ROLE_KERNEL_NAME, ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME; @@ -41,15 +32,15 @@ public class UserAdminLoginModule implements LoginModule { private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL; static { try { - ROLE_KERNEL_NAME = new LdapName(KernelHeader.ROLE_KERNEL); - ROLE_ADMIN_NAME = new LdapName(KernelHeader.ROLE_ADMIN); - ROLE_USER_NAME = new LdapName(KernelHeader.ROLE_USER); - ROLE_ANONYMOUS_NAME = new LdapName(KernelHeader.ROLE_ANONYMOUS); + ROLE_KERNEL_NAME = new LdapName(AuthConstants.ROLE_KERNEL); + 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(KernelHeader.ROLE_GROUP_ADMIN), - new LdapName(KernelHeader.ROLE_USER_ADMIN) })); + new LdapName(AuthConstants.ROLE_GROUP_ADMIN), + new LdapName(AuthConstants.ROLE_USER_ADMIN) })); ROLE_ANONYMOUS_PRINCIPAL = new X500Principal( ROLE_ANONYMOUS_NAME.toString()); } catch (InvalidNameException e) { @@ -62,61 +53,16 @@ public class UserAdminLoginModule implements LoginModule { @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - try { - this.subject = subject; - this.callbackHandler = callbackHandler; - if (options.containsKey("anonymous")) - isAnonymous = Boolean.parseBoolean(options.get("anonymous") - .toString()); - // String ldifFile = options.get("ldifFile").toString(); - // InputStream in = new URL(ldifFile).openStream(); - // userAdmin = new LdifUserAdmin(in); - } catch (Exception e) { - throw new CmsException("Cannot initialize login module", e); - } + this.subject = subject; } @Override public boolean login() throws LoginException { - // TODO use a callback in order to get the bundle context - BundleContext bc = Activator.getBundleContext(); - UserAdmin userAdmin = bc.getService(bc - .getServiceReference(UserAdmin.class)); - final User user; - - if (!isAnonymous) { - // ask for username and password - NameCallback nameCallback = new NameCallback("User"); - PasswordCallback passwordCallback = new PasswordCallback( - "Password", false); - // handle callbacks - try { - callbackHandler.handle(new Callback[] { nameCallback, - passwordCallback }); - } catch (Exception e) { - throw new CmsException("Cannot handle callbacks", e); - } - - // create credentials - final String username = nameCallback.getName(); - if (username == null || username.trim().equals("")) - throw new CredentialNotFoundException("No credentials provided"); - - char[] password = {}; - if (passwordCallback.getPassword() != null) - password = passwordCallback.getPassword(); - else - throw new CredentialNotFoundException("No credentials provided"); - - user = userAdmin.getUser(null, username); - if (user == null) - return false; - if (!user.hasCredential(null, password)) - return false; - } else - // anonymous - user = null; - this.authorization = userAdmin.getAuthorization(user); + Iterator auth = subject.getPrivateCredentials( + Authorization.class).iterator(); + if (!auth.hasNext()) + return false; + authorization = auth.next(); return true; } @@ -183,6 +129,10 @@ public class UserAdminLoginModule implements LoginModule { subject.getPrincipals(X500Principal.class)); subject.getPrincipals().removeAll( subject.getPrincipals(ImpliedByPrincipal.class)); + subject.getPrincipals().removeAll( + subject.getPrincipals(AdminPrincipal.class)); + subject.getPrincipals().removeAll( + subject.getPrincipals(AnonymousPrincipal.class)); cleanUp(); return true; } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java new file mode 100644 index 000000000..3e44e6533 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java @@ -0,0 +1,114 @@ +package org.argeo.cms.auth; + +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.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.CredentialNotFoundException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.argeo.ArgeoException; +import org.osgi.framework.BundleContext; +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 Subject subject; + private Map sharedState; + private CallbackHandler callbackHandler; + private boolean isAnonymous = false; + + @SuppressWarnings("unchecked") + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + try { + this.subject = subject; + this.sharedState = (Map) sharedState; + this.callbackHandler = callbackHandler; + if (options.containsKey("anonymous")) + isAnonymous = Boolean.parseBoolean(options.get("anonymous") + .toString()); + } catch (Exception e) { + throw new ArgeoException("Cannot initialize login module", e); + } + } + + @Override + public boolean login() throws LoginException { + BundleContext bc = (BundleContext) sharedState + .get(AuthConstants.BUNDLE_CONTEXT_KEY); + UserAdmin userAdmin = bc.getService(bc + .getServiceReference(UserAdmin.class)); + Authorization authorization = (Authorization) sharedState + .get(AuthConstants.AUTHORIZATION_KEY); + if (authorization == null) + if (!isAnonymous) { + // ask for username and password + NameCallback nameCallback = new NameCallback("User"); + PasswordCallback passwordCallback = new PasswordCallback( + "Password", false); + + // handle callbacks + try { + callbackHandler.handle(new Callback[] { nameCallback, + passwordCallback }); + } catch (Exception e) { + throw new ArgeoException("Cannot handle callbacks", e); + } + + // create credentials + final String username = nameCallback.getName(); + if (username == null || username.trim().equals("")) + throw new CredentialNotFoundException( + "No credentials provided"); + + char[] password = {}; + if (passwordCallback.getPassword() != null) + password = passwordCallback.getPassword(); + else + throw new CredentialNotFoundException( + "No credentials provided"); + + User user = userAdmin.getUser(null, username); + if (user == null) + return false; + if (!user.hasCredential(null, password)) + return false; + authorization = userAdmin.getAuthorization(user); + } else { + authorization = userAdmin.getAuthorization(null); + } + subject.getPrivateCredentials().add(authorization); + return true; + } + + @Override + public boolean commit() throws LoginException { + return true; + } + + @Override + public boolean abort() throws LoginException { + cleanUp(); + return true; + } + + @Override + public boolean logout() throws LoginException { + cleanUp(); + return true; + } + + private void cleanUp() { + subject.getPrivateCredentials().removeAll( + subject.getPrivateCredentials(Authorization.class)); + subject = null; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java index f96bc8880..8983d65dc 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/KernelLoginModule.java @@ -14,7 +14,7 @@ import javax.security.auth.x500.X500PrivateCredential; import org.apache.jackrabbit.core.security.SecurityConstants; import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.auth.AuthConstants; public class KernelLoginModule implements LoginModule { private Subject subject; @@ -39,9 +39,9 @@ public class KernelLoginModule implements LoginModule { if (names.isEmpty() || names.size() > 1) throw new LoginException("Kernel must have been named"); X500Principal name = names.iterator().next(); - if (!KernelHeader.ROLE_KERNEL.equals(name.getName())) + if (!AuthConstants.ROLE_KERNEL.equals(name.getName())) throw new LoginException("Kernel must be named named " - + KernelHeader.ROLE_KERNEL); + + AuthConstants.ROLE_KERNEL); // Private certificate Set privateCerts = subject .getPrivateCredentials(X500PrivateCredential.class); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/SimpleJcrSecurityModel.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/SimpleJcrSecurityModel.java new file mode 100644 index 000000000..7cb682cd0 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/SimpleJcrSecurityModel.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.cms.internal.auth; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.security.Privilege; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; + +/** + * Manages data expected by the Argeo security model, such as user home and + * profile. + */ +public class SimpleJcrSecurityModel implements JcrSecurityModel { + private final static Log log = LogFactory + .getLog(SimpleJcrSecurityModel.class); + // ArgeoNames not implemented as interface in order to ease derivation by + // Jackrabbit bundles + + /** The home base path. */ + private String homeBasePath = "/home"; + private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH; + + @Override + public void init(Session adminSession) throws RepositoryException { + JcrUtils.mkdirs(adminSession, homeBasePath); + JcrUtils.mkdirs(adminSession, peopleBasePath); + adminSession.save(); + + JcrUtils.addPrivilege(adminSession, homeBasePath, + AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); + JcrUtils.addPrivilege(adminSession, peopleBasePath, + AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL); + } + + public synchronized Node sync(Session session, String username, + List roles) { + // TODO check user name validity (e.g. should not start by ROLE_) + + try { + Node userHome = UserJcrUtils.getUserHome(session, username); + if (userHome == null) { + String homePath = generateUserPath(homeBasePath, username); + userHome = JcrUtils.mkdirs(session, homePath); + // userHome = JcrUtils.mkfolders(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + session.save(); + + JcrUtils.clearAccessControList(session, homePath, username); + JcrUtils.addPrivilege(session, homePath, username, + Privilege.JCR_ALL); + } else { + // for backward compatibility with pre 1.0 security model + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { + userHome.getNode(ArgeoNames.ARGEO_PROFILE).remove(); + userHome.getSession().save(); + } + } + + // Remote roles + if (roles != null) { + // writeRemoteRoles(userHome, roles); + } + + Node userProfile = UserJcrUtils.getUserProfile(session, username); + // new user + if (userProfile == null) { + String personPath = generateUserPath(peopleBasePath, username); + Node personBase = JcrUtils.mkdirs(session, personPath); + userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, + true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, + true); + userProfile.setProperty( + ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); + session.save(); + + JcrUtils.clearAccessControList(session, userProfile.getPath(), + username); + JcrUtils.addPrivilege(session, userProfile.getPath(), username, + Privilege.JCR_READ); + } + + // Remote roles + if (roles != null) { + writeRemoteRoles(userProfile, roles); + } + return userProfile; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot sync node security model for " + + username, e); + } + } + + /** Generate path for a new user home */ + protected String generateUserPath(String base, String username) { + int atIndex = username.indexOf('@'); + if (atIndex > 0) { + String domain = username.substring(0, atIndex); + String name = username.substring(atIndex + 1); + return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/' + + name; + } else if (atIndex == 0 || atIndex == (username.length() - 1)) { + throw new ArgeoException("Unsupported username " + username); + } else { + return base + '/' + JcrUtils.firstCharsToPath(username, 2) + '/' + + username; + } + } + + /** Write remote roles used by remote access in the home directory */ + protected void writeRemoteRoles(Node userHome, List roles) + throws RepositoryException { + boolean writeRoles = false; + if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] remoteRoles = userHome.getProperty( + ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + if (remoteRoles.length != roles.size()) + writeRoles = true; + else + for (int i = 0; i < remoteRoles.length; i++) + if (!remoteRoles[i].getString().equals(roles.get(i))) + writeRoles = true; + } else + writeRoles = true; + + if (writeRoles) { + userHome.getSession().getWorkspace().getVersionManager() + .checkout(userHome.getPath()); + String[] roleIds = roles.toArray(new String[roles.size()]); + userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds); + JcrUtils.updateLastModified(userHome); + userHome.getSession().save(); + userHome.getSession().getWorkspace().getVersionManager() + .checkin(userHome.getPath()); + if (log.isDebugEnabled()) + log.debug("Wrote remote roles " + roles + " for " + + userHome.getProperty(ArgeoNames.ARGEO_USER_ID)); + } + + } + + public void setHomeBasePath(String homeBasePath) { + this.homeBasePath = homeBasePath; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java new file mode 100644 index 000000000..102bb7702 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java @@ -0,0 +1,387 @@ +package org.argeo.cms.internal.kernel; + +import static org.argeo.cms.auth.AuthConstants.ACCESS_CONTROL_CONTEXT; + +import java.io.IOException; +import java.io.Serializable; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.cert.X509Certificate; +import java.util.Properties; +import java.util.StringTokenizer; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.server.SessionProvider; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; +import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.JcrUtils; +import org.osgi.service.http.HttpContext; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; +import org.osgi.service.useradmin.Authorization; + +/** + * Intercepts and enriches http access, mainly focusing on security and + * transactionality. + */ +class DataHttp implements KernelConstants, ArgeoJcrConstants { + private final static Log log = LogFactory.getLog(DataHttp.class); + + private final static String ATTR_AUTH = "auth"; + private final static String HEADER_AUTHORIZATION = "Authorization"; + private final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + // private final AuthenticationManager authenticationManager; + private final HttpService httpService; + + // FIXME Make it more unique + private String httpAuthRealm = "Argeo"; + + // WebDav / JCR remoting + private OpenInViewSessionProvider sessionProvider; + + DataHttp(HttpService httpService, JackrabbitNode node) { + this.httpService = httpService; + sessionProvider = new OpenInViewSessionProvider(); + registerRepositoryServlets(ALIAS_NODE, node); + } + + public void destroy() { + unregisterRepositoryServlets(ALIAS_NODE); + } + + void registerRepositoryServlets(String alias, Repository repository) { + try { + registerWebdavServlet(alias, repository, true); + registerWebdavServlet(alias, repository, false); + registerRemotingServlet(alias, repository, true); + registerRemotingServlet(alias, repository, false); + } catch (Exception e) { + throw new CmsException( + "Could not register servlets for repository " + alias, e); + } + } + + void unregisterRepositoryServlets(String alias) { + // FIXME unregister servlets + } + + void registerWebdavServlet(String alias, Repository repository, + boolean anonymous) throws NamespaceException, ServletException { + WebdavServlet webdavServlet = new WebdavServlet(repository, + sessionProvider); + String pathPrefix = anonymous ? WEBDAV_PUBLIC : WEBDAV_PRIVATE; + String path = pathPrefix + "/" + alias; + Properties ip = new Properties(); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG); + ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path); + // httpService.registerFilter(path, anonymous ? new AnonymousFilter() + // : new DavFilter(), null, null); + // Cast to servlet because of a weird behaviour in Eclipse + httpService.registerServlet(path, (Servlet) webdavServlet, ip, + new DataHttpContext(anonymous)); + } + + void registerRemotingServlet(String alias, Repository repository, + boolean anonymous) throws NamespaceException, ServletException { + String pathPrefix = anonymous ? REMOTING_PUBLIC : REMOTING_PRIVATE; + RemotingServlet remotingServlet = new RemotingServlet(repository, + sessionProvider); + String path = pathPrefix + "/" + 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"); + // in order to avoid annoying warning. + ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, ""); + // Cast to servlet because of a weird behaviour in Eclipse + // httpService.registerFilter(path, anonymous ? new AnonymousFilter() + // : new DavFilter(), null, null); + httpService.registerServlet(path, (Servlet) remotingServlet, ip, + new DataHttpContext(anonymous)); + } + + // private Boolean isSessionAuthenticated(HttpSession httpSession) { + // SecurityContext contextFromSession = (SecurityContext) httpSession + // .getAttribute(SPRING_SECURITY_CONTEXT_KEY); + // return contextFromSession != null; + // } + + private void requestBasicAuth(HttpSession httpSession, + HttpServletResponse response) { + response.setStatus(401); + response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + + httpAuthRealm + "\""); + httpSession.setAttribute(ATTR_AUTH, Boolean.TRUE); + } + + private CallbackHandler basicAuth(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[] + 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 { + throw new CmsException( + "Invalid authentication token"); + } + } catch (Exception e) { + throw new CmsException( + "Couldn't retrieve authentication", e); + } + } + } + } + throw new CmsException("Couldn't retrieve authentication"); + } + + private X509Certificate extractCertificate(HttpServletRequest req) { + X509Certificate[] certs = (X509Certificate[]) req + .getAttribute("javax.servlet.request.X509Certificate"); + if (null != certs && certs.length > 0) { + return certs[0]; + } + return null; + } + + private Subject subjectFromRequest(HttpServletRequest request) { + HttpSession httpSession = request.getSession(); + Authorization authorization = (Authorization) request + .getAttribute(HttpContext.AUTHORIZATION); + if (authorization == null) + throw new CmsException("Not authenticated"); + AccessControlContext acc = (AccessControlContext) httpSession + .getAttribute(AuthConstants.ACCESS_CONTROL_CONTEXT); + Subject subject = Subject.getSubject(acc); + return subject; + } + + private class DataHttpContext implements HttpContext { + private final boolean anonymous; + + DataHttpContext(boolean anonymous) { + this.anonymous = anonymous; + } + + @Override + public boolean handleSecurity(HttpServletRequest request, + HttpServletResponse response) throws IOException { + final Subject subject; + + if (anonymous) { + subject = KernelUtils.anonymousLogin(); + Authorization authorization = subject + .getPrivateCredentials(Authorization.class).iterator() + .next(); + request.setAttribute(AUTHORIZATION, authorization); + return true; + } + + final HttpSession httpSession = request.getSession(); + AccessControlContext acc = (AccessControlContext) httpSession + .getAttribute(AuthConstants.ACCESS_CONTROL_CONTEXT); + if (acc != null) { + subject = Subject.getSubject(acc); + } else { + // Process basic auth + String basicAuth = request.getHeader(HEADER_AUTHORIZATION); + if (basicAuth != null) { + CallbackHandler token = basicAuth(basicAuth); + try { + LoginContext lc = new LoginContext( + AuthConstants.LOGIN_CONTEXT_USER, token); + lc.login(); + subject = lc.getSubject(); + } catch (LoginException e) { + throw new CmsException("Could not login", e); + } + Subject.doAs(subject, new PrivilegedAction() { + public Void run() { + // add security context to session + httpSession.setAttribute(ACCESS_CONTROL_CONTEXT, + AccessController.getContext()); + return null; + } + }); + } else { + requestBasicAuth(httpSession, response); + return false; + } + } + // authenticate request + Authorization authorization = subject + .getPrivateCredentials(Authorization.class).iterator() + .next(); + request.setAttribute(AUTHORIZATION, authorization); + return true; + } + + @Override + public URL getResource(String name) { + return Activator.getBundleContext().getBundle().getResource(name); + } + + @Override + public String getMimeType(String name) { + return null; + } + + } + + /** + * Implements an open session in view patter: a new JCR session is created + * for each request + */ + private class OpenInViewSessionProvider implements SessionProvider, + Serializable { + private static final long serialVersionUID = 2270957712453841368L; + + public Session getSession(HttpServletRequest request, Repository rep, + String workspace) throws javax.jcr.LoginException, + ServletException, RepositoryException { + return login(request, rep, workspace); + } + + protected Session login(HttpServletRequest request, + Repository repository, String workspace) + throws RepositoryException { + if (log.isTraceEnabled()) + log.trace("Login to workspace " + + (workspace == null ? "" : workspace) + + " in web session " + request.getSession().getId()); + return repository.login(workspace); + } + + public void releaseSession(Session session) { + JcrUtils.logoutQuietly(session); + if (log.isTraceEnabled()) + log.trace("Logged out remote JCR session " + session); + } + } + + private class WebdavServlet extends SimpleWebdavServlet { + private static final long serialVersionUID = -4687354117811443881L; + private final Repository repository; + + public WebdavServlet(Repository repository, + SessionProvider sessionProvider) { + this.repository = repository; + setSessionProvider(sessionProvider); + } + + public Repository getRepository() { + return repository; + } + + @Override + protected void service(final HttpServletRequest request, + final HttpServletResponse response) throws ServletException, + IOException { + try { + Subject subject = subjectFromRequest(request); + Subject.doAs(subject, new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + WebdavServlet.super.service(request, response); + return null; + } + }); + } catch (PrivilegedActionException e) { + throw new CmsException("Cannot process webdav request", + e.getException()); + } + } + } + + private class RemotingServlet extends JcrRemotingServlet { + private static final long serialVersionUID = 4605238259548058883L; + private final Repository repository; + private final SessionProvider sessionProvider; + + public RemotingServlet(Repository repository, + SessionProvider sessionProvider) { + this.repository = repository; + this.sessionProvider = sessionProvider; + } + + @Override + protected Repository getRepository() { + return repository; + } + + @Override + protected SessionProvider getSessionProvider() { + return sessionProvider; + } + + @Override + protected void service(final HttpServletRequest request, + final HttpServletResponse response) throws ServletException, + IOException { + try { + Subject subject = subjectFromRequest(request); + Subject.doAs(subject, new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + RemotingServlet.super.service(request, response); + return null; + } + }); + } catch (PrivilegedActionException e) { + throw new CmsException("Cannot process JCR remoting request", + e.getException()); + } + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Kernel.java index cb47a11b3..8486f8d7e 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 @@ -4,10 +4,10 @@ import java.lang.management.ManagementFactory; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; -import java.util.Properties; import javax.jcr.Repository; import javax.jcr.RepositoryFactory; +import javax.jcr.Session; import javax.security.auth.Subject; import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; @@ -26,7 +26,7 @@ import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; +import org.osgi.service.useradmin.UserAdmin; import org.osgi.util.tracker.ServiceTracker; /** @@ -48,14 +48,14 @@ final class Kernel implements ServiceListener { ThreadGroup threadGroup = new ThreadGroup(Kernel.class.getSimpleName()); JackrabbitNode node; - + private NodeUserAdmin userAdmin; private SimpleTransactionManager transactionManager; private OsgiJackrabbitRepositoryFactory repositoryFactory; - private NodeHttp nodeHttp; + private DataHttp nodeHttp; private KernelThread kernelThread; public Kernel() { - nodeSecurity = new NodeSecurity(bundleContext); + nodeSecurity = new NodeSecurity(); } final void init() { @@ -89,8 +89,11 @@ final class Kernel implements ServiceListener { repositoryFactory = new OsgiJackrabbitRepositoryFactory(); // Authentication - nodeSecurity.getUserAdmin().setTransactionManager( - transactionManager); + Session adminSession = node.login(); + userAdmin = new NodeUserAdmin(adminSession); + userAdmin.setTransactionManager(transactionManager); + bundleContext.registerService(UserAdmin.class, userAdmin, + userAdmin.currentState()); // Equinox dependency // ExtendedHttpService httpService = waitForHttpService(); @@ -114,7 +117,6 @@ final class Kernel implements ServiceListener { TransactionSynchronizationRegistry.class, transactionManager.getTransactionSynchronizationRegistry(), null); - nodeSecurity.publish(); node.publish(repositoryFactory); bundleContext.registerService(RepositoryFactory.class, repositoryFactory, null); @@ -143,8 +145,8 @@ final class Kernel implements ServiceListener { if (nodeHttp != null) nodeHttp.destroy(); - // if (nodeSecurity != null) - // nodeSecurity.destroy(); + if (userAdmin != null) + userAdmin.destroy(); if (node != null) node.destroy(); @@ -200,14 +202,14 @@ final class Kernel implements ServiceListener { } private void addHttpService(ServiceReference sr) { -// for (String key : sr.getPropertyKeys()) -// log.debug(key + "=" + sr.getProperty(key)); + // for (String key : sr.getPropertyKeys()) + // log.debug(key + "=" + sr.getProperty(key)); ExtendedHttpService httpService = (ExtendedHttpService) bundleContext .getService(sr); // TODO find constants Object httpPort = sr.getProperty("http.port"); Object httpsPort = sr.getProperty("https.port"); - nodeHttp = new NodeHttp(httpService, node); + nodeHttp = new DataHttp(httpService, node); if (log.isDebugEnabled()) log.debug("HTTP " + httpPort + (httpsPort != null ? " - HTTPS " + httpsPort : "")); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index 1d7e0868e..b2fb03d8e 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -15,7 +15,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.argeo.cms.CmsException; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.auth.AuthConstants; /** Package utilities */ class KernelUtils implements KernelConstants { @@ -74,7 +74,7 @@ class KernelUtils implements KernelConstants { Subject subject = new Subject(); LoginContext lc; try { - lc = new LoginContext(KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject); + lc = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, subject); lc.login(); return subject; } catch (LoginException e) { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java index 284606935..416f3bf75 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java @@ -6,6 +6,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import javax.security.auth.x500.X500Principal; + import org.osgi.service.useradmin.Authorization; class NodeAuthorization implements Authorization { @@ -16,7 +18,7 @@ class NodeAuthorization implements Authorization { public NodeAuthorization(String name, String displayName, Collection systemRoles, String[] roles) { - this.name = name; + this.name = new X500Principal(name).getName(); this.displayName = displayName; this.systemRoles = Collections.unmodifiableList(new ArrayList( systemRoles)); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java index de7561aa0..edcf719d7 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java @@ -1,6 +1,6 @@ package org.argeo.cms.internal.kernel; -import static org.argeo.cms.KernelHeader.ACCESS_CONTROL_CONTEXT; +import static org.argeo.cms.auth.AuthConstants.ACCESS_CONTROL_CONTEXT; import java.io.IOException; import java.security.AccessControlContext; @@ -31,7 +31,7 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.cms.CmsException; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.auth.AuthConstants; import org.argeo.jackrabbit.servlet.OpenInViewSessionProvider; import org.argeo.jackrabbit.servlet.RemotingServlet; import org.argeo.jackrabbit.servlet.WebdavServlet; @@ -43,6 +43,7 @@ import org.osgi.service.http.NamespaceException; * Intercepts and enriches http access, mainly focusing on security and * transactionality. */ +@Deprecated class NodeHttp implements KernelConstants, ArgeoJcrConstants { private final static Log log = LogFactory.getLog(NodeHttp.class); @@ -337,7 +338,7 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { ServletException { AccessControlContext acc = (AccessControlContext) httpSession - .getAttribute(KernelHeader.ACCESS_CONTROL_CONTEXT); + .getAttribute(AuthConstants.ACCESS_CONTROL_CONTEXT); final Subject subject; if (acc != null) { subject = Subject.getSubject(acc); @@ -348,7 +349,7 @@ class NodeHttp implements KernelConstants, ArgeoJcrConstants { CallbackHandler token = basicAuth(basicAuth); try { LoginContext lc = new LoginContext( - KernelHeader.LOGIN_CONTEXT_USER, token); + AuthConstants.LOGIN_CONTEXT_USER, token); lc.login(); subject = lc.getSubject(); } catch (LoginException e) { 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 910953e30..b436ac8d1 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 @@ -3,12 +3,10 @@ package org.argeo.cms.internal.kernel; import java.io.File; import java.io.IOException; import java.net.URL; -import java.nio.file.ProviderNotFoundException; import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.util.Arrays; -import java.util.Hashtable; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; @@ -23,15 +21,14 @@ 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.KernelHeader; +import org.argeo.cms.auth.AuthConstants; import org.argeo.security.crypto.PkiUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceRegistration; -import org.osgi.service.useradmin.UserAdmin; /** Authentication and user management. */ class NodeSecurity { + final static String SECURITY_PROVIDER = "BC";// Bouncy Castle + private final static Log log; static { log = LogFactory.getLog(NodeSecurity.class); @@ -42,27 +39,21 @@ class NodeSecurity { log.error("Provider " + provider.getName() + " already installed and could not be set as default"); Provider defaultProvider = Security.getProviders()[0]; - if (!defaultProvider.getName().equals(KernelHeader.SECURITY_PROVIDER)) + if (!defaultProvider.getName().equals(SECURITY_PROVIDER)) log.error("Provider name is " + defaultProvider.getName() - + " but it should be " + KernelHeader.SECURITY_PROVIDER); + + " but it should be " + SECURITY_PROVIDER); } - private final BundleContext bundleContext; - private final NodeUserAdmin userAdmin; private final Subject kernelSubject; - private ServiceRegistration userAdminReg; - - public NodeSecurity(BundleContext bundleContext) { + public NodeSecurity() { // Configure JAAS first URL url = getClass().getClassLoader().getResource( KernelConstants.JAAS_CONFIG); System.setProperty("java.security.auth.login.config", url.toExternalForm()); - this.bundleContext = bundleContext; this.kernelSubject = logKernel(); - userAdmin = new NodeUserAdmin(); } private Subject logKernel() { @@ -75,7 +66,7 @@ class NodeSecurity { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { // alias - ((NameCallback) callbacks[1]).setName(KernelHeader.ROLE_KERNEL); + ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL); // store pwd ((PasswordCallback) callbacks[2]).setPassword("changeit" .toCharArray()); @@ -95,15 +86,7 @@ class NodeSecurity { return kernelSubject; } - public void publish() { - userAdminReg = bundleContext.registerService(UserAdmin.class, - userAdmin, userAdmin.currentState()); - } - void destroy() { - userAdmin.destroy(); - userAdminReg.unregister(); - // Logout kernel try { LoginContext kernelLc = new LoginContext( @@ -113,11 +96,7 @@ class NodeSecurity { throw new CmsException("Cannot log in kernel", e); } - Security.removeProvider(KernelHeader.SECURITY_PROVIDER); - } - - public NodeUserAdmin getUserAdmin() { - return userAdmin; + Security.removeProvider(SECURITY_PROVIDER); } public Subject getKernelSubject() { @@ -134,7 +113,7 @@ class NodeSecurity { keyStoreFile.getParentFile().mkdirs(); KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd); PkiUtils.generateSelfSignedCertificate(keyStore, - new X500Principal(KernelHeader.ROLE_KERNEL), keyPwd); + new X500Principal(AuthConstants.ROLE_KERNEL), keyPwd); PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore); } catch (Exception e) { throw new CmsException("Cannot create key store " diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java index 31295ae89..75cd44491 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java @@ -13,6 +13,11 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.security.Privilege; import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.transaction.TransactionManager; @@ -20,8 +25,14 @@ import javax.transaction.TransactionManager; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; import org.argeo.cms.CmsException; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.UserJcrUtils; import org.argeo.osgi.useradmin.LdapUserAdmin; import org.argeo.osgi.useradmin.LdifUserAdmin; import org.argeo.osgi.useradmin.UserAdminConf; @@ -38,7 +49,7 @@ public class NodeUserAdmin implements UserAdmin { final static LdapName ROLES_BASE; static { try { - ROLES_BASE = new LdapName(KernelHeader.ROLES_BASEDN); + ROLES_BASE = new LdapName(AuthConstants.ROLES_BASEDN); } catch (InvalidNameException e) { throw new UserDirectoryException("Cannot initialize " + NodeUserAdmin.class, e); @@ -48,7 +59,13 @@ public class NodeUserAdmin implements UserAdmin { private UserAdmin nodeRoles = null; private Map userAdmins = new HashMap(); - public NodeUserAdmin() { + /** The home base path. */ + private String homeBasePath = "/home"; + private String peopleBasePath = ArgeoJcrConstants.PEOPLE_BASE_PATH; + private Session adminSession; + + public NodeUserAdmin(Session adminSession) { + this.adminSession = adminSession; File osgiInstanceDir = KernelUtils.getOsgiInstanceDir(); File nodeBaseDir = new File(osgiInstanceDir, "node"); nodeBaseDir.mkdirs(); @@ -109,10 +126,10 @@ public class NodeUserAdmin implements UserAdmin { + u.getScheme() + "] enabled."); } - // NOde roles + // Node roles String nodeRolesUri = KernelUtils .getFrameworkProp(KernelConstants.ROLES_URI); - String baseNodeRoleDn = KernelHeader.ROLES_BASEDN; + String baseNodeRoleDn = AuthConstants.ROLES_BASEDN; if (nodeRolesUri == null) { File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif"); if (!nodeRolesFile.exists()) @@ -143,6 +160,9 @@ public class NodeUserAdmin implements UserAdmin { addUserAdmin(baseNodeRoleDn, (UserAdmin) nodeRoles); if (log.isTraceEnabled()) log.trace("Node roles enabled."); + + // JCR + initJcr(adminSession); } Dictionary currentState() { @@ -214,7 +234,7 @@ public class NodeUserAdmin implements UserAdmin { @Override public Authorization getAuthorization(User user) { - if (user == null) { + if (user == null) {// anonymous return nodeRoles.getAuthorization(null); } UserAdmin userAdmin = findUserAdmin(user.getName()); @@ -226,16 +246,18 @@ public class NodeUserAdmin implements UserAdmin { .getRole(role)); systemRoles.addAll(Arrays.asList(auth.getRoles())); } - return new NodeAuthorization(rawAuthorization.getName(), - rawAuthorization.toString(), systemRoles, - rawAuthorization.getRoles()); + Authorization authorization = new NodeAuthorization( + rawAuthorization.getName(), rawAuthorization.toString(), + systemRoles, rawAuthorization.getRoles()); + syncJcr(adminSession, authorization); + return authorization; } // // USER ADMIN AGGREGATOR // public synchronized void addUserAdmin(String baseDn, UserAdmin userAdmin) { - if (baseDn.equals(KernelHeader.ROLES_BASEDN)) { + if (baseDn.equals(AuthConstants.ROLES_BASEDN)) { nodeRoles = userAdmin; return; } @@ -252,7 +274,7 @@ public class NodeUserAdmin implements UserAdmin { } public synchronized void removeUserAdmin(String baseDn) { - if (baseDn.equals(KernelHeader.ROLES_BASEDN)) + if (baseDn.equals(AuthConstants.ROLES_BASEDN)) throw new UserDirectoryException("Node roles cannot be removed."); LdapName base; try { @@ -302,4 +324,143 @@ public class NodeUserAdmin implements UserAdmin { .setTransactionManager(transactionManager); } } + + /* + * JCR + */ + private void initJcr(Session adminSession) { + try { + JcrUtils.mkdirs(adminSession, homeBasePath); + JcrUtils.mkdirs(adminSession, peopleBasePath); + adminSession.save(); + + JcrUtils.addPrivilege(adminSession, homeBasePath, + AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); + JcrUtils.addPrivilege(adminSession, peopleBasePath, + AuthConstants.ROLE_USER_ADMIN, Privilege.JCR_ALL); + adminSession.save(); + } catch (RepositoryException e) { + throw new CmsException("Cannot initialize node user admin", e); + } + } + + private Node syncJcr(Session session, Authorization authorization) { + // TODO check user name validity (e.g. should not start by ROLE_) + String username = authorization.getName(); + String[] roles = authorization.getRoles(); + try { + Node userHome = UserJcrUtils.getUserHome(session, username); + if (userHome == null) { + String homePath = generateUserPath(homeBasePath, username); + if (session.itemExists(homePath))// duplicate user id + userHome = session.getNode(homePath).getParent() + .addNode(JcrUtils.lastPathElement(homePath)); + else + userHome = JcrUtils.mkdirs(session, homePath); + // userHome = JcrUtils.mkfolders(session, homePath); + userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); + userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); + session.save(); + + JcrUtils.clearAccessControList(session, homePath, username); + JcrUtils.addPrivilege(session, homePath, username, + Privilege.JCR_ALL); + } + + Node userProfile = UserJcrUtils.getUserProfile(session, username); + // new user + if (userProfile == null) { + String personPath = generateUserPath(peopleBasePath, username); + Node personBase; + if (session.itemExists(personPath))// duplicate user id + personBase = session.getNode(personPath).getParent() + .addNode(JcrUtils.lastPathElement(personPath)); + else + personBase = JcrUtils.mkdirs(session, personPath); + userProfile = personBase.addNode(ArgeoNames.ARGEO_PROFILE); + userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); + userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); + userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, + true); + userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, + true); + userProfile.setProperty( + ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, true); + session.save(); + + JcrUtils.clearAccessControList(session, userProfile.getPath(), + username); + JcrUtils.addPrivilege(session, userProfile.getPath(), username, + Privilege.JCR_READ); + } + + // Remote roles + if (roles != null) { + writeRemoteRoles(userProfile, roles); + } + adminSession.save(); + return userProfile; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot sync node security model for " + + username, e); + } + } + + /** Generate path for a new user home */ + private String generateUserPath(String base, String username) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new ArgeoException("Invalid name " + username, e); + } + String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + int atIndex = userId.indexOf('@'); + if (atIndex > 0) { + String domain = userId.substring(0, atIndex); + String name = userId.substring(atIndex + 1); + return base + '/' + JcrUtils.firstCharsToPath(domain, 2) + '/' + + domain + '/' + JcrUtils.firstCharsToPath(name, 2) + '/' + + name; + } else if (atIndex == 0 || atIndex == (userId.length() - 1)) { + throw new ArgeoException("Unsupported username " + userId); + } else { + return base + '/' + JcrUtils.firstCharsToPath(userId, 2) + '/' + + userId; + } + } + + /** Write remote roles used by remote access in the home directory */ + private void writeRemoteRoles(Node userHome, String[] roles) + throws RepositoryException { + boolean writeRoles = false; + if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] remoteRoles = userHome.getProperty( + ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + if (remoteRoles.length != roles.length) + writeRoles = true; + else + for (int i = 0; i < remoteRoles.length; i++) + if (!remoteRoles[i].getString().equals(roles[i])) + writeRoles = true; + } else + writeRoles = true; + + if (writeRoles) { + userHome.getSession().getWorkspace().getVersionManager() + .checkout(userHome.getPath()); + userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roles); + JcrUtils.updateLastModified(userHome); + userHome.getSession().save(); + userHome.getSession().getWorkspace().getVersionManager() + .checkin(userHome.getPath()); + if (log.isDebugEnabled()) + log.debug("Wrote remote roles " + roles + " for " + + userHome.getProperty(ArgeoNames.ARGEO_USER_ID)); + } + + } + } 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 f97cf9113..4f647cf8a 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,19 +1,13 @@ USER { - org.argeo.cms.internal.auth.UserAdminLoginModule requisite; -}; - -OLD_USER { - org.argeo.cms.internal.auth.EndUserLoginModule requisite; - org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite; + org.argeo.cms.auth.NodeContextLoginModule requisite; + org.argeo.cms.auth.UserAdminLoginModule requisite; + org.argeo.cms.auth.NodeUserLoginModule requisite; }; ANONYMOUS { - org.argeo.cms.internal.auth.UserAdminLoginModule requisite anonymous=true; -}; - -OLD_ANONYMOUS { - org.argeo.cms.internal.auth.AnonymousLoginModule requisite; - org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite; + org.argeo.cms.auth.NodeContextLoginModule requisite; + org.argeo.cms.auth.UserAdminLoginModule requisite anonymous=true; + org.argeo.cms.auth.NodeUserLoginModule requisite; }; SYSTEM { @@ -26,11 +20,6 @@ KERNEL { org.argeo.cms.internal.auth.KernelLoginModule requisite; }; -OLD_SYSTEM { - org.argeo.cms.internal.auth.SystemLoginModule requisite; - org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite; -}; - KEYRING { org.argeo.security.crypto.KeyringLoginModule required; }; @@ -44,4 +33,3 @@ SINGLE_USER { Jackrabbit { org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite; }; - diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java index acbf1bebb..fc651e65b 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java +++ b/org.argeo.cms/src/org/argeo/cms/util/UserMenu.java @@ -1,8 +1,8 @@ package org.argeo.cms.util; -import static org.argeo.cms.KernelHeader.ACCESS_CONTROL_CONTEXT; -import static org.argeo.cms.KernelHeader.LOGIN_CONTEXT_ANONYMOUS; -import static org.argeo.cms.KernelHeader.LOGIN_CONTEXT_USER; +import static org.argeo.cms.auth.AuthConstants.ACCESS_CONTROL_CONTEXT; +import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_ANONYMOUS; +import static org.argeo.cms.auth.AuthConstants.LOGIN_CONTEXT_USER; import java.io.IOException; import java.security.AccessController; @@ -21,10 +21,10 @@ import javax.servlet.http.HttpSession; import org.argeo.cms.CmsException; import org.argeo.cms.CmsMsg; -import org.argeo.cms.CmsView; import org.argeo.cms.CmsStyles; -import org.argeo.cms.KernelHeader; -import org.argeo.cms.auth.ArgeoLoginContext; +import org.argeo.cms.CmsView; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.security.SecurityUtils; import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; @@ -50,13 +50,8 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - // Authentication authentication = SecurityContextHolder.getContext() - // .getAuthentication(); - // if (authentication == null) - // throw new CmsException("No authentication available"); - - String username = CurrentUserUtils.getUsername(); - if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS)) { + String username = SecurityUtils.getUsername(CmsUtils.getCmsView().getSubject()); + if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS)) { username = null; anonymousUi(); } else { @@ -86,15 +81,6 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { c.setLayout(new GridLayout()); c.setLayoutData(CmsUtils.fillAll()); - // String username = SecurityContextHolder.getContext() - // .getAuthentication().getName(); - // - // Label l = new Label(c, SWT.NONE); - // l.setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU_ITEM); - // l.setData(RWT.MARKUP_ENABLED, true); - // l.setLayoutData(CmsUtils.fillWidth()); - // l.setText("" + username + ""); - specificUserUi(c); Label l = new Label(c, SWT.NONE); @@ -113,12 +99,6 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { }); } - // protected String getUsername() { - // // String username = SecurityContextHolder.getContext() - // // .getAuthentication().getName(); - // return CurrentUserUtils.getUsername(); - // } - /** To be overridden */ protected void specificUserUi(Composite parent) { @@ -168,16 +148,15 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { } protected void login() { - CmsView cmsSession = (CmsView) getDisplay().getData( - CmsView.KEY); + CmsView cmsSession = (CmsView) getDisplay().getData(CmsView.KEY); Subject subject = cmsSession.getSubject(); try { // // LOGIN // - new ArgeoLoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).logout(); - LoginContext loginContext = new ArgeoLoginContext( - LOGIN_CONTEXT_USER, subject, this); + new LoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).logout(); + LoginContext loginContext = new LoginContext(LOGIN_CONTEXT_USER, + subject, this); loginContext.login(); // save context in session @@ -193,7 +172,7 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { }); } catch (LoginException e1) { try { - new ArgeoLoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).login(); + new LoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).login(); } catch (LoginException e) { throw new CmsException("Cannot authenticate anonymous", e1); } @@ -205,15 +184,14 @@ public class UserMenu extends Shell implements CmsStyles, CallbackHandler { } protected void logout() { - final CmsView cmsSession = (CmsView) getDisplay().getData( - CmsView.KEY); + final CmsView cmsSession = (CmsView) getDisplay().getData(CmsView.KEY); Subject subject = cmsSession.getSubject(); try { // // LOGOUT // - new ArgeoLoginContext(LOGIN_CONTEXT_USER, subject).logout(); - new ArgeoLoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).login(); + new LoginContext(LOGIN_CONTEXT_USER, subject).logout(); + new LoginContext(LOGIN_CONTEXT_ANONYMOUS, subject).login(); HttpServletRequest httpRequest = RWT.getRequest(); HttpSession httpSession = httpRequest.getSession(); diff --git a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java b/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java index 0dbb4ac5a..f4c5f0063 100644 --- a/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java +++ b/org.argeo.cms/src/org/argeo/cms/util/UserMenuLink.java @@ -4,7 +4,8 @@ import javax.jcr.Node; import org.argeo.cms.CmsMsg; import org.argeo.cms.CmsStyles; -import org.argeo.cms.KernelHeader; +import org.argeo.cms.auth.AuthConstants; +import org.argeo.security.SecurityUtils; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseEvent; @@ -22,13 +23,14 @@ public class UserMenuLink extends MenuLink { @Override public Control createUi(Composite parent, Node context) { - // String username = SecurityContextHolder.getContext() - // .getAuthentication().getName(); - String username = CurrentUserUtils.getUsername(); - if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS)) + String username = SecurityUtils.getUsername(CmsUtils.getCmsView() + .getSubject()); + if (username.equalsIgnoreCase(AuthConstants.ROLE_ANONYMOUS)) setLabel(CmsMsg.login.lead()); - else - setLabel(username); + else { + setLabel(SecurityUtils.getDisplayName(CmsUtils.getCmsView() + .getSubject())); + } Label link = (Label) ((Composite) super.createUi(parent, context)) .getChildren()[0]; link.addMouseListener(new UserMenuLinkController()); diff --git a/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/commands/AddRemoteRepository.java b/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/commands/AddRemoteRepository.java index 6ed419f04..2ee8f8963 100644 --- a/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/commands/AddRemoteRepository.java +++ b/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/commands/AddRemoteRepository.java @@ -153,7 +153,7 @@ public class AddRemoteRepository extends AbstractHandler implements char[] pwd = password.getText().toCharArray(); SimpleCredentials sc = new SimpleCredentials( username.getText(), pwd); - session = repository.login(sc); + session = repository.login(sc, "main"); MessageDialog.openInformation(getParentShell(), "Success", "Connection to '" + uri.getText() + "' successful"); } diff --git a/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/jcr/internal/model/RepositoryElem.java b/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/jcr/internal/model/RepositoryElem.java index 6a42bd8bd..422e5cfbe 100644 --- a/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/jcr/internal/model/RepositoryElem.java +++ b/org.argeo.eclipse.ui.workbench/src/org/argeo/eclipse/ui/workbench/jcr/internal/model/RepositoryElem.java @@ -43,7 +43,7 @@ public class RepositoryElem extends TreeParent { public void login() { try { - defaultSession = repositoryLogin(null); + defaultSession = repositoryLogin("main"); String[] wkpNames = defaultSession.getWorkspace() .getAccessibleWorkspaceNames(); for (String wkpName : wkpNames) { diff --git a/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallback.java b/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallback.java deleted file mode 100644 index 193da34a7..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallback.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.argeo.osgi.auth; - -import javax.security.auth.callback.Callback; -import javax.security.auth.spi.LoginModule; - -import org.osgi.framework.BundleContext; - -/** Allows a {@link LoginModule} to as for a {@link BundleContext} */ -public class BundleContextCallback implements Callback { - private BundleContext bundleContext; - - public BundleContext getBundleContext() { - return bundleContext; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - -} diff --git a/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHandler.java b/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHandler.java deleted file mode 100644 index 37733e0eb..000000000 --- a/org.argeo.security.core/src/org/argeo/osgi/auth/BundleContextCallbackHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.argeo.osgi.auth; - -import java.io.IOException; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.osgi.framework.BundleContext; - -public class BundleContextCallbackHandler implements CallbackHandler { - private final BundleContext bundleContext; - - public BundleContextCallbackHandler(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - @Override - public void handle(Callback[] callbacks) throws IOException, - UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (!(callback instanceof BundleContextCallback)) - throw new UnsupportedCallbackException(callback); - ((BundleContextCallback) callback).setBundleContext(bundleContext); - } - - } - -} diff --git a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java index b3b0f37f3..e1f7899a5 100644 --- a/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java +++ b/org.argeo.security.core/src/org/argeo/security/SecurityUtils.java @@ -26,6 +26,7 @@ import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; import org.argeo.ArgeoException; +import org.osgi.service.useradmin.Authorization; /** Static utilities */ public final class SecurityUtils { @@ -42,13 +43,16 @@ public final class SecurityUtils { * anonymous */ public static String getCurrentThreadUsername() { - return getUsername(); - } - - public final static String getUsername() { Subject subject = Subject.getSubject(AccessController.getContext()); if (subject == null) return null; + return getUsername(subject); + } + + public final static String getUsername(Subject subject) { + // Subject subject = Subject.getSubject(AccessController.getContext()); + // if (subject == null) + // return null; if (subject.getPrincipals(X500Principal.class).size() != 1) return null; Principal principal = subject.getPrincipals(X500Principal.class) @@ -57,6 +61,15 @@ public final class SecurityUtils { } + public final static String getDisplayName(Subject subject) { + return getAuthorization(subject).toString(); + } + + public final static Authorization getAuthorization(Subject subject) { + return subject.getPrivateCredentials(Authorization.class).iterator() + .next(); + } + public final static Set roles() { Set roles = Collections.synchronizedSet(new HashSet()); Subject subject = Subject.getSubject(AccessController.getContext()); diff --git a/org.argeo.security.ui.rap/bnd.bnd b/org.argeo.security.ui.rap/bnd.bnd index c4b0deee7..dffe8ff61 100644 --- a/org.argeo.security.ui.rap/bnd.bnd +++ b/org.argeo.security.ui.rap/bnd.bnd @@ -6,5 +6,6 @@ Require-Bundle: org.eclipse.rap.ui,org.eclipse.core.runtime Import-Package: org.argeo.eclipse.spring,\ org.argeo.eclipse.ui.specific,\ org.argeo.cms,\ +org.argeo.cms.auth,\ org.argeo.security.ui,\ * diff --git a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/AnonymousEntryPoint.java b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/AnonymousEntryPoint.java index 99536faa1..7c1702d8d 100644 --- a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/AnonymousEntryPoint.java +++ b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/AnonymousEntryPoint.java @@ -24,8 +24,7 @@ import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; -import org.argeo.cms.KernelHeader; -import org.argeo.cms.auth.ArgeoLoginContext; +import org.argeo.cms.auth.AuthConstants; import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.application.EntryPoint; import org.eclipse.swt.widgets.Display; @@ -57,8 +56,8 @@ public class AnonymousEntryPoint implements EntryPoint { final LoginContext loginContext; try { - loginContext = new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject); + loginContext = new LoginContext(AuthConstants.LOGIN_CONTEXT_ANONYMOUS, + subject); loginContext.login(); } catch (LoginException e1) { throw new ArgeoException("Cannot initialize login context", e1); diff --git a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java index c27fbc70e..6a582f83f 100644 --- a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java +++ b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java @@ -15,7 +15,7 @@ */ package org.argeo.security.ui.rap; -import static org.argeo.cms.KernelHeader.ACCESS_CONTROL_CONTEXT; +import static org.argeo.cms.auth.AuthConstants.ACCESS_CONTROL_CONTEXT; import java.security.AccessControlContext; import java.security.AccessController; @@ -33,8 +33,7 @@ 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.KernelHeader; -import org.argeo.cms.auth.ArgeoLoginContext; +import org.argeo.cms.auth.AuthConstants; import org.argeo.cms.widgets.auth.DefaultLoginDialog; import org.argeo.eclipse.ui.dialogs.ErrorFeedback; import org.argeo.util.LocaleUtils; @@ -78,7 +77,7 @@ public class SecureEntryPoint implements EntryPoint { HttpServletRequest httpRequest = RWT.getRequest(); final HttpSession httpSession = httpRequest.getSession(); AccessControlContext acc = (AccessControlContext) httpSession - .getAttribute(KernelHeader.ACCESS_CONTROL_CONTEXT); + .getAttribute(AuthConstants.ACCESS_CONTROL_CONTEXT); final Subject subject; if (acc != null @@ -92,8 +91,8 @@ public class SecureEntryPoint implements EntryPoint { try { CallbackHandler callbackHandler = new DefaultLoginDialog( display.getActiveShell()); - loginContext = new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_USER, subject, + loginContext = new LoginContext( + AuthConstants.LOGIN_CONTEXT_USER, subject, callbackHandler); } catch (LoginException e1) { throw new ArgeoException("Cannot initialize login context", e1); @@ -134,8 +133,8 @@ public class SecureEntryPoint implements EntryPoint { if (log.isTraceEnabled()) log.trace("Display disposed"); try { - LoginContext loginContext = new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_USER, subject); + LoginContext loginContext = new LoginContext( + AuthConstants.LOGIN_CONTEXT_USER, subject); loginContext.logout(); } catch (LoginException e) { log.error("Error when logging out", e); @@ -205,8 +204,8 @@ public class SecureEntryPoint implements EntryPoint { private void fullLogout(Subject subject, String username) { try { - LoginContext loginContext = new ArgeoLoginContext( - KernelHeader.LOGIN_CONTEXT_USER, subject); + LoginContext loginContext = new LoginContext( + AuthConstants.LOGIN_CONTEXT_USER, subject); loginContext.logout(); HttpServletRequest httpRequest = RWT.getRequest(); HttpSession httpSession = httpRequest.getSession(); diff --git a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureRapActivator.java b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureRapActivator.java index 1364eeb91..b3d7c2337 100644 --- a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureRapActivator.java +++ b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureRapActivator.java @@ -22,24 +22,17 @@ import org.osgi.framework.BundleContext; public class SecureRapActivator implements BundleActivator { public final static String ID = "org.argeo.security.ui.rap"; - private BundleContext bundleContext; - private static SecureRapActivator activator = null; + private static BundleContext bundleContext; - public void start(BundleContext bundleContext) throws Exception { - activator = this; - this.bundleContext = bundleContext; + public void start(BundleContext bc) throws Exception { + bundleContext = bc; } public void stop(BundleContext context) throws Exception { bundleContext = null; - activator = null; } - public BundleContext getBundleContext() { + public static BundleContext getBundleContext() { return bundleContext; } - - public static SecureRapActivator getActivator() { - return activator; - } } diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java b/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java index 73932a7a4..7086de0af 100644 --- a/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java +++ b/org.argeo.security.ui/src/org/argeo/security/ui/internal/CurrentUser.java @@ -25,7 +25,7 @@ import org.argeo.security.SecurityUtils; */ public class CurrentUser { public final static String getUsername() { - return SecurityUtils.getUsername(); + return SecurityUtils.getCurrentThreadUsername(); } public final static Set roles() { -- 2.30.2