From 1f4ff4da0e5d85821b005267dfa9eece9f8ca9bb Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 8 Sep 2019 08:06:04 +0200 Subject: [PATCH] Make data admin log-in more robust and easier to use. --- .../org/argeo/cms/auth/IdentLoginModule.java | 4 +- .../cms/internal/kernel/HomeRepository.java | 33 +++++--- .../internal/kernel/NodeAuthorization.java | 1 + .../argeo/cms/internal/kernel/NodeHttp.java | 1 - .../argeo/cms/internal/kernel/jaas-ipa.cfg | 4 +- .../org/argeo/cms/internal/kernel/jaas.cfg | 4 +- .../org/argeo/jcr/JcrRepositoryWrapper.java | 8 +- .../org/argeo/node}/DataAdminLoginModule.java | 10 ++- .../src/org/argeo/node/NodeUtils.java | 83 ++++++++++++++----- 9 files changed, 98 insertions(+), 50 deletions(-) rename {org.argeo.cms/src/org/argeo/cms/auth => org.argeo.node.api/src/org/argeo/node}/DataAdminLoginModule.java (80%) diff --git a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java index b831097cc..ff741d8a0 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java @@ -16,17 +16,17 @@ import org.apache.commons.logging.LogFactory; import org.argeo.cms.internal.kernel.Activator; import org.argeo.ident.IdentClient; +/** Use an ident service to identify. */ public class IdentLoginModule implements LoginModule { private final static Log log = LogFactory.getLog(IdentLoginModule.class); - private Subject subject = null; private CallbackHandler callbackHandler = null; private Map sharedState = null; + @SuppressWarnings("unchecked") @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = (Map) sharedState; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java index fd432d11e..e5164b6c3 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java @@ -71,7 +71,7 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants { } @Override - protected void processNewSession(Session session) { + protected void processNewSession(Session session, String workspaceName) { String username = session.getUserID(); if (username == null || username.toString().equals("")) return; @@ -82,7 +82,7 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants { return; Session adminSession = KernelUtils.openAdminSession(getRepository(), session.getWorkspace().getName()); try { - syncJcr(adminSession, username); + syncJcr(adminSession, username, workspaceName); checkedUsers.add(username); } finally { JcrUtils.logoutQuietly(adminSession); @@ -109,28 +109,35 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants { } } - private void syncJcr(Session session, String username) { + protected synchronized void syncJcr(Session adminSession, String username, String workspaceName) { + // only in the default workspace + if (workspaceName != null) + return; + // skip system users + if (username.endsWith(NodeConstants.ROLES_BASEDN)) + return; + try { - Node userHome = NodeUtils.getUserHome(session, username); + Node userHome = NodeUtils.getUserHome(adminSession, username); if (userHome == null) { String homePath = generateUserPath(username); - if (session.itemExists(homePath))// duplicate user id - userHome = session.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); + if (adminSession.itemExists(homePath))// duplicate user id + userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); else - userHome = JcrUtils.mkdirs(session, homePath); + userHome = JcrUtils.mkdirs(adminSession, homePath); // userHome = JcrUtils.mkfolders(session, homePath); userHome.addMixin(NodeTypes.NODE_USER_HOME); userHome.addMixin(NodeType.MIX_CREATED); userHome.setProperty(NodeNames.LDAP_UID, username); - session.save(); + adminSession.save(); - JcrUtils.clearAccessControList(session, homePath, username); - JcrUtils.addPrivilege(session, homePath, username, Privilege.JCR_ALL); + JcrUtils.clearAccessControList(adminSession, homePath, username); + JcrUtils.addPrivilege(adminSession, homePath, username, Privilege.JCR_ALL); } - if (session.hasPendingChanges()) - session.save(); + if (adminSession.hasPendingChanges()) + adminSession.save(); } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); + JcrUtils.discardQuietly(adminSession); throw new CmsException("Cannot sync node security model for " + username, 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 416f3bf75..ddb9730e6 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 @@ -10,6 +10,7 @@ import javax.security.auth.x500.X500Principal; import org.osgi.service.useradmin.Authorization; +@Deprecated class NodeAuthorization implements Authorization { private final String name; private final String displayName; 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 4b700d4bc..e92198495 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 @@ -18,7 +18,6 @@ import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; import org.argeo.cms.CmsException; import org.argeo.cms.internal.http.CmsSessionProvider; import org.argeo.cms.internal.http.DataHttpContext; -import org.argeo.cms.internal.http.HtmlServlet; import org.argeo.cms.internal.http.HttpUtils; import org.argeo.cms.internal.http.LinkServlet; import org.argeo.cms.internal.http.PrivateHttpContext; diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg index 018c1bf9c..1d43afd9a 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg @@ -11,7 +11,7 @@ ANONYMOUS { }; DATA_ADMIN { - org.argeo.cms.auth.DataAdminLoginModule requisite; + org.argeo.node.DataAdminLoginModule requisite; }; NODE { @@ -19,7 +19,7 @@ NODE { keyTab="${osgi.instance.area}node/krb5.keytab" useKeyTab=true storeKey=true; - org.argeo.cms.auth.DataAdminLoginModule requisite; + org.argeo.node.DataAdminLoginModule requisite; }; KEYRING { 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 9b3f0114b..e54277a3c 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 @@ -10,11 +10,11 @@ ANONYMOUS { }; DATA_ADMIN { - org.argeo.cms.auth.DataAdminLoginModule requisite; + org.argeo.node.DataAdminLoginModule requisite; }; NODE { - org.argeo.cms.auth.DataAdminLoginModule requisite; + org.argeo.node.DataAdminLoginModule requisite; }; KEYRING { diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java index 1e08c18ac..d4bf4381e 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java @@ -106,7 +106,7 @@ public abstract class JcrRepositoryWrapper implements Repository { else throw e; } - processNewSession(session); + processNewSession(session, workspaceName); return session; } @@ -123,7 +123,7 @@ public abstract class JcrRepositoryWrapper implements Repository { } /** Called after a session has been created, does nothing by default. */ - protected void processNewSession(Session session) { + protected void processNewSession(Session session, String workspaceName) { } /** Wraps access to the repository, making sure it is available. */ @@ -138,8 +138,8 @@ public abstract class JcrRepositoryWrapper implements Repository { } /** - * Logs in to the default workspace, creates the required workspace, logs - * out, logs in to the required workspace. + * Logs in to the default workspace, creates the required workspace, logs out, + * logs in to the required workspace. */ protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName) throws RepositoryException { diff --git a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java b/org.argeo.node.api/src/org/argeo/node/DataAdminLoginModule.java similarity index 80% rename from org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java rename to org.argeo.node.api/src/org/argeo/node/DataAdminLoginModule.java index 50a878834..307474821 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java +++ b/org.argeo.node.api/src/org/argeo/node/DataAdminLoginModule.java @@ -1,7 +1,8 @@ -package org.argeo.cms.auth; +package org.argeo.node; import java.util.Map; +import javax.security.auth.AuthPermission; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginException; @@ -9,7 +10,11 @@ import javax.security.auth.spi.LoginModule; import org.argeo.node.security.DataAdminPrincipal; -/** Logs a system process as data admin */ +/** + * Log-in a system process as data admin. Protection is via + * {@link AuthPermission} on this login module, so if it can be accessed it will + * always succeed. + */ public class DataAdminLoginModule implements LoginModule { private Subject subject; @@ -21,7 +26,6 @@ public class DataAdminLoginModule implements LoginModule { @Override public boolean login() throws LoginException { - // TODO check permission? return true; } diff --git a/org.argeo.node.api/src/org/argeo/node/NodeUtils.java b/org.argeo.node.api/src/org/argeo/node/NodeUtils.java index afb64bd7a..9b9e854b3 100644 --- a/org.argeo.node.api/src/org/argeo/node/NodeUtils.java +++ b/org.argeo.node.api/src/org/argeo/node/NodeUtils.java @@ -15,6 +15,7 @@ */ package org.argeo.node; +import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; @@ -31,13 +32,17 @@ import javax.jcr.query.qom.DynamicOperand; import javax.jcr.query.qom.QueryObjectModelFactory; import javax.jcr.query.qom.Selector; import javax.jcr.query.qom.StaticOperand; +import javax.security.auth.AuthPermission; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; /** Utilities related to Argeo model in JCR */ public class NodeUtils { /** * Wraps the call to the repository factory based on parameter - * {@link NodeConstants#CN} in order to simplify it and - * protect against future API changes. + * {@link NodeConstants#CN} in order to simplify it and protect against future + * API changes. */ public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) { try { @@ -52,8 +57,8 @@ public class NodeUtils { /** * Wraps the call to the repository factory based on parameter - * {@link NodeConstants#LABELED_URI} in order to simplify it and - * protect against future API changes. + * {@link NodeConstants#LABELED_URI} in order to simplify it and protect against + * future API changes. */ public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) { return getRepositoryByUri(repositoryFactory, uri, null); @@ -61,8 +66,8 @@ public class NodeUtils { /** * Wraps the call to the repository factory based on parameter - * {@link NodeConstants#LABELED_URI} in order to simplify it and - * protect against future API changes. + * {@link NodeConstants#LABELED_URI} in order to simplify it and protect against + * future API changes. */ public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) { try { @@ -76,18 +81,13 @@ public class NodeUtils { } } - private NodeUtils() { - } - /** * Returns the home node of the user or null if none was found. * - * @param session - * the session to use in order to perform the search, this can be - * a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param username - * the username of the user + * @param session the session to use in order to perform the search, this can + * be a session with a different user ID than the one searched, + * typically when a system or admin session is used. + * @param username the username of the user */ public static Node getUserHome(Session session, String username) { try { @@ -106,12 +106,10 @@ public class NodeUtils { /** * Returns the home node of the user or null if none was found. * - * @param session - * the session to use in order to perform the search, this can be - * a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param cn - * the name of the group + * @param session the session to use in order to perform the search, this can be + * a session with a different user ID than the one searched, + * typically when a system or admin session is used. + * @param cn the name of the group */ public static Node getGroupHome(Session session, String cn) { try { @@ -131,8 +129,7 @@ public class NodeUtils { * Queries one single node. * * @return one single node or null if none was found - * @throws ArgeoJcrException - * if more than one node was found + * @throws ArgeoJcrException if more than one node was found */ private static Node querySingleNode(Query query) { NodeIterator nodeIterator; @@ -159,10 +156,50 @@ public class NodeUtils { return getUserHome(session, userID); } + /** + * Translate the path to this node into a path containing the name of the + * repository and the name of the workspace. + */ public static String getDataPath(String cn, Node node) throws RepositoryException { assert node != null; StringBuilder buf = new StringBuilder(NodeConstants.PATH_DATA); return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName()) .append(node.getPath()).toString(); } + + /** + * Open a JCR session with full read/write rights on the data, as + * {@link NodeConstants#ROLE_USER_ADMIN}, using the + * {@link NodeConstants#LOGIN_CONTEXT_DATA_ADMIN} login context. For security + * hardened deployement, use {@link AuthPermission} on this login context. + */ + public static Session openDataAdminSession(Repository repository, String workspaceName) { + ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + LoginContext loginContext; + try { + loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN); + loginContext.login(); + } catch (LoginException e1) { + throw new RuntimeException("Could not login as data admin", e1); + } finally { + Thread.currentThread().setContextClassLoader(currentCl); + } + return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + + @Override + public Session run() { + try { + return repository.login(workspaceName); + } catch (RepositoryException e) { + throw new RuntimeException("Cannot open data admin session", e); + } + } + + }); + } + + /** Singleton. */ + private NodeUtils() { + } + } -- 2.30.2