Remove node data model, home areas based on workspaces instead.
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 22 Feb 2020 11:56:52 +0000 (12:56 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 22 Feb 2020 11:56:52 +0000 (12:56 +0100)
19 files changed:
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java
org.argeo.cms/ext/test/org/argeo/cms/tabular/JcrTabularTest.java
org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java
org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/HomeRepository.java
org.argeo.ext.jackrabbit/pom.xml
org.argeo.ext.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java
org.argeo.ext.jackrabbit/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
org.argeo.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java
org.argeo.node.api/bnd.bnd
org.argeo.node.api/src/org/argeo/node/NodeConstants.java
org.argeo.node.api/src/org/argeo/node/NodeNames.java
org.argeo.node.api/src/org/argeo/node/NodeTypes.java
org.argeo.node.api/src/org/argeo/node/NodeUtils.java
org.argeo.node.api/src/org/argeo/node/ldap.cnd [new file with mode: 0644]
org.argeo.node.api/src/org/argeo/node/node.cnd

index 765f3201a9612bf067966e76212a83232b29dca4..0cd4516007bf79d7bb369d1eb61e8488f6576b0f 100644 (file)
@@ -17,6 +17,7 @@ package org.argeo.cms.ui.jcr;
 
 import javax.jcr.NamespaceException;
 import javax.jcr.Node;
+import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NodeType;
 
@@ -28,8 +29,6 @@ import org.argeo.cms.ui.jcr.model.RepositoryElem;
 import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
 import org.argeo.cms.ui.jcr.model.WorkspaceElem;
 import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.node.NodeTypes;
 import org.eclipse.jface.viewers.ColumnLabelProvider;
 import org.eclipse.swt.graphics.Image;
 
@@ -110,9 +109,13 @@ public class NodeLabelProvider extends ColumnLabelProvider {
                        else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE))
                                return JcrImages.BINARY;
                        try {
-                               // optimizes
-                               if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME))
+                               // TODO check workspace type?
+                               if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID))
                                        return JcrImages.HOME;
+
+                               // optimizes
+//                             if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME))
+//                                     return JcrImages.HOME;
                        } catch (NamespaceException e) {
                                // node namespace is not registered in this repo
                        }
