From: Mathieu Baudier Date: Sat, 28 Nov 2020 11:52:58 +0000 (+0100) Subject: Introduce user and session nodes. X-Git-Tag: argeo-suite-2.1.16~21 X-Git-Url: http://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=25ed06d42600a81063515220c51ac2d93fefa27a Introduce user and session nodes. --- diff --git a/org.argeo.entity.api/src/org/argeo/entity/EntityType.java b/org.argeo.entity.api/src/org/argeo/entity/EntityType.java index 408b142..732c9b1 100644 --- a/org.argeo.entity.api/src/org/argeo/entity/EntityType.java +++ b/org.argeo.entity.api/src/org/argeo/entity/EntityType.java @@ -22,6 +22,10 @@ public enum EntityType implements JcrName { return "entity"; } + public String basePath() { + return '/' + name(); + } + @Override public String getNamespace() { return namespace(); diff --git a/org.argeo.entity.api/src/org/argeo/entity/entity.cnd b/org.argeo.entity.api/src/org/argeo/entity/entity.cnd index 85da650..cd6952a 100644 --- a/org.argeo.entity.api/src/org/argeo/entity/entity.cnd +++ b/org.argeo.entity.api/src/org/argeo/entity/entity.cnd @@ -74,7 +74,10 @@ mixin mixin - ldap:sn (String) - ldap:givenName (String) +- ldap:mail (String) * [entity:user] > entity:person +mixin +- ldap:distinguishedName (String) - ldap:uid (String) diff --git a/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java b/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java new file mode 100644 index 0000000..f264bdf --- /dev/null +++ b/org.argeo.suite.core/src/org/argeo/suite/SuiteUtils.java @@ -0,0 +1,82 @@ +package org.argeo.suite; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.security.Privilege; +import javax.naming.ldap.LdapName; + +import org.argeo.cms.auth.CmsSession; +import org.argeo.entity.EntityType; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.argeo.naming.LdapAttrs; + +/** Utilities around the Argeo Suite APIs. */ +public class SuiteUtils { + + public static String getUserNodePath(LdapName userDn) { + String uid = userDn.getRdn(userDn.size() - 1).getValue().toString(); + return EntityType.user.basePath() + '/' + uid; + } + + public static Node getOrCreateUserNode(Session adminSession, LdapName userDn) { + try { + Node usersBase = adminSession.getNode(EntityType.user.basePath()); + String uid = userDn.getRdn(userDn.size() - 1).getValue().toString(); + Node userNode; + if (!usersBase.hasNode(uid)) { + userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED); + userNode.addMixin(EntityType.user.get()); + userNode.addMixin(NodeType.MIX_CREATED); + userNode.setProperty(LdapAttrs.distinguishedName.property(), userDn.toString()); + userNode.setProperty(LdapAttrs.uid.property(), uid); + adminSession.save(); + } else { + userNode = usersBase.getNode(uid); + } + return userNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot create user node for " + userDn, e); + } + } + + public static Node getOrCreateSessionDir(Session adminSession, CmsSession cmsSession) { + try { + LdapName userDn = cmsSession.getUserDn(); +// String uid = userDn.get(userDn.size() - 1); + Node userNode = getOrCreateUserNode(adminSession, userDn); +// if (!usersBase.hasNode(uid)) { +// userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED); +// userNode.addMixin(EntityType.user.get()); +// userNode.addMixin(NodeType.MIX_CREATED); +// usersBase.setProperty(LdapAttrs.uid.property(), uid); +// usersBase.setProperty(LdapAttrs.distinguishedName.property(), userDn.toString()); +// adminSession.save(); +// } else { +// userNode = usersBase.getNode(uid); +// } + String cmsSessionUuid = cmsSession.getUuid().toString(); + Node userDir; + if (!userNode.hasNode(cmsSessionUuid)) { + userDir = userNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED); + userDir.addMixin(NodeType.MIX_CREATED); + adminSession.save(); + JcrUtils.addPrivilege(adminSession, userDir.getPath(), cmsSession.getUserDn().toString(), + Privilege.JCR_ALL); + } else { + userDir = userNode.getNode(cmsSessionUuid); + } + return userDir; + } catch (RepositoryException e) { + throw new JcrException("Cannot create session dir for " + cmsSession, e); + } + } + + /** Singleton. */ + private SuiteUtils() { + + } + +} diff --git a/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java b/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java index 91f2043..db6ac91 100644 --- a/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java +++ b/org.argeo.suite.core/src/org/argeo/suite/core/SuiteMaintenanceService.java @@ -2,9 +2,15 @@ package org.argeo.suite.core; import java.io.IOException; +import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.security.Privilege; +import org.argeo.api.NodeConstants; +import org.argeo.entity.EntityType; +import org.argeo.jcr.JcrUtils; import org.argeo.maintenance.AbstractMaintenanceService; /** Initialises an Argeo Suite backend. */ @@ -13,14 +19,20 @@ public class SuiteMaintenanceService extends AbstractMaintenanceService { @Override public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException { boolean modified = false; -// Node rootNode = adminSession.getRootNode(); -// if (!rootNode.hasNode(EntityNames.TERM)) { -// rootNode.addNode(EntityNames.TERM, EntityType.typologies.get()); -// modified = true; -// } -// if (modified) -// adminSession.save(); + Node rootNode = adminSession.getRootNode(); + if (!rootNode.hasNode(EntityType.user.name())) { + rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED); + modified = true; + } + if (modified) + adminSession.save(); return modified; } + @Override + public void configurePrivileges(Session adminSession) throws RepositoryException { + JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), NodeConstants.ROLE_USER_ADMIN, + Privilege.JCR_ALL); + } + } diff --git a/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java b/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java index 0e7d2d8..170bf83 100644 --- a/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java +++ b/org.argeo.suite.ui/src/org/argeo/suite/ui/AdminEntryArea.java @@ -5,6 +5,7 @@ import java.util.Set; import javax.jcr.Node; import javax.jcr.RepositoryException; +import org.argeo.api.NodeConstants; import org.argeo.cms.CmsUserManager; import org.argeo.cms.ui.CmsTheme; import org.argeo.cms.ui.CmsUiProvider; @@ -14,6 +15,7 @@ import org.argeo.entity.EntityType; import org.argeo.jcr.Jcr; import org.argeo.jcr.JcrUtils; import org.argeo.naming.LdapAttrs; +import org.argeo.suite.SuiteRole; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; @@ -105,9 +107,9 @@ public class AdminEntryArea implements CmsUiProvider { public void doubleClick(DoubleClickEvent event) { User user = (User) usersViewer.getStructuredSelection().getFirstElement(); if (user != null) { - Node userNode = getOrCreateUserNode(user, context); +// Node userNode = getOrCreateUserNode(user, context); CmsView.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(), - SuiteEvent.eventProperties(userNode)); + SuiteEvent.eventProperties(user)); } } @@ -116,9 +118,9 @@ public class AdminEntryArea implements CmsUiProvider { public void selectionChanged(SelectionChangedEvent event) { User user = (User) usersViewer.getStructuredSelection().getFirstElement(); if (user != null) { - Node userNode = getOrCreateUserNode(user, context); +// Node userNode = getOrCreateUserNode(user, context); CmsView.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(), - SuiteEvent.eventProperties(userNode)); + SuiteEvent.eventProperties(user)); deleteItem.setEnabled(true); } else { deleteItem.setEnabled(false); @@ -132,11 +134,11 @@ public class AdminEntryArea implements CmsUiProvider { return usersViewer.getTable(); } - private Node getOrCreateUserNode(User user, Node context) { - return JcrUtils.mkdirs(Jcr.getSession(context), - "/" + EntityType.user.name() + "/" + getUserProperty(user, LdapAttrs.uid.name()), - EntityType.user.get()); - } +// private Node getOrCreateUserNode(User user, Node context) { +// return JcrUtils.mkdirs(Jcr.getSession(context), +// "/" + EntityType.user.name() + "/" + getUserProperty(user, LdapAttrs.uid.name()), +// EntityType.user.get()); +// } private String getUserProperty(Object element, String key) { Object value = ((User) element).getProperties().get(key); @@ -148,8 +150,7 @@ public class AdminEntryArea implements CmsUiProvider { @Override public Object[] getElements(Object inputElement) { CmsUserManager cum = (CmsUserManager) inputElement; - String baseGroup = "cn=apaf-coworker,cn=groups,cn=accounts,dc=id,dc=argeo,dc=pro"; - Set users = cum.listUsersInGroup(baseGroup, null); + Set users = cum.listUsersInGroup(SuiteRole.coworker.dn(), null); return users.toArray(); } diff --git a/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java b/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java index 80ebd6e..db8a8dc 100644 --- a/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java +++ b/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java @@ -15,11 +15,14 @@ import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeUtils; import org.argeo.cms.CmsUserManager; +import org.argeo.cms.auth.CmsSession; import org.argeo.cms.ui.AbstractCmsApp; import org.argeo.cms.ui.CmsTheme; import org.argeo.cms.ui.CmsUiProvider; @@ -31,14 +34,15 @@ import org.argeo.entity.EntityConstants; import org.argeo.entity.EntityNames; import org.argeo.entity.EntityType; import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrUtils; import org.argeo.suite.RankedObject; +import org.argeo.suite.SuiteUtils; import org.argeo.util.LangUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.osgi.framework.Constants; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; +import org.osgi.service.useradmin.User; /** The Argeo Suite App. */ public class SuiteApp extends AbstractCmsApp implements EventHandler { @@ -125,25 +129,32 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { ui.logout(); ui.refreshBelowHeader(false); refreshPart(findUiProvider(LOGIN_SCREEN_PID), ui.getBelowHeader(), context); + ui.layout(true, true); } else { - try { - if (ui.getUserHome() == null) - ui.initSessions(getRepository()); - context = ui.getUserHome(); - - } catch (RepositoryException e) { - e.printStackTrace(); + if (ui.getUserDir() == null) { + CmsSession cmsSession = cmsView.getCmsSession(); + Session adminSession = null; + try { + adminSession = NodeUtils.openDataAdminSession(getRepository(), null); + Node userDir = SuiteUtils.getOrCreateSessionDir(adminSession, cmsSession); + ui.initSessions(getRepository(), userDir.getPath()); + } finally { + Jcr.logout(adminSession); + } } - ui.refreshBelowHeader(true); + context = stateToNode(ui, state); + if (context == null) + context = ui.getUserDir(); + ui.refreshBelowHeader(true); for (String key : layersByPid.keySet()) { SuiteLayer layer = layersByPid.get(key).get(); ui.addLayer(key, layer); } refreshPart(findUiProvider(LEAD_PANE_PID), ui.getLeadPane(), context); + ui.layout(true, true); + setState(parent, state); } - ui.layout(true, true); - setState(parent, state); } catch (Exception e) { CmsFeedback.show("Unexpected exception", e); } @@ -201,7 +212,7 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { } if (types.size() == 0) - throw new IllegalArgumentException("No component found for " + context); + throw new IllegalArgumentException("No type found for " + context); String type = types.iterator().next(); if (!byType.containsKey(type)) throw new IllegalArgumentException("No component found for " + context + " with type " + type); @@ -213,46 +224,93 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { @Override public void setState(Composite parent, String state) { - CmsView cmsView = CmsView.getCmsView(parent); - if(cmsView.isAnonymous()) + if (state == null || !state.startsWith("/")) return; - // for the time being we systematically open a session, in order to make sure - // that home is initialised - Session session = null; - try { - if (state != null && state.startsWith("/")) { - String path = state.substring(1); - String workspace; - if (path.equals("")) { - workspace = null; - path = "/"; - } else { - int index = path.indexOf('/'); - if (index == 0) { - log.error("Cannot interpret " + state); - cmsView.navigateTo("~"); - return; - } else if (index > 0) { - workspace = path.substring(0, index); - path = path.substring(index); - } else {// index<0, assuming root node - workspace = path; - path = "/"; - } - } - session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace)); + SuiteUi suiteUi = (SuiteUi) parent; + Node node = stateToNode(suiteUi, state); + if (node == null) { + suiteUi.getCmsView().navigateTo("~"); + } else { + suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node)); + suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node)); + } + +// CmsView cmsView = CmsView.getCmsView(parent); +// if (cmsView.isAnonymous()) +// return; +// Session session = null; +// try { +// if (state != null && state.startsWith("/")) { +// String path = state.substring(1); +// String workspace; +// if (path.equals("")) { +// workspace = null; +// path = "/"; +// } else { +// int index = path.indexOf('/'); +// if (index == 0) { +// log.error("Cannot interpret " + state); +// cmsView.navigateTo("~"); +// return; +// } else if (index > 0) { +// workspace = path.substring(0, index); +// path = path.substring(index); +// } else {// index<0, assuming root node +// workspace = path; +// path = "/"; +// } +// } +// session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace)); +// +// Node node = session.getNode(path); +// +// cmsView.sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node)); +// cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node)); +// } +// } catch (RepositoryException e) { +// log.error("Cannot load state " + state, e); +// cmsView.navigateTo("~"); +// } finally { +// JcrUtils.logoutQuietly(session); +// } + } + + private String nodeToState(Node node) { + return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node); + } - Node node = session.getNode(path); + private Node stateToNode(SuiteUi suiteUi, String state) { + if (suiteUi == null) + return null; + if (state == null) + return null; - cmsView.sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node)); - cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node)); + String path = state.substring(1); + String workspace; + if (path.equals("")) { + workspace = null; + path = "/"; + } else { + int index = path.indexOf('/'); + if (index == 0) { + log.error("Cannot interpret " + state); +// cmsView.navigateTo("~"); + return null; + } else if (index > 0) { + workspace = path.substring(0, index); + path = path.substring(index); + } else {// index<0, assuming root node + workspace = path; + path = "/"; } - } catch (RepositoryException e) { - log.error("Cannot load state " + state, e); - cmsView.navigateTo("~"); - } finally { - JcrUtils.logoutQuietly(session); } +// session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace)); + + Session session = suiteUi.getSession(workspace); + if (session == null) + return null; + Node node = Jcr.getNode(session, path); + return node; } /* @@ -268,16 +326,12 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { String currentLayerId = ui.getCurrentLayerId(); SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null; if (isTopic(event, SuiteEvent.refreshPart)) { - String nodePath = get(event, SuiteEvent.NODE_PATH); - String workspace = get(event, SuiteEvent.WORKSPACE); - Node node = Jcr.getNode(ui.getSession(workspace), nodePath); + Node node = getNode(ui, event); CmsUiProvider uiProvider = findByType(uiProvidersByType, node); currentLayer.view(uiProvider, ui.getCurrentWorkArea(), node); ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node)); } else if (isTopic(event, SuiteEvent.openNewPart)) { - String nodePath = get(event, SuiteEvent.NODE_PATH); - String workspace = get(event, SuiteEvent.WORKSPACE); - Node node = Jcr.getNode(ui.getSession(workspace), nodePath); + Node node = getNode(ui, event); CmsUiProvider uiProvider = findByType(uiProvidersByType, node); currentLayer.open(uiProvider, ui.getCurrentWorkArea(), node); ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node)); @@ -286,10 +340,8 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { if (layerId != null) { ui.switchToLayer(layerId, Jcr.getRootNode(ui.getSession(null))); } else { - String nodePath = get(event, SuiteEvent.NODE_PATH); - String workspace = get(event, SuiteEvent.WORKSPACE); - if (nodePath != null) { - Node node = Jcr.getNode(ui.getSession(workspace), nodePath); + Node node = getNode(ui, event); + if (node != null) { SuiteLayer layer = findByType(layersByType, node); ui.switchToLayer(layer, node); } @@ -302,8 +354,42 @@ public class SuiteApp extends AbstractCmsApp implements EventHandler { } - private String nodeToState(Node node) { - return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node); + private Node getNode(SuiteUi ui, Event event) { + String nodePath = get(event, SuiteEvent.NODE_PATH); + String workspaceName = get(event, SuiteEvent.WORKSPACE); + Session session = ui.getSession(workspaceName); + Node node; + if (nodePath == null) { + // look for a user + String username = get(event, SuiteEvent.USERNAME); + if (username == null) + return null; + User user = cmsUserManager.getUser(username); + if (user == null) + return null; + LdapName userDn; + try { + userDn = new LdapName(user.getName()); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Badly formatted username", e); + } + String userNodePath = SuiteUtils.getUserNodePath(userDn); + if (Jcr.itemExists(session, userNodePath)) + node = Jcr.getNode(session, userNodePath); + else { + Session adminSession = null; + try { + adminSession = NodeUtils.openDataAdminSession(getRepository(), workspaceName); + SuiteUtils.getOrCreateUserNode(adminSession, userDn); + } finally { + Jcr.logout(adminSession); + } + node = Jcr.getNode(session, userNodePath); + } + } else { + node = Jcr.getNode(session, nodePath); + } + return node; } private SuiteUi getRelatedUi(Event event) { diff --git a/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java b/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java index 1231d92..cad5a9b 100644 --- a/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java +++ b/org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteUi.java @@ -11,7 +11,6 @@ import javax.jcr.Session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.api.NodeConstants; -import org.argeo.api.NodeUtils; import org.argeo.cms.ui.CmsView; import org.argeo.cms.ui.util.CmsUiUtils; import org.argeo.jcr.Jcr; @@ -29,8 +28,8 @@ class SuiteUi extends Composite { private Composite dynamicArea; private Session sysSession; - private Session homeSession; - private Node userHome; +// private Session homeSession; + private Node userDir; private Map layers = new HashMap<>(); private Map workAreas = new HashMap<>(); @@ -105,7 +104,7 @@ class SuiteUi extends Composite { } if (context == null) { if (!cmsView.isAnonymous()) - context = userHome; + context = userDir; } Composite toShow = getLayer(layerId, context); currentLayerId = layerId; @@ -152,9 +151,9 @@ class SuiteUi extends Composite { } synchronized void logout() { - userHome = null; + userDir = null; Jcr.logout(sysSession); - Jcr.logout(homeSession); +// Jcr.logout(homeSession); currentLayerId = null; workAreas.clear(); } @@ -179,18 +178,18 @@ class SuiteUi extends Composite { // return sysSession; // } // - synchronized void initSessions(Repository repository) throws RepositoryException { + synchronized void initSessions(Repository repository, String userDirPath) throws RepositoryException { this.sysSession = repository.login(); - this.homeSession = repository.login(NodeConstants.HOME_WORKSPACE); - userHome = NodeUtils.getUserHome(homeSession); +// this.homeSession = repository.login(NodeConstants.HOME_WORKSPACE); + userDir = sysSession.getNode(userDirPath); addDisposeListener((e) -> { Jcr.logout(sysSession); - Jcr.logout(homeSession); +// Jcr.logout(homeSession); }); } - Node getUserHome() { - return userHome; + Node getUserDir() { + return userDir; } Session getSysSession() { @@ -202,8 +201,8 @@ class SuiteUi extends Composite { return sysSession; if (NodeConstants.SYS_WORKSPACE.equals(workspaceName)) return sysSession; - else if (NodeConstants.HOME_WORKSPACE.equals(workspaceName)) - return homeSession; +// else if (NodeConstants.HOME_WORKSPACE.equals(workspaceName)) +// return homeSession; else throw new IllegalArgumentException("Unknown workspace " + workspaceName); }