Introduce hierarchies in user management
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 15 Jun 2022 09:15:52 +0000 (11:15 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 15 Jun 2022 09:15:52 +0000 (11:15 +0200)
19 files changed:
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtPart.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtHierarchicalPart.java
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTabularPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java

diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtPart.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtPart.java
new file mode 100644 (file)
index 0000000..e846bb5
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.widgets.Composite;
+
+public abstract class AbstractSwtPart {
+       protected final Composite area;
+
+       public AbstractSwtPart(Composite parent, int style) {
+               area = new Composite(parent, style);
+               area.setLayout(CmsSwtUtils.noSpaceGridLayout());
+       }
+
+}
index d390b5364f407b8ae43d10bf31dd255e0cdfa3f5..08c85b1657af0a9a01a0449aa4880d9cb16bb5cd 100644 (file)
@@ -12,28 +12,30 @@ import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeItem;
 
 /** {@link HierarchicalPart} implementation based on a {@link Tree}. */
-public class SwtHierarchicalPart extends Composite implements HierarchicalPart {
-       private static final long serialVersionUID = -2189742596757101778L;
-
+public class SwtHierarchicalPart implements HierarchicalPart {
+       private Composite area;
        private final Tree tree;
 
        private Consumer<Object> onSelected;
        private Consumer<Object> onAction;
 
        public SwtHierarchicalPart(Composite parent, int style) {
-               super(parent, style);
-               setLayout(CmsSwtUtils.noSpaceGridLayout());
-               tree = new Tree(this, SWT.VIRTUAL | SWT.BORDER);
+               area = new Composite(parent, style);
+               area.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               tree = new Tree(area, SWT.VIRTUAL | SWT.BORDER);
        }
 
+       @Override
        public void refresh() {
+               // TODO optimise
+               tree.clearAll(true);
                tree.addListener(SWT.SetData, event -> {
                        TreeItem item = (TreeItem) event.item;
                        TreeItem parentItem = item.getParentItem();
                        if (parentItem == null) {
                                refreshRootItem(item);
                        } else {
-                               refreshItem(parentItem, parentItem);
+                               refreshItem(parentItem, item);
                        }
                });
                tree.setItemCount(getRootItemCount());
@@ -45,17 +47,30 @@ public class SwtHierarchicalPart extends Composite implements HierarchicalPart {
 
                        @Override
                        public void widgetSelected(SelectionEvent e) {
-                               onSelected.accept(e.item.getData());
+                               if (onSelected != null)
+                                       onSelected.accept(e.item.getData());
                        }
 
                        @Override
                        public void widgetDefaultSelected(SelectionEvent e) {
-                               onAction.accept(e.item.getData());
+                               if (onAction != null)
+                                       onAction.accept(e.item.getData());
                        }
                });
 
        }
 
+       @Override
+       public void setInput(Object data) {
+               area.setData(data);
+               refresh();
+       }
+
+       @Override
+       public Object getInput() {
+               return area.getData();
+       }
+
        protected void refreshRootItem(TreeItem item) {
 
        }
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTabularPart.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTabularPart.java
new file mode 100644 (file)
index 0000000..02f2f81
--- /dev/null
@@ -0,0 +1,89 @@
+package org.argeo.cms.swt.widgets;
+
+import java.util.function.Consumer;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.TabularPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+
+/** {@link TabularPart} implementation based on a {@link Table}. */
+public class SwtTabularPart implements TabularPart {
+       private Composite area;
+
+       private final Table table;
+
+       private Consumer<Object> onSelected;
+       private Consumer<Object> onAction;
+
+       public SwtTabularPart(Composite parent, int style) {
+               area = new Composite(parent, style);
+               area.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               table = new Table(area, SWT.VIRTUAL | SWT.BORDER);
+       }
+
+       @Override
+       public void refresh() {
+               // TODO optimise
+               table.clearAll();
+               table.addListener(SWT.SetData, event -> {
+                       TableItem item = (TableItem) event.item;
+                       refreshItem(item);
+               });
+               table.setItemCount(getItemCount());
+               CmsSwtUtils.fill(table);
+
+               table.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = -5225905921522775948L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               if (onSelected != null)
+                                       onSelected.accept(e.item.getData());
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               if (onAction != null)
+                                       onAction.accept(e.item.getData());
+                       }
+               });
+
+       }
+
+       @Override
+       public void setInput(Object data) {
+               area.setData(data);
+               refresh();
+       }
+
+       @Override
+       public Object getInput() {
+               return area.getData();
+       }
+
+       protected void refreshItem(TableItem item) {
+
+       }
+
+       protected int getItemCount() {
+               return 0;
+       }
+
+       protected Table getTable() {
+               return table;
+       }
+
+       public void onSelected(Consumer<Object> onSelected) {
+               this.onSelected = onSelected;
+       }
+
+       public void onAction(Consumer<Object> onAction) {
+               this.onAction = onAction;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java
new file mode 100644 (file)
index 0000000..7df7086
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.cms.ux.widgets;
+
+public interface ColumnsPart extends DataPart {
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java
new file mode 100644 (file)
index 0000000..5a8da46
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.function.Consumer;
+
+public interface DataPart {
+       void setInput(Object data);
+
+       Object getInput();
+
+       void refresh();
+
+       void onSelected(Consumer<Object> onSelected);
+
+       void onAction(Consumer<Object> onAction);
+}
index 244765eaffeee49689b480bb055ce3ca2b2b33c9..0926ff527901728c13f2231a8885c55b79c8a655 100644 (file)
@@ -1,5 +1,5 @@
 package org.argeo.cms.ux.widgets;
 
-public interface HierarchicalPart {
+public interface HierarchicalPart extends ColumnsPart {
 
 }
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java
new file mode 100644 (file)
index 0000000..40a56eb
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.cms.ux.widgets;
+
+public interface TabularPart extends ColumnsPart {
+
+}
index cd76d65ef05618372e1e11c2b3159a2a9b412832..c51d21caa30505129f01476db05647ffb02d0586 100644 (file)
@@ -2,10 +2,12 @@ package org.argeo.cms;
 
 import java.time.ZonedDateTime;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.security.auth.Subject;
 
+import org.argeo.osgi.useradmin.UserDirectory;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
@@ -15,7 +17,9 @@ import org.osgi.service.useradmin.User;
  * the userAdmin.
  */
 public interface CmsUserManager {
-
+       public Map<String, String> getKnownBaseDns(boolean onlyWritable);
+       public Set<UserDirectory> getUserDirectories();
+       
        // CurrentUser
        /** Returns the e-mail of the current logged in user */
        public String getMyMail();
index b43bf98b5f707744591b26006535ca784d8252b7..faf5555d0a61ef31ee06c7a79412e516ad7d67dd 100644 (file)
@@ -6,6 +6,7 @@ import java.security.PrivilegedAction;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Locale;
 import java.util.Set;
 import java.util.UUID;
@@ -125,7 +126,12 @@ public final class CurrentUser {
 
        public static CmsSession getCmsSession() {
                Subject subject = currentSubject();
-               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
+               Iterator<CmsSessionId> it = subject.getPrivateCredentials(CmsSessionId.class).iterator();
+               if (!it.hasNext())
+                       throw new IllegalStateException("No CMS session id available for " + subject);
+               CmsSessionId cmsSessionId = it.next();
+               if (it.hasNext())
+                       throw new IllegalStateException("More than one CMS session id available for " + subject);
                return CmsContextImpl.getCmsContext().getCmsSessionByUuid(cmsSessionId.getUuid());
        }
 
@@ -165,8 +171,8 @@ public final class CurrentUser {
        }
 
        /*
-        * PREPARE EVOLUTION OF JAVA APIs INTRODUCED IN JDK 18
-        * The following static methods will be added to Subject 
+        * PREPARE EVOLUTION OF JAVA APIs INTRODUCED IN JDK 18 The following static
+        * methods will be added to Subject
         */
        public Subject current() {
                return currentSubject();
index 782487a9ad7b083275e3db8d65810f750d0fbdcd..84562ebd16660a860df397778421037581f9ba64 100644 (file)
@@ -17,6 +17,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.UUID;
 
 import javax.naming.InvalidNameException;
@@ -243,6 +244,12 @@ public class CmsUserManagerImpl implements CmsUserManager {
                return dns;
        }
 
+       public Set<UserDirectory> getUserDirectories() {
+               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBasePath().compareTo(o2.getBasePath()));
+               res.addAll(userDirectories.keySet());
+               return res;
+       }
+
        public String buildDistinguishedName(String localId, String baseDn, int type) {
                Map<String, String> dns = getKnownBaseDns(true);
                Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
index 1ca5e4a553842600f18a7a911d0a4d881012235a..821808017644b9dcd17089cd426809b2d73c7a66 100644 (file)
@@ -182,7 +182,8 @@ public class InitUtils {
                        // TODO downgrade security level
                }
                for (String userAdminUri : userAdminUris.split(" "))
-                       uris.add(userAdminUri);
+                       if (!userAdminUri.trim().equals(""))
+                               uris.add(userAdminUri);
 
                // Interprets URIs
                for (String uri : uris) {
index 19cd06886ba3b83b2daa1ef42e291e88876b72a0..716ddb5edfa72975fecfd3fef81812d4e7554505 100644 (file)
@@ -124,7 +124,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
 
-       protected abstract List<DirectoryUser> doGetRoles(Filter f);
+       protected abstract List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep);
 
        protected abstract AbstractUserDirectory scope(User user);
 
@@ -240,9 +240,31 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        @Override
        public Role[] getRoles(String filter) throws InvalidSyntaxException {
+//             UserDirectoryWorkingCopy wc = getWorkingCopy();
+//             Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+//             List<DirectoryUser> res = doGetRoles(getBaseDn(), f, true);
+//             if (wc != null) {
+//                     for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
+//                             DirectoryUser user = it.next();
+//                             LdapName dn = user.getDn();
+//                             if (wc.getDeletedUsers().containsKey(dn))
+//                                     it.remove();
+//                     }
+//                     for (DirectoryUser user : wc.getNewUsers().values()) {
+//                             if (f == null || f.match(user.getProperties()))
+//                                     res.add(user);
+//                     }
+//                     // no need to check modified users,
+//                     // since doGetRoles was already based on the modified attributes
+//             }
+               List<? extends Role> res = getRoles(getBaseDn(), filter, true);
+               return res.toArray(new Role[res.size()]);
+       }
+
+       List<DirectoryUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
                UserDirectoryWorkingCopy wc = getWorkingCopy();
                Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
-               List<DirectoryUser> res = doGetRoles(f);
+               List<DirectoryUser> res = doGetRoles(searchBase, f, deep);
                if (wc != null) {
                        for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
                                DirectoryUser user = it.next();
@@ -257,7 +279,22 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        // no need to check modified users,
                        // since doGetRoles was already based on the modified attributes
                }
-               return res.toArray(new Role[res.size()]);
+
+               // if non deep we also search users and groups
+               if (!deep) {
+                       try {
+                               if (!(searchBase.endsWith(new LdapName(getUserBase()))
+                                               || searchBase.endsWith(new LdapName(getGroupBase())))) {
+                                       LdapName usersBase = (LdapName) ((LdapName) searchBase.clone()).add(getUserBase());
+                                       res.addAll(getRoles(usersBase, filter, false));
+                                       LdapName groupsBase = (LdapName) ((LdapName) searchBase.clone()).add(getGroupBase());
+                                       res.addAll(getRoles(groupsBase, filter, false));
+                               }
+                       } catch (InvalidNameException e) {
+                               throw new IllegalStateException("Cannot search users and groups", e);
+                       }
+               }
+               return res;
        }
 
        @Override
@@ -282,7 +319,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
                try {
                        Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
-                       List<DirectoryUser> users = doGetRoles(f);
+                       List<DirectoryUser> users = doGetRoles(getBaseDn(), f, true);
                        collectedUsers.addAll(users);
                } catch (InvalidSyntaxException e) {
                        throw new IllegalArgumentException("Cannot get user with " + key + "=" + value, e);
@@ -394,6 +431,40 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        }
 
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public int getHierarchyChildCount() {
+               return 0;
+       }
+
+       @Override
+       public HierarchyUnit getHierarchyChild(int i) {
+               throw new IllegalArgumentException("No child hierarchy unit available");
+       }
+
+       @Override
+       public int getHierarchyUnitType() {
+               return 0;
+       }
+
+       @Override
+       public String getHierarchyUnitName() {
+               String name = baseDn.getRdn(baseDn.size() - 1).getValue().toString();
+               // TODO check ou, o, etc.
+               return name;
+       }
+
+       @Override
+       public List<? extends Role> getRoles(String filter, boolean deep) {
+               try {
+                       return getRoles(getBaseDn(), filter, deep);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot filter " + filter + " " + getBaseDn(), e);
+               }
+       }
+
        // GETTERS
        protected String getMemberAttributeId() {
                return memberAttributeId;
@@ -512,6 +583,16 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return scoped;
        }
 
+       @Override
+       public int hashCode() {
+               return baseDn.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "User Directory " + baseDn.toString();
+       }
+
        /*
         * STATIC UTILITIES
         */
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java b/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java
new file mode 100644 (file)
index 0000000..c01ea15
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.List;
+
+import org.osgi.service.useradmin.Role;
+
+/** A unit within the high-level organisational structure of a directory. */
+public interface HierarchyUnit {
+       final static int UNKOWN = 0;
+       final static int ORGANIZATION = 1;
+       final static int OU = 2;
+
+       String getHierarchyUnitName();
+
+       int getHierarchyChildCount();
+
+       HierarchyUnit getHierarchyChild(int i);
+
+       int getHierarchyUnitType();
+
+       String getBasePath();
+
+       List<? extends Role> getRoles(String filter, boolean deep);
+}
index f8396085bd44849294254466956b417c3e16d8b0..623ca2a929b7ad8c0b8e981576cfe9a52bb99e33 100644 (file)
@@ -95,16 +95,17 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        }
 
        @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
+       protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
                ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
                try {
                        String searchFilter = f != null ? f.toString()
                                        : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
                                                        + getGroupObjectClass() + "))";
                        SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+                       // FIXME make one level consistent with deep
+                       searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
 
-                       LdapName searchBase = getBaseDn();
+                       // LdapName searchBase = getBaseDn();
                        NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
 
                        results: while (results.hasMoreElements()) {
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java
new file mode 100644 (file)
index 0000000..5cf52b9
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+
+/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
+class LdifHierarchyUnit implements HierarchyUnit {
+       private final AbstractUserDirectory directory;
+
+       private final LdapName dn;
+       private final int type;
+       private final Attributes attributes;
+
+       HierarchyUnit parent;
+       List<HierarchyUnit> children = new ArrayList<>();
+
+       LdifHierarchyUnit(AbstractUserDirectory directory, LdapName dn, int type, Attributes attributes) {
+               Objects.requireNonNull(directory);
+               Objects.requireNonNull(dn);
+
+               this.directory = directory;
+               this.dn = dn;
+               this.type = type;
+               this.attributes = attributes;
+       }
+
+       @Override
+       public int getHierarchyChildCount() {
+               return children.size();
+       }
+
+       @Override
+       public HierarchyUnit getHierarchyChild(int i) {
+               return children.get(i);
+       }
+
+       @Override
+       public int getHierarchyUnitType() {
+               return type;
+       }
+
+       @Override
+       public String getHierarchyUnitName() {
+               String name = dn.getRdn(dn.size() - 1).getValue().toString();
+               // TODO check ou, o, etc.
+               return name;
+       }
+
+       public Attributes getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       public String getBasePath() {
+               return dn.toString();
+       }
+
+       @Override
+       public List<? extends Role> getRoles(String filter, boolean deep) {
+               try {
+                       return directory.getRoles(dn, filter, deep);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
+               }
+       }
+
+       @Override
+       public int hashCode() {
+               return dn.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof LdifHierarchyUnit))
+                       return false;
+               return ((LdifHierarchyUnit) obj).dn.equals(dn);
+       }
+
+       @Override
+       public String toString() {
+               return "Hierarchy Unit " + dn.toString();
+       }
+
+}
index 8b1206a72b1655b38c6f111e8bd335dc954222ff..f9163d7e2af20dfd43caa759443241ee464b74c3 100644 (file)
@@ -16,6 +16,7 @@ import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -25,6 +26,7 @@ import javax.naming.NamingEnumeration;
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.util.naming.LdapObjs;
 import org.argeo.util.naming.LdifParser;
 import org.argeo.util.naming.LdifWriter;
 import org.osgi.framework.Filter;
@@ -33,8 +35,11 @@ import org.osgi.service.useradmin.User;
 
 /** A user admin based on a LDIF files. */
 public class LdifUserAdmin extends AbstractUserDirectory {
-       private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
-       private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
+       private SortedMap<LdapName, DirectoryUser> users = new TreeMap<>();
+       private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<>();
+
+       private SortedMap<LdapName, LdifHierarchyUnit> hierarchy = new TreeMap<>();
+       private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
 
        public LdifUserAdmin(String uri, String baseDn) {
                this(fromUri(uri, baseDn), false);
@@ -113,6 +118,8 @@ public class LdifUserAdmin extends AbstractUserDirectory {
        public void save(OutputStream out) throws IOException {
                try {
                        LdifWriter ldifWriter = new LdifWriter(out);
+                       for (LdapName name : hierarchy.keySet())
+                               ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
                        for (LdapName name : groups.keySet())
                                ldifWriter.writeEntry(name, groups.get(name).getAttributes());
                        for (LdapName name : users.keySet())
@@ -126,6 +133,7 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                try {
                        users.clear();
                        groups.clear();
+                       hierarchy.clear();
 
                        LdifParser ldifParser = new LdifParser();
                        SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
@@ -153,9 +161,34 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                                        } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
                                                groups.put(key, new LdifGroup(this, key, attributes));
                                                break objectClasses;
+                                       } else if (objectClass.equalsIgnoreCase(LdapObjs.organization.name())) {
+                                               // we only consider organizations which are not groups
+                                               hierarchy.put(key, new LdifHierarchyUnit(this, key, HierarchyUnit.ORGANIZATION, attributes));
+                                               break objectClasses;
+                                       } else if (objectClass.equalsIgnoreCase(LdapObjs.organizationalUnit.name())) {
+                                               String name = key.getRdn(key.size() - 1).toString();
+                                               if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
+                                                       break objectClasses; // skip
+                                               // TODO skip if it does not contain groups or users
+                                               hierarchy.put(key, new LdifHierarchyUnit(this, key, HierarchyUnit.OU, attributes));
+                                               break objectClasses;
                                        }
                                }
                        }
+
+                       // link hierarchy
+                       hierachyUnits: for (LdapName dn : hierarchy.keySet()) {
+                               LdifHierarchyUnit unit = hierarchy.get(dn);
+                               LdapName parentDn = (LdapName) dn.getPrefix(dn.size() - 1);
+                               LdifHierarchyUnit parent = hierarchy.get(parentDn);
+                               if (parent == null) {
+                                       rootHierarchyUnits.add(unit);
+                                       unit.parent = this;
+                                       continue hierachyUnits;
+                               }
+                               parent.children.add(unit);
+                               unit.parent = parent;
+                       }
                } catch (Exception e) {
                        throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
                }
@@ -168,6 +201,10 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                groups = null;
        }
 
+       /*
+        * USER ADMIN
+        */
+
        @Override
        protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
                if (groups.containsKey(key))
@@ -182,21 +219,35 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                return users.containsKey(dn) || groups.containsKey(dn);
        }
 
-       protected List<DirectoryUser> doGetRoles(Filter f) {
+       @Override
+       protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
+               Objects.requireNonNull(searchBase);
                ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
-               if (f == null) {
+               if (f == null && deep && getBaseDn().equals(searchBase)) {
                        res.addAll(users.values());
                        res.addAll(groups.values());
                } else {
-                       for (DirectoryUser user : users.values()) {
-                               if (f.match(user.getProperties()))
+                       filterRoles(users, searchBase, f, deep, res);
+                       filterRoles(groups, searchBase, f, deep, res);
+               }
+               return res;
+       }
+
+       private void filterRoles(SortedMap<LdapName, ? extends DirectoryUser> map, LdapName searchBase, Filter f,
+                       boolean deep, List<DirectoryUser> res) {
+               // TODO reduce map with search base ?
+               roles: for (DirectoryUser user : map.values()) {
+                       LdapName dn = user.getDn();
+                       if (dn.startsWith(searchBase)) {
+                               if (!deep && dn.size() != (searchBase.size() + 1))
+                                       continue roles;
+                               if (f == null)
+                                       res.add(user);
+                               else if (f.match(user.getProperties()))
                                        res.add(user);
                        }
-                       for (DirectoryUser group : groups.values())
-                               if (f.match(group.getProperties()))
-                                       res.add(group);
                }
-               return res;
+
        }
 
        @Override
@@ -257,4 +308,18 @@ public class LdifUserAdmin extends AbstractUserDirectory {
                init();
        }
 
+       @Override
+       public int getHierarchyChildCount() {
+               return rootHierarchyUnits.size();
+       }
+
+       @Override
+       public HierarchyUnit getHierarchyChild(int i) {
+               return rootHierarchyUnits.get(i);
+       }
+
+       /*
+        * HIERARCHY
+        */
+
 }
index dd16e1a3be30a9f048455b8cdca58a01fe2d6288..68f2eabcd142cbfd181e80c05a084171a922465d 100644 (file)
@@ -51,7 +51,7 @@ public class OsUserDirectory extends AbstractUserDirectory {
        }
 
        @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
+       protected List<DirectoryUser> doGetRoles(LdapName searchBase, Filter f, boolean deep) {
                List<DirectoryUser> res = new ArrayList<>();
                if (f == null || f.match(osUser.getProperties()))
                        res.add(osUser);
index 7d773a9e72283aed73fabe695314e1cd2da12a4f..5a69f1a169b00177738a65019f0f68d275749fbc 100644 (file)
@@ -5,7 +5,7 @@ import java.util.Optional;
 import org.argeo.osgi.transaction.WorkControl;
 
 /** Information about a user directory. */
-public interface UserDirectory {
+public interface UserDirectory extends HierarchyUnit {
        /**
         * The base of the hierarchy defined by this directory. This could typically be
         * an LDAP base DN.
@@ -31,7 +31,7 @@ public interface UserDirectory {
        String getGroupBase();
 
        Optional<String> getRealm();
-
+       
        @Deprecated
        void setTransactionControl(WorkControl transactionControl);
 }
index 2eb783527f790b8cac6802e58ccb1ac94f8c4db2..2236870f6e74ab2144fdd9ccf3b827a8b222a923 100644 (file)
@@ -269,6 +269,8 @@ public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationL
        @Override
        public CmsSession getCmsSession() {
                CmsSession cmsSession = cmsWebApp.getCmsApp().getCmsContext().getCmsSession(getSubject());
+               if (cmsSession == null)
+                       throw new IllegalStateException("No CMS session available for " + getSubject());
                return cmsSession;
        }