index 9201a14fc8328c5024baa04810478e9ee484f277..44ef29d6a6dcf2c81e2e498fe6a5d9879e9fc5d8 100644 (file)
@@ -37,7 +37,7 @@ public class JcrTabularTest extends AbstractJackrabbitTestCase {
 
        public void testWriteReadCsv() throws Exception {
                // session().setNamespacePrefix("argeo", ArgeoNames.ARGEO_NAMESPACE);
-               InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/node/node.cnd"));
+               InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/node/ldap.cnd"));
                CndImporter.registerNodeTypes(reader, session());
                reader.close();
                reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/cms/argeo.cnd"));
index 0785430205ca6a683654548c556582b9824a5cee..b9798006b4ca040fa3ecb3d543cf27f639dcc967 100644 (file)
@@ -1,12 +1,17 @@
 package org.argeo.cms.auth;
 
 import java.time.ZonedDateTime;
+import java.util.Collection;
 import java.util.Locale;
 import java.util.UUID;
 
 import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
 
 import org.argeo.naming.LdapAttrs;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.useradmin.Authorization;
 
 /** An authenticated user session. */
@@ -15,8 +20,6 @@ public interface CmsSession {
        final static String SESSION_UUID = LdapAttrs.entryUUID.name();
        final static String SESSION_LOCAL_ID = LdapAttrs.uniqueIdentifier.name();
 
-       // public String getId();
-
        UUID getUuid();
 
        LdapName getUserDn();
@@ -35,12 +38,25 @@ public interface CmsSession {
 
        boolean isValid();
 
-       // public Session getDataSession(String cn, String workspace, Repository
-       // repository);
-       //
-       // public void releaseDataSession(String cn, Session session);
-
-       // public void addHttpSession(HttpServletRequest request);
-
-       // public void cleanUp();
+       /** @return The {@link CmsSession} for this {@link Subject} or null. */
+       static CmsSession getCmsSession(BundleContext bc, Subject subject) {
+               if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
+                       return null;
+               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
+               String uuid = cmsSessionId.getUuid().toString();
+               Collection<ServiceReference<CmsSession>> sr;
+               try {
+                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
+               }
+               ServiceReference<CmsSession> cmsSessionRef;
+               if (sr.size() == 1) {
+                       cmsSessionRef = sr.iterator().next();
+                       return bc.getService(cmsSessionRef);
+               } else if (sr.size() == 0) {
+                       return null;
+               } else
+                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
+       }
 }
index 875328974b4f1199226adfeb816d3d953b3e8733..b71eb4fc786a589283ac271dde899e80c05dfabd 100644 (file)
@@ -2,8 +2,14 @@ package org.argeo.cms.auth;
 
 import java.util.UUID;
 
+import javax.security.auth.Subject;
+
 import org.argeo.cms.CmsException;
 
+/**
+ * The ID of a {@link CmsSession}, which must be available in the private
+ * credentials of an authenticated {@link Subject}.
+ */
 public class CmsSessionId {
        private final UUID uuid;
 
index 010000f61b5f9b1c5bc40122b2f32709762092ec..ce38cf0ee94514ed445661707bb9d27e180e6563 100644 (file)
@@ -128,6 +128,10 @@ public class CmsSessionImpl implements CmsSession {
                return getSubject().getPrivateCredentials(SecretKey.class);
        }
 
+       public Session newDataSession(String cn, String workspace, Repository repository) {
+               return login(repository, workspace);
+       }
+
        public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
                // FIXME make it more robust
                if (workspace == null)
index a1ddcb0babb26315ac569bf0297e03f7094e6a43..14f311f3796903721987b8c197f8cd4d3f0ca690 100644 (file)
@@ -1,7 +1,6 @@
 package org.argeo.cms.internal.http;
 
 import java.io.Serializable;
-import java.util.LinkedHashMap;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
@@ -26,7 +25,7 @@ public class CmsSessionProvider implements SessionProvider, Serializable {
 
        private final String alias;
 
-       private LinkedHashMap<Session, CmsSessionImpl> cmsSessions = new LinkedHashMap<>();
+//     private LinkedHashMap<Session, CmsSessionImpl> cmsSessions = new LinkedHashMap<>();
 
        public CmsSessionProvider(String alias) {
                this.alias = alias;
@@ -35,47 +34,27 @@ public class CmsSessionProvider implements SessionProvider, Serializable {
        public Session getSession(HttpServletRequest request, Repository rep, String workspace)
                        throws javax.jcr.LoginException, ServletException, RepositoryException {
 
+               // a client is scanning parent URLs.
+               if (workspace == null)
+                       return null;
+
                CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
-               // if (cmsSession == null)
-               // return anonymousSession(request, rep, workspace);
                if (log.isTraceEnabled()) {
                        log.trace("Get JCR session from " + cmsSession);
                }
-               Session session = cmsSession.getDataSession(alias, workspace, rep);
-               cmsSessions.put(session, cmsSession);
+               Session session = cmsSession.newDataSession(alias, workspace, rep);
+//             cmsSessions.put(session, cmsSession);
                return session;
        }
 
-       // private synchronized Session anonymousSession(HttpServletRequest request,
-       // Repository repository, String workspace) {
-       // // TODO rather log in here as anonymous?
-       // LoginContext lc = (LoginContext)
-       // request.getAttribute(NodeConstants.LOGIN_CONTEXT_ANONYMOUS);
-       // if (lc == null)
-       // throw new CmsException("No login context available");
-       // // optimize
-       // Session session;
-       // try {
-       // session = Subject.doAs(lc.getSubject(), new
-       // PrivilegedExceptionAction<Session>() {
-       // @Override
-       // public Session run() throws Exception {
-       // return repository.login(workspace);
-       // }
-       // });
-       // } catch (Exception e) {
-       // throw new CmsException("Cannot log in to JCR", e);
-       // }
-       // return session;
-       // }
-
-       public synchronized void releaseSession(Session session) {
-               if (cmsSessions.containsKey(session)) {
-                       CmsSessionImpl cmsSession = cmsSessions.get(session);
-                       cmsSession.releaseDataSession(alias, session);
-               } else {
-                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
-                       JcrUtils.logoutQuietly(session);
-               }
+       public void releaseSession(Session session) {
+               JcrUtils.logoutQuietly(session);
+//             if (cmsSessions.containsKey(session)) {
+//                     CmsSessionImpl cmsSession = cmsSessions.get(session);
+//                     cmsSession.releaseDataSession(alias, session);
+//             } else {
+//                     log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
+//                     JcrUtils.logoutQuietly(session);
+//             }
        }
 }
index 77d901d07e06bda7ce6c1112d012fc5c307d40d5..cc7005be3850932a0a001fee778d5a7878e00cf0 100644 (file)
@@ -9,6 +9,7 @@ import javax.jcr.Credentials;
 import javax.jcr.LoginException;
 import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Node;
+import javax.jcr.Property;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -20,11 +21,10 @@ import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 
 import org.argeo.cms.CmsException;
+import org.argeo.jackrabbit.security.JackrabbitSecurityUtils;
 import org.argeo.jcr.JcrRepositoryWrapper;
 import org.argeo.jcr.JcrUtils;
 import org.argeo.node.NodeConstants;
-import org.argeo.node.NodeNames;
-import org.argeo.node.NodeTypes;
 import org.argeo.node.NodeUtils;
 
 /**
@@ -33,15 +33,17 @@ import org.argeo.node.NodeUtils;
 class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
 
        /** The home base path. */
-       private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH;
-       private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH;
-       private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH;
+//     private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH;
+//     private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH;
+//     private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH;
 
        private Set<String> checkedUsers = new HashSet<String>();
 
        private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM");
 
        private String defaultHomeWorkspace = NodeConstants.HOME;
+       private String defaultGroupsWorkspace = NodeConstants.GROUPS;
+       private String defaultGuestsWorkspace = NodeConstants.GUESTS;
        private final boolean remote;
 
        public HomeRepository(Repository repository, boolean remote) {
@@ -60,16 +62,8 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
 
                                @Override
                                public Void run() {
-                                       Session adminSession = null;
-                                       try {
-                                               adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(defaultHomeWorkspace),
-                                                               defaultHomeWorkspace);
-                                               initJcr(adminSession);
-                                       } catch (RepositoryException e) {
-                                               throw new CmsException("Cannot init JCR home", e);
-                                       } finally {
-                                               JcrUtils.logoutQuietly(adminSession);
-                                       }
+                                       loginOrCreateWorkspace(defaultHomeWorkspace);
+                                       loginOrCreateWorkspace(defaultGroupsWorkspace);
                                        return null;
                                }
 
@@ -77,6 +71,20 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
                }
        }
 
+       private void loginOrCreateWorkspace(String workspace) {
+               Session adminSession = null;
+               try {
+                       adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace);
+//                     JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ);
+
+//                     initJcr(adminSession);
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot init JCR home", e);
+               } finally {
+                       JcrUtils.logoutQuietly(adminSession);
+               }
+       }
+
        @Override
        public Session login(Credentials credentials, String workspaceName)
                        throws LoginException, NoSuchWorkspaceException, RepositoryException {
@@ -92,6 +100,16 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
                return defaultHomeWorkspace;
        }
 
+       protected String getGroupsWorkspace() {
+               // TODO base on JAAS Subject metadata
+               return defaultGroupsWorkspace;
+       }
+
+       protected String getGuestsWorkspace() {
+               // TODO base on JAAS Subject metadata
+               return defaultGuestsWorkspace;
+       }
+
        @Override
        protected void processNewSession(Session session, String workspaceName) {
                String username = session.getUserID();
@@ -122,7 +140,7 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
        private void initJcr(Session adminSession) {
                try {
 //                     JcrUtils.mkdirs(adminSession, homeBasePath);
-                       JcrUtils.mkdirs(adminSession, groupsBasePath);
+//                     JcrUtils.mkdirs(adminSession, groupsBasePath);
                        adminSession.save();
 
 //                     JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
@@ -146,19 +164,23 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
                try {
                        Node userHome = NodeUtils.getUserHome(adminSession, username);
                        if (userHome == null) {
-                               String homePath = generateUserPath(username);
-                               if (adminSession.itemExists(homePath))// duplicate user id
-                                       userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
-                               else
-                                       userHome = JcrUtils.mkdirs(adminSession, homePath);
-                               // userHome = JcrUtils.mkfolders(session, homePath);
-                               userHome.addMixin(NodeTypes.NODE_USER_HOME);
+//                             String homePath = generateUserPath(username);
+                               String userId = extractUserId(username);
+//                             if (adminSession.itemExists(homePath))// duplicate user id
+//                                     userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
+//                             else
+//                                     userHome = JcrUtils.mkdirs(adminSession, homePath);
+                               userHome = adminSession.getRootNode().addNode(userId);
+//                             userHome.addMixin(NodeTypes.NODE_USER_HOME);
                                userHome.addMixin(NodeType.MIX_CREATED);
-                               userHome.setProperty(NodeNames.LDAP_UID, username);
+                               userHome.setProperty(Property.JCR_ID, username);
+//                             userHome.setProperty(NodeNames.LDAP_UID, username);
                                adminSession.save();
 
-                               JcrUtils.clearAccessControList(adminSession, homePath, username);
-                               JcrUtils.addPrivilege(adminSession, homePath, username, Privilege.JCR_ALL);
+                               JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username);
+                               JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL);
+//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER,
+//                                             Privilege.JCR_READ);
                        }
                        if (adminSession.hasPendingChanges())
                                adminSession.save();
@@ -186,8 +208,26 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
 //             }
        }
 
+       private String extractUserId(String username) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new CmsException("Invalid name " + username, e);
+               }
+               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+               return userId;
+//             int atIndex = userId.indexOf('@');
+//             if (atIndex < 0) {
+//                     return homeBasePath+'/' + userId;
+//             } else {
+//                     return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
+//             }
+       }
+
        public void createWorkgroup(LdapName dn) {
-               Session adminSession = KernelUtils.openAdminSession(this);
+               String groupsWorkspace = getGroupsWorkspace();
+               Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace);
                String cn = dn.getRdn(dn.size() - 1).getValue().toString();
                Node newWorkgroup = NodeUtils.getGroupHome(adminSession, cn);
                if (newWorkgroup != null) {
@@ -198,10 +238,12 @@ class HomeRepository extends JcrRepositoryWrapper implements KernelConstants {
                        // TODO enhance transformation of cn to a valid node name
                        // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_");
                        String relPath = JcrUtils.replaceInvalidChars(cn);
-                       newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED);
-                       newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME);
+                       newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED);
+//                     newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED);
+//                     newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME);
                        newWorkgroup.addMixin(NodeType.MIX_CREATED);
-                       newWorkgroup.setProperty(NodeNames.LDAP_CN, cn);
+                       newWorkgroup.setProperty(Property.JCR_ID, dn.toString());
+//                     newWorkgroup.setProperty(NodeNames.LDAP_CN, cn);
                        adminSession.save();
                        JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL);
                        adminSession.save();
index 0f2d2d8f7d2aba9713aa52788a8d6853b8bc166d..14f8f73274a4764ecf190b6fa1f07e21f3fc902d 100644 (file)
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.argeo.commons</groupId>
                        <artifactId>org.argeo.node.api</artifactId>
                        <version>2.1.86-SNAPSHOT</version>
                </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>2.1.86-SNAPSHOT</version>
+               </dependency>
 
                <!-- TESTING -->
                <dependency>
index cd0cf86f29e103b6b67c5bfb29a3a8154ea0979f..bffe531a171fbcef5aaa9b598893cbb6b0387b00 100644 (file)
@@ -1,6 +1,8 @@
 package org.argeo.security.jackrabbit;
 
+import java.security.Principal;
 import java.util.Map;
+import java.util.Set;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -12,11 +14,17 @@ public class ArgeoAccessControlProvider extends ACLProvider {
 
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
-       public void init(Session systemSession, Map configuration)
-                       throws RepositoryException {
+       public void init(Session systemSession, Map configuration) throws RepositoryException {
                if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS))
                        configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true");
+               if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS))
+                       configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true");
                super.init(systemSession, configuration);
        }
 
