--- /dev/null
+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());
+ }
+
+}
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());
@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) {
}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public interface ColumnsPart extends DataPart {
+
+}
--- /dev/null
+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);
+}
package org.argeo.cms.ux.widgets;
-public interface HierarchicalPart {
+public interface HierarchicalPart extends ColumnsPart {
}
--- /dev/null
+package org.argeo.cms.ux.widgets;
+
+public interface TabularPart extends ColumnsPart {
+
+}
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;
* 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();
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;
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());
}
}
/*
- * 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();
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import java.util.UUID;
import javax.naming.InvalidNameException;
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));
// 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) {
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);
@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();
// 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
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);
}
+ /*
+ * 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;
return scoped;
}
+ @Override
+ public int hashCode() {
+ return baseDn.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "User Directory " + baseDn.toString();
+ }
+
/*
* STATIC UTILITIES
*/
--- /dev/null
+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);
+}
}
@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()) {
--- /dev/null
+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();
+ }
+
+}
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;
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;
/** 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);
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())
try {
users.clear();
groups.clear();
+ hierarchy.clear();
LdifParser ldifParser = new LdifParser();
SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
} 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);
}
groups = null;
}
+ /*
+ * USER ADMIN
+ */
+
@Override
protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
if (groups.containsKey(key))
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
init();
}
+ @Override
+ public int getHierarchyChildCount() {
+ return rootHierarchyUnits.size();
+ }
+
+ @Override
+ public HierarchyUnit getHierarchyChild(int i) {
+ return rootHierarchyUnits.get(i);
+ }
+
+ /*
+ * HIERARCHY
+ */
+
}
}
@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);
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.
String getGroupBase();
Optional<String> getRealm();
-
+
@Deprecated
void setTransactionControl(WorkControl transactionControl);
}
@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;
}