+       @Override
+       public boolean canAccessRoot(Set<Principal> principals) throws RepositoryException {
+               return super.canAccessRoot(principals);
+       }
+
 }
index 15199c0ce6df15eee4c9692d1d15ef9048bc3916..36401596f0fb38192c1d319af5abcc82f7e9275c 100644 (file)
 package org.argeo.security.jackrabbit;
 
 import java.security.Principal;
+import java.util.HashSet;
+import java.util.Properties;
 import java.util.Set;
 
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.core.DefaultSecurityManager;
 import org.apache.jackrabbit.core.security.AMContext;
@@ -31,12 +36,26 @@ import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.SystemPrincipal;
 import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
 import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
+import org.argeo.cms.auth.CmsSession;
 import org.argeo.node.NodeConstants;
 import org.argeo.node.security.AnonymousPrincipal;
 import org.argeo.node.security.DataAdminPrincipal;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 
 /** Customises Jackrabbit security. */
 public class ArgeoSecurityManager extends DefaultSecurityManager {
+       private final static Log log = LogFactory.getLog(ArgeoSecurityManager.class);
+
+       private BundleContext cmsBundleContext = null;
+
+       public ArgeoSecurityManager() {
+               if (FrameworkUtil.getBundle(CmsSession.class) != null) {
+                       cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext();
+               }
+       }
+
        @Override
        public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
                synchronized (getSystemSession()) {
@@ -51,6 +70,11 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
                }
        }
 
+       @Override
+       protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
+               return super.createDefaultPrincipalProvider(moduleConfig);
+       }
+
        /** Called once when the session is created */
        @Override
        public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
@@ -59,6 +83,13 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
                boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty();
                Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
                boolean isRegularUser = !userPrincipal.isEmpty();
+               CmsSession cmsSession = null;
+               if (cmsBundleContext != null) {
+                       cmsSession = CmsSession.getCmsSession(cmsBundleContext, subject);
+                       if (log.isTraceEnabled())
+                               log.trace("Opening JCR session for CMS session " + cmsSession);
+               }
+
                if (isAnonymous) {
                        if (isDataAdmin || isJackrabbitSystem || isRegularUser)
                                throw new IllegalStateException("Inconsistent " + subject);
@@ -96,7 +127,10 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
        @Override
        protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
                WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager();
-               return new ArgeoWorkspaceAccessManagerImpl(wam);
+               ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam);
+               if (log.isTraceEnabled())
+                       log.trace("Created workspace access manager");
+               return workspaceAccessManager;
        }
 
        private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager {
@@ -109,6 +143,10 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
 
                public void init(Session systemSession) throws RepositoryException {
                        wam.init(systemSession);
+                       Repository repository = systemSession.getRepository();
+                       if (log.isTraceEnabled())
+                               log.trace("Initialised workspace access manager on repository " + repository
+                                               + ", systemSession workspace: " + systemSession.getWorkspace().getName());
                }
 
                public void close() throws RepositoryException {
@@ -116,7 +154,10 @@ public class ArgeoSecurityManager extends DefaultSecurityManager {
 
                public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
                        // TODO: implements finer access to workspaces
+                       if (log.isTraceEnabled())
+                               log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'");
                        return true;
+                       // return wam.grants(principals, workspaceName);
                }
        }
 
diff --git a/org.argeo.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/org.argeo.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java
new file mode 100644 (file)
index 0000000..a75c795
--- /dev/null
@@ -0,0 +1,80 @@
+package org.argeo.jackrabbit.security;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.Privilege;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.argeo.jcr.JcrUtils;
+
+/** Utilities around Jackrabbit security extensions. */
+public class JackrabbitSecurityUtils {
+       private final static Log log = LogFactory.getLog(JackrabbitSecurityUtils.class);
+
+       /**
+        * Convenience method for denying a single privilege to a principal (user or
+        * role), typically jcr:all
+        */
+       public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
+                       throws RepositoryException {
+               List<Privilege> privileges = new ArrayList<Privilege>();
+               privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
+               denyPrivileges(session, path, () -> principal, privileges);
+       }
+
+       /**
+        * Deny privileges on a path to a {@link Principal}. The path must already
+        * exist. Session is saved. Synchronized to prevent concurrent modifications of
+        * the same node.
+        */
+       public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
+                       List<Privilege> privs) throws RepositoryException {
+               // make sure the session is in line with the persisted state
+               session.refresh(false);
+               JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
+               JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
+
+//             accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+//                     Principal currentPrincipal = ace.getPrincipal();
+//                     if (currentPrincipal.getName().equals(principal.getName())) {
+//                             Privilege[] currentPrivileges = ace.getPrivileges();
+//                             if (currentPrivileges.length != privs.size())
+//                                     break accessControlEntries;
+//                             for (int i = 0; i < currentPrivileges.length; i++) {
+//                                     Privilege currP = currentPrivileges[i];
+//                                     Privilege p = privs.get(i);
+//                                     if (!currP.getName().equals(p.getName())) {
+//                                             break accessControlEntries;
+//                                     }
+//                             }
+//                             return false;
+//                     }
+//             }
+
+               Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+               acl.addEntry(principal, privileges, false);
+               acm.setPolicy(path, acl);
+               if (log.isDebugEnabled()) {
+                       StringBuffer privBuf = new StringBuffer();
+                       for (Privilege priv : privs)
+                               privBuf.append(priv.getName());
+                       log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
+                                       + session.getWorkspace().getName() + "'");
+               }
+               session.refresh(true);
+               session.save();
+               return true;
+       }
+
+       /** Singleton. */
+       private JackrabbitSecurityUtils() {
+
+       }
+}
index d78ccae8d1d756bf380b8dfaebad3562be0caa04..416d035f8b5925555e421c46ede06e21e85dd231 100644 (file)
@@ -1308,23 +1308,30 @@ public class JcrUtils {
                return true;
        }
 
-       /** Gets access control list for this path, throws exception if not found */
+       /**
+        * Gets the first available access control list for this path, throws exception
+        * if not found
+        */
        public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
                        throws RepositoryException {
                // search for an access control list
                AccessControlList acl = null;
                AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
-               if (policyIterator.hasNext()) {
+               applicablePolicies: if (policyIterator.hasNext()) {
                        while (policyIterator.hasNext()) {
                                AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
-                               if (acp instanceof AccessControlList)
+                               if (acp instanceof AccessControlList) {
                                        acl = ((AccessControlList) acp);
+                                       break applicablePolicies;
+                               }
                        }
                } else {
                        AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
-                       for (AccessControlPolicy acp : existingPolicies) {
-                               if (acp instanceof AccessControlList)
+                       existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
+                               if (acp instanceof AccessControlList) {
                                        acl = ((AccessControlList) acp);
+                                       break existingPolicies;
+                               }
                        }
                }
                if (acl != null)
index 52a2bdd574dc70567789cc824d4f6ac5f9d820e4..5b235f666b8e7d1221dd4b76b13f0b45089ffa37 100644 (file)
@@ -1 +1 @@
-Provide-Capability: cms.datamodel;name=node;cnd=/org/argeo/node/node.cnd
\ No newline at end of file
+Provide-Capability: cms.datamodel;name=node;cnd=/org/argeo/node/ldap.cnd;abstract=true
index 31029d9915a9af57b3d6cea49f58257afce19932..067d8c3174af7fc4d5c962f11e6a0d0824f782c1 100644 (file)
@@ -24,6 +24,9 @@ public interface NodeConstants {
         */
        String NODE = "node";
        String HOME = "home";
+       String GROUPS = "groups";
+       String GUESTS = "guests";
+       String PUBLIC = "public";
 
        /*
         * BASE DNs
index 05b86ffdcec13e9b5c1272251142b6b4520e291f..7ded1115d1b0adc24ccddbbd502a7febc416461e 100644 (file)
@@ -16,6 +16,7 @@
 package org.argeo.node;
 
 /** JCR types in the http://www.argeo.org/node namespace */
+@Deprecated
 public interface NodeNames {
        String LDAP_UID = "ldap:"+NodeConstants.UID;
        String LDAP_CN = "ldap:"+NodeConstants.CN;
index bfb55e1750ee1cf2a7a9577ffdc4a8d3fea84d68..891f54659f57691edab8d63080c386565213b882 100644 (file)
@@ -16,6 +16,7 @@
 package org.argeo.node;
 
 /** JCR types in the http://www.argeo.org/node namespace */
+@Deprecated
 public interface NodeTypes {
        String NODE_USER_HOME = "node:userHome";
        String NODE_GROUP_HOME = "node:groupHome";
index 375e916d58f6c2692a60cc8f23ae73ce928a2c03..378ec3322032a13a9d043374546d999ba72c6684 100644 (file)
@@ -28,11 +28,8 @@ import javax.jcr.RepositoryFactory;
 import javax.jcr.Session;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
-import javax.jcr.query.qom.Constraint;
-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.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
 import javax.security.auth.AuthPermission;
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
@@ -91,39 +88,102 @@ public class NodeUtils {
         * @param username the username of the user
         */
        public static Node getUserHome(Session session, String username) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for user " + username, e);
+//             }
+
                try {
-                       QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-                       Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
-                       DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
-                       StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
-                       Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-                       Query query = qomf.createQuery(sel, constraint, null, null);
-                       return querySingleNode(query);
+                       checkUserWorkspace(session, username);
+                       String homePath = getHomePath(username);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/home/" + username;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
                } catch (RepositoryException e) {
                        throw new RuntimeException("Cannot find home for user " + username, e);
                }
        }
 
+       private static String getHomePath(String username) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid name " + username, e);
+               }
+               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+               return '/' + userId;
+       }
+
+       private static void checkUserWorkspace(Session session, String username) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!NodeConstants.HOME.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
+       }
+
        /**
         * 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 groupname the name of the group
         */
-       public static Node getGroupHome(Session session, String cn) {
+       public static Node getGroupHome(Session session, String groupname) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for group " + cn, e);
+//             }
+
                try {
-                       QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-                       Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
-                       DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
-                       StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
-                       Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-                       Query query = qomf.createQuery(sel, constraint, null, null);
-                       return querySingleNode(query);
+                       checkGroupWorkspace(session, groupname);
+                       String homePath = getGroupPath(groupname);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/groups/" + groupname;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
                } catch (RepositoryException e) {
-                       throw new RuntimeException("Cannot find home for user " + cn, e);
+                       throw new RuntimeException("Cannot find home for group " + groupname, e);
+               }
+
+       }
+
+       private static String getGroupPath(String groupname) {
+               String cn;
+               try {
+                       LdapName dn = new LdapName(groupname);
+                       cn = dn.getRdn(dn.size() - 1).getValue().toString();
+               } catch (InvalidNameException e) {
+                       cn = groupname;
                }
+               return '/' + cn;
+       }
+
+       private static void checkGroupWorkspace(Session session, String groupname) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!NodeConstants.GROUPS.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
        }
 
        /**
diff --git a/org.argeo.node.api/src/org/argeo/node/ldap.cnd b/org.argeo.node.api/src/org/argeo/node/ldap.cnd
new file mode 100644 (file)
index 0000000..a2306c6
--- /dev/null
@@ -0,0 +1 @@
+<ldap = 'http://www.argeo.org/ns/ldap'>
index 1a6dec5efeca9843f20d2fbc4791fe762c7c0124..d8a26b64e3640db863de82d7bf4b4402839a7cb3 100644 (file)
@@ -1,4 +1,3 @@
-<ldap = 'http://www.argeo.org/ns/ldap'>
 <node = 'http://www.argeo.org/ns/node'>
 
 [node:userHome]