From 285c23f26c4d634cd139d393ebcb708187d5e960 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Thu, 18 Aug 2022 09:26:10 +0200 Subject: [PATCH] Improve user management --- .../src/org/argeo/cms/CmsUserManager.java | 18 ++- .../src/org/argeo/cms/auth/CmsRole.java | 2 + .../cms/internal/auth/CmsUserManagerImpl.java | 142 +++++++++++++++--- .../cms/internal/runtime/CmsUserAdmin.java | 4 +- .../osgi/useradmin/AggregatingUserAdmin.java | 4 +- .../osgi/useradmin/DirectoryUserAdmin.java | 18 +-- .../org/argeo/osgi/useradmin/LdifGroup.java | 5 +- .../org/argeo/osgi/useradmin/LdifUser.java | 5 +- .../argeo/osgi/useradmin/OsUserDirectory.java | 7 +- .../org/argeo/util/directory/Directory.java | 6 - .../argeo/util/directory/HierarchyUnit.java | 6 +- .../directory/ldap/AbstractLdapDirectory.java | 25 +-- .../ldap/AbstractLdapDirectoryDao.java | 8 +- .../util/directory/ldap/DefaultLdapEntry.java | 28 ++-- .../argeo/util/directory/ldap/LdapDao.java | 29 ++-- .../util/directory/ldap/LdapDirectoryDao.java | 4 +- .../argeo/util/directory/ldap/LdapEntry.java | 26 +++- .../directory/ldap/LdapHierarchyUnit.java | 9 +- .../util/directory/ldap/LdapNameUtils.java | 4 + .../argeo/util/directory/ldap/LdifDao.java | 19 +-- .../transaction/WorkingCopyXaResource.java | 7 +- .../argeo/cms/e4/users/UserAdminWrapper.java | 2 +- 22 files changed, 259 insertions(+), 119 deletions(-) diff --git a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java index 040138878..4017c7ebf 100644 --- a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java +++ b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java @@ -7,9 +7,11 @@ import java.util.Set; import javax.security.auth.Subject; +import org.argeo.cms.auth.SystemRole; import org.argeo.osgi.useradmin.UserDirectory; import org.argeo.util.directory.HierarchyUnit; import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.User; @@ -55,9 +57,21 @@ public interface CmsUserManager { /* * EDITION */ - /** Creates a new user.*/ + /** Creates a new user. */ User createUser(String username, Map properties, Map credentials); + /** Creates a group. */ + Group getOrCreateGroup(HierarchyUnit groups, String commonName); + + /** Creates a new system role. */ + Group getOrCreateSystemRole(HierarchyUnit roles, SystemRole systemRole); + + /** Add additional object classes to this role. */ + void addObjectClasses(Role role, Set objectClasses, Map additionalProperties); + + /** Add a member to this group. */ + void addMember(Group group, Role role); + /* MISCELLANEOUS */ /** Returns the dn of a role given its local ID */ String buildDefaultDN(String localId, int type); @@ -92,5 +106,5 @@ public interface CmsUserManager { UserDirectory getDirectory(Role role); /** Create a new hierarchy unit. Does nothing if it already exists. */ - HierarchyUnit createHierarchyUnit(UserDirectory directory, String path); + HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path); } \ No newline at end of file diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java index af64508f4..6aa1c1c25 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java @@ -5,6 +5,7 @@ import javax.xml.namespace.QName; import org.argeo.api.acr.ContentName; import org.argeo.api.acr.CrName; +/** Standard CMS system roles. */ public enum CmsRole implements SystemRole { userAdmin, // groupAdmin; @@ -17,6 +18,7 @@ public enum CmsRole implements SystemRole { name = new ContentName(CrName.ROLE_NAMESPACE_URI, QUALIFIER + name()); } + @Override public QName getName() { return name; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java index 36ae6e6d6..0bf9a211b 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java @@ -23,16 +23,19 @@ import javax.naming.InvalidNameException; import javax.naming.ldap.LdapName; import javax.security.auth.Subject; +import org.argeo.api.acr.NamespaceUtils; import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; import org.argeo.cms.CmsUserManager; import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.SystemRole; import org.argeo.cms.auth.UserAdminUtils; import org.argeo.osgi.useradmin.AggregatingUserAdmin; import org.argeo.osgi.useradmin.TokenUtils; import org.argeo.osgi.useradmin.UserDirectory; import org.argeo.util.directory.DirectoryConf; import org.argeo.util.directory.HierarchyUnit; +import org.argeo.util.directory.ldap.LdapEntry; import org.argeo.util.directory.ldap.SharedSecret; import org.argeo.util.naming.LdapAttrs; import org.argeo.util.naming.NamingUtils; @@ -237,6 +240,116 @@ public class CmsUserManagerImpl implements CmsUserManager { } } + @Override + public Group getOrCreateGroup(HierarchyUnit groups, String commonName) { + try { + String dn = LdapAttrs.cn.name() + "=" + commonName + "," + groups.getBase(); + Group group = (Group) getUserAdmin().getRole(dn); + if (group != null) + return group; + userTransaction.begin(); + group = (Group) userAdmin.createRole(dn, Role.GROUP); + userTransaction.commit(); + return group; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e); + } + } + + @Override + public Group getOrCreateSystemRole(HierarchyUnit roles, SystemRole systemRole) { + try { + String dn = LdapAttrs.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole.getName()) + "," + + roles.getBase(); + Group group = (Group) getUserAdmin().getRole(dn); + if (group != null) + return group; + userTransaction.begin(); + group = (Group) userAdmin.createRole(dn, Role.GROUP); + userTransaction.commit(); + return group; + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Could not roll back", e1); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e); + } + } + + @Override + public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) { + HierarchyUnit hi = directory.getHierarchyUnit(path); + if (hi != null) + return hi; + try { + userTransaction.begin(); + HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path); + userTransaction.commit(); + return hierarchyUnit; + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1); + } + } + + @Override + public void addObjectClasses(Role role, Set objectClasses, Map additionalProperties) { + try { + userTransaction.begin(); + LdapEntry.addObjectClasses(role.getProperties(), objectClasses); + for (String key : additionalProperties.keySet()) { + role.getProperties().put(key, additionalProperties.get(key)); + } + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1); + } + } + + @Override + public void addMember(Group group, Role role) { + try { + userTransaction.begin(); + group.addMember(role); + userTransaction.commit(); + } catch (Exception e1) { + try { + if (!userTransaction.isNoTransactionStatus()) + userTransaction.rollback(); + } catch (Exception e2) { + if (log.isTraceEnabled()) + log.trace("Cannot rollback transaction", e2); + } + throw new RuntimeException("Cannot add object classes " + role + " to group " + group, e1); + } + } + @Override public String getDefaultDomainName() { Map dns = getKnownBaseDns(true); @@ -251,7 +364,7 @@ public class CmsUserManagerImpl implements CmsUserManager { Map dns = new HashMap(); for (UserDirectory userDirectory : userDirectories) { Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getContext(); + String baseDn = userDirectory.getBase(); if (onlyWritable && readOnly) continue; @@ -266,7 +379,7 @@ public class CmsUserManagerImpl implements CmsUserManager { } public Set getUserDirectories() { - TreeSet res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext())); + TreeSet res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase())); res.addAll(userDirectories); return res; } @@ -464,8 +577,8 @@ public class CmsUserManagerImpl implements CmsUserManager { String name = user.getName(); NavigableMap possible = new TreeMap<>(); for (UserDirectory userDirectory : userDirectories) { - if (name.endsWith(userDirectory.getContext())) { - possible.put(userDirectory.getContext(), userDirectory); + if (name.endsWith(userDirectory.getBase())) { + possible.put(userDirectory.getBase(), userDirectory); } } if (possible.size() == 0) @@ -473,27 +586,6 @@ public class CmsUserManagerImpl implements CmsUserManager { return possible.lastEntry().getValue(); } - public HierarchyUnit createHierarchyUnit(UserDirectory directory, String path) { - HierarchyUnit hi = directory.getHierarchyUnit(path); - if (hi != null) - return hi; - try { - userTransaction.begin(); - HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path); - userTransaction.commit(); - return hierarchyUnit; - } catch (Exception e1) { - try { - if (!userTransaction.isNoTransactionStatus()) - userTransaction.rollback(); - } catch (Exception e2) { - if (log.isTraceEnabled()) - log.trace("Cannot rollback transaction", e2); - } - throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1); - } - } - // public User createUserFromPerson(Node person) { // String email = JcrUtils.get(person, LdapAttrs.mail.property()); // String dn = buildDefaultDN(email, Role.USER); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java index ecb7b8c1c..daec2ea76 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -219,14 +219,14 @@ public class CmsUserAdmin extends AggregatingUserAdmin { // } else { // throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); // } - String basePath = userDirectory.getContext(); + String basePath = userDirectory.getBase(); addUserDirectory(userDirectory); if (isSystemRolesBaseDn(basePath)) { addStandardSystemRoles(); } if (log.isDebugEnabled()) { - log.debug("User directory " + userDirectory.getContext() + (u != null ? " [" + u.getScheme() + "]" : "") + log.debug("User directory " + userDirectory.getBase() + (u != null ? " [" + u.getScheme() + "]" : "") + " enabled." + (realm != null ? " " + realm + " realm." : "")); } return userDirectory; diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index ef253800c..179099bad 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -174,7 +174,7 @@ public class AggregatingUserAdmin implements UserAdmin { if (!(ud instanceof DirectoryUserAdmin)) throw new IllegalArgumentException("Only " + DirectoryUserAdmin.class.getName() + " is supported"); DirectoryUserAdmin userDirectory = (DirectoryUserAdmin) ud; - String basePath = userDirectory.getContext(); + String basePath = userDirectory.getBase(); if (isSystemRolesBaseDn(basePath)) { this.systemRoles = userDirectory; systemRoles.setExternalRoles(this); @@ -303,7 +303,7 @@ public class AggregatingUserAdmin implements UserAdmin { } public Set getUserDirectories() { - TreeSet res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext())); + TreeSet res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase())); res.addAll(businessRoles.values()); res.add(systemRoles); return res; diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java index 003aad11d..8ed23ad2e 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java @@ -346,13 +346,13 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm return getRole(name); } else { wc.getModifiedData().put(dn, attrs); - LdapEntry newRole = newRole(dn, type, attrs); + LdapEntry newRole = doCreateRole(dn, type, attrs); wc.getNewData().put(dn, newRole); return (Role) newRole; } } - protected LdapEntry newRole(LdapName dn, int type, Attributes attrs) { + private LdapEntry doCreateRole(LdapName dn, int type, Attributes attrs) { LdapEntry newRole; BasicAttribute objClass = new BasicAttribute(objectClass.name()); if (type == Role.USER) { @@ -367,14 +367,14 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm objClass.add(top.name()); objClass.add(extensibleObject.name()); attrs.put(objClass); - newRole = newUser(dn, attrs); + newRole = newUser(dn); } else if (type == Role.GROUP) { String groupObjClass = getGroupObjectClass(); objClass.add(groupObjClass); // objClass.add(LdifName.extensibleObject.name()); objClass.add(top.name()); attrs.put(objClass); - newRole = newGroup(dn, attrs); + newRole = newGroup(dn); } else throw new IllegalArgumentException("Unsupported type " + type); return newRole; @@ -416,7 +416,7 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm @Override public Iterable getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) { - LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getContext()); + LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getBase()); try { return getRoles(dn, filter, deep); } catch (InvalidSyntaxException e) { @@ -427,13 +427,13 @@ public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdm /* * ROLES CREATION */ - protected LdapEntry newUser(LdapName name, Attributes attrs) { + protected LdapEntry newUser(LdapName name) { // TODO support devices, applications, etc. - return new LdifUser(this, name, attrs); + return new LdifUser(this, name); } - protected LdapEntry newGroup(LdapName name, Attributes attrs) { - return new LdifGroup(this, name, attrs); + protected LdapEntry newGroup(LdapName name) { + return new LdifGroup(this, name); } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java index 2453fcb23..bdf34aa91 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java @@ -5,7 +5,6 @@ import java.util.List; import javax.naming.InvalidNameException; import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import org.argeo.util.directory.ldap.AbstractLdapDirectory; @@ -15,8 +14,8 @@ import org.osgi.service.useradmin.Role; class LdifGroup extends LdifUser implements DirectoryGroup { private final String memberAttributeId; - LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) { - super(userAdmin, dn, attributes); + LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn) { + super(userAdmin, dn); memberAttributeId = userAdmin.getMemberAttributeId(); } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java index a40de1c83..0b07c7565 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java @@ -1,6 +1,5 @@ package org.argeo.osgi.useradmin; -import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import org.argeo.util.directory.ldap.AbstractLdapDirectory; @@ -8,8 +7,8 @@ import org.argeo.util.directory.ldap.DefaultLdapEntry; /** Directory user implementation */ class LdifUser extends DefaultLdapEntry implements DirectoryUser { - LdifUser(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) { - super(userAdmin, dn, attributes); + LdifUser(AbstractLdapDirectory userAdmin, LdapName dn) { + super(userAdmin, dn); } @Override diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java index 466563a4d..e1ad5edf5 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java @@ -6,7 +6,6 @@ import java.util.List; import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttributes; import javax.naming.ldap.LdapName; import org.argeo.util.directory.HierarchyUnit; @@ -27,9 +26,9 @@ public class OsUserDirectory extends AbstractLdapDirectoryDao { try { osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + directory.getUserBaseRdn() + "," + directory.getBaseDn()); - Attributes attributes = new BasicAttributes(); - attributes.put(LdapAttrs.uid.name(), osUsername); - osUser = newUser(osUserDn, attributes); +// Attributes attributes = new BasicAttributes(); +// attributes.put(LdapAttrs.uid.name(), osUsername); + osUser = newUser(osUserDn); } catch (NamingException e) { throw new IllegalStateException("Cannot create system user", e); } diff --git a/org.argeo.util/src/org/argeo/util/directory/Directory.java b/org.argeo.util/src/org/argeo/util/directory/Directory.java index 11e8e6285..9e564ced9 100644 --- a/org.argeo.util/src/org/argeo/util/directory/Directory.java +++ b/org.argeo.util/src/org/argeo/util/directory/Directory.java @@ -7,12 +7,6 @@ import org.argeo.util.transaction.WorkControl; /** An information directory (typicylly LDAP). */ public interface Directory extends HierarchyUnit { - /** - * The base of the hierarchy defined by this directory. This could typically be - * an LDAP base DN. - */ - String getContext(); - String getName(); /** Whether this directory is read only. */ diff --git a/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java b/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java index 2673f16f0..d35557784 100644 --- a/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java +++ b/org.argeo.util/src/org/argeo/util/directory/HierarchyUnit.java @@ -12,7 +12,11 @@ public interface HierarchyUnit { boolean isFunctional(); - String getContext(); + /** + * The base of this organisational unit within the hierarchy. This would + * typically be an LDAP base DN. + */ + String getBase(); Directory getDirectory(); diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java index 27c2b9531..54d9776b5 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java @@ -161,9 +161,9 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv /* * CREATION */ - protected abstract LdapEntry newUser(LdapName name, Attributes attrs); + protected abstract LdapEntry newUser(LdapName name); - protected abstract LdapEntry newGroup(LdapName name, Attributes attrs); + protected abstract LdapEntry newGroup(LdapName name); /* * EDITION @@ -255,11 +255,11 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv } else { // user doesn't have the right to retrieve role, but we know it exists // otherwise memberOf would not work - Attributes a = new BasicAttributes(); - a.put(LdapNameUtils.getLastRdn(groupDn).getType(), - LdapNameUtils.getLastRdn(groupDn).getValue()); - a.put(LdapAttrs.objectClass.name(), LdapObjs.groupOfNames.name()); - group = newGroup(groupDn, a); +// Attributes a = new BasicAttributes(); +// a.put(LdapNameUtils.getLastRdn(groupDn).getType(), +// LdapNameUtils.getLastRdn(groupDn).getValue()); +// a.put(LdapAttrs.objectClass.name(), LdapObjs.groupOfNames.name()); + group = newGroup(groupDn); allRoles.add(group); } } @@ -267,10 +267,13 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv throw new IllegalStateException("Cannot get memberOf groups for " + user, e); } } else { - for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) { - // TODO check for loops + directGroups: for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) { LdapEntry group = doGetRole(groupDn); if (group != null) { + if (allRoles.contains(group)) { + // important in order to avoi loops + continue directGroups; + } allRoles.add(group); collectGroups(group, allRoles); } @@ -326,7 +329,7 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv // TODO deal with multiple attr RDN attrs.put(nameRdn.getType(), nameRdn.getValue()); wc.getModifiedData().put(dn, attrs); - LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn, attrs); + LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn); wc.getNewData().put(dn, newHierarchyUnit); return newHierarchyUnit; } @@ -336,7 +339,7 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv */ @Override - public String getContext() { + public String getBase() { return getBaseDn().toString(); } diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java index 8c87170fe..8e132887d 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java @@ -22,13 +22,13 @@ public abstract class AbstractLdapDirectoryDao implements LdapDirectoryDao { } @Override - public LdapEntry newUser(LdapName name, Attributes attrs) { - return getDirectory().newUser(name, attrs); + public LdapEntry newUser(LdapName name) { + return getDirectory().newUser(name); } @Override - public LdapEntry newGroup(LdapName name, Attributes attrs) { - return getDirectory().newGroup(name, attrs); + public LdapEntry newGroup(LdapName name) { + return getDirectory().newGroup(name); } } diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java b/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java index 14a7eb87c..c4d20feba 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/DefaultLdapEntry.java @@ -32,18 +32,18 @@ public class DefaultLdapEntry implements LdapEntry { private final LdapName dn; - private Attributes publishedAttributes; +// private Attributes publishedAttributes; // Temporarily expose the fields protected AttributeDictionary properties; protected AttributeDictionary credentials; - protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) { + protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) { Objects.requireNonNull(directory); Objects.requireNonNull(dn); this.directory = directory; this.dn = dn; - this.publishedAttributes = attributes; +// this.publishedAttributes = attributes; // properties = new AttributeDictionary(false); // credentials = new AttributeDictionary(true); } @@ -54,10 +54,10 @@ public class DefaultLdapEntry implements LdapEntry { } public synchronized Attributes getAttributes() { - // lazy loading - if (publishedAttributes == null) - publishedAttributes = getDirectory().getDirectoryDao().doGetAttributes(dn); - return isEditing() ? getModifiedAttributes() : publishedAttributes; +// // lazy loading +// if (publishedAttributes == null) +// publishedAttributes = getDirectory().getDirectoryDao().doGetAttributes(dn); + return isEditing() ? getModifiedAttributes() : getDirectory().getDirectoryDao().doGetAttributes(dn); } @Override @@ -104,7 +104,7 @@ public class DefaultLdapEntry implements LdapEntry { } public synchronized void publishAttributes(Attributes modifiedAttributes) { - publishedAttributes = modifiedAttributes; +// publishedAttributes = modifiedAttributes; } /* @@ -376,6 +376,7 @@ public class DefaultLdapEntry implements LdapEntry { public Object put(String key, Object value) { try { if (key == null) { + // FIXME remove this "feature", a key should be specified // TODO persist to other sources (like PKCS12) char[] password = DirectoryDigestUtils.bytesToChars(value); byte[] hashedPassword = sha1hash(password); @@ -384,6 +385,13 @@ public class DefaultLdapEntry implements LdapEntry { if (key.startsWith("X-")) { return put(LdapAttrs.authPassword.name(), value); } + + // start editing + getDirectory().checkEdit(); + if (!isEditing()) + startEditing(); + + // object classes special case. if (key.equals(LdapAttrs.objectClasses.name())) { Attribute attribute = new BasicAttribute(LdapAttrs.objectClass.name()); String[] objectClasses = value.toString().split("\n"); @@ -399,10 +407,6 @@ public class DefaultLdapEntry implements LdapEntry { return null; } - getDirectory().checkEdit(); - if (!isEditing()) - startEditing(); - if (!(value instanceof String || value instanceof byte[])) throw new IllegalArgumentException("Value must be String or byte[]"); diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java index b1c0c9849..e5ce0a4c1 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java @@ -93,23 +93,23 @@ public class LdapDao extends AbstractLdapDirectoryDao { attrs.put(LdapAttrs.objectClass.name(), LdapObjs.top.name()); attrs.put(LdapAttrs.objectClass.name(), getDirectory().getGroupObjectClass()); } - res = newGroup(name, attrs); + res = newGroup(name); } else if (getDirectory().getSystemRoleBaseRdn().equals(technicalRdn)) { if (attrs.size() == 0) {// exists but not accessible attrs = new BasicAttributes(); attrs.put(LdapAttrs.objectClass.name(), LdapObjs.top.name()); attrs.put(LdapAttrs.objectClass.name(), getDirectory().getGroupObjectClass()); } - res = newGroup(name, attrs); + res = newGroup(name); } else if (getDirectory().getUserBaseRdn().equals(technicalRdn)) { if (attrs.size() == 0) {// exists but not accessible attrs = new BasicAttributes(); attrs.put(LdapAttrs.objectClass.name(), LdapObjs.top.name()); attrs.put(LdapAttrs.objectClass.name(), getDirectory().getUserObjectClass()); } - res = newUser(name, attrs); + res = newUser(name); } else { - res = new DefaultLdapEntry(getDirectory(), name, attrs); + res = new DefaultLdapEntry(getDirectory(), name); } return res; } catch (NameNotFoundException e) { @@ -146,9 +146,11 @@ public class LdapDao extends AbstractLdapDirectoryDao { ArrayList res = new ArrayList<>(); try { String searchFilter = f != null ? f.toString() - : "(|(" + objectClass + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass + "=" - + getDirectory().getGroupObjectClass() + "))"; + : "(|(" + objectClass.name() + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass.name() + + "=" + getDirectory().getGroupObjectClass() + "))"; SearchControls searchControls = new SearchControls(); + // only attribute needed is objectClass + searchControls.setReturningAttributes(new String[] { objectClass.name() }); // FIXME make one level consistent with deep searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE); @@ -163,10 +165,10 @@ public class LdapDao extends AbstractLdapDirectoryDao { LdapEntry role; if (objectClassAttr.contains(getDirectory().getGroupObjectClass()) || objectClassAttr.contains(getDirectory().getGroupObjectClass().toLowerCase())) - role = newGroup(dn, attrs); + role = newGroup(dn); else if (objectClassAttr.contains(getDirectory().getUserObjectClass()) || objectClassAttr.contains(getDirectory().getUserObjectClass().toLowerCase())) - role = newUser(dn, attrs); + role = newUser(dn); else { // log.warn("Unsupported LDAP type for " + searchResult.getName()); continue results; @@ -248,14 +250,16 @@ public class LdapDao extends AbstractLdapDirectoryDao { SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE); + // no attributes needed + searchControls.setReturningAttributes(new String[0]); NamingEnumeration results = ldapConnection.search(searchBase, searchFilter, searchControls); while (results.hasMoreElements()) { SearchResult searchResult = (SearchResult) results.nextElement(); LdapName dn = toDn(searchBase, searchResult); - Attributes attrs = searchResult.getAttributes(); - LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(getDirectory(), dn, attrs); +// Attributes attrs = searchResult.getAttributes(); + LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(getDirectory(), dn); if (functionalOnly) { if (hierarchyUnit.isFunctional()) res.add(hierarchyUnit); @@ -276,8 +280,9 @@ public class LdapDao extends AbstractLdapDirectoryDao { return getDirectory(); if (!dn.startsWith(getDirectory().getBaseDn())) throw new IllegalArgumentException(dn + " does not start with base DN " + getDirectory().getBaseDn()); - Attributes attrs = ldapConnection.getAttributes(dn); - return new LdapHierarchyUnit(getDirectory(), dn, attrs); + if (!ldapConnection.entryExists(dn)) + return null; + return new LdapHierarchyUnit(getDirectory(), dn); } catch (NameNotFoundException e) { return null; } catch (NamingException e) { diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java index 81a86fd05..a4e65998c 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java @@ -24,9 +24,9 @@ public interface LdapDirectoryDao extends WorkingCopyProcessor getReferences(String attributeId); - - public Dictionary getProperties(); + List getReferences(String attributeId); + + Dictionary getProperties(); - public boolean hasCredential(String key, Object value) ; + boolean hasCredential(String key, Object value); + /* + * UTILITIES + */ + public static void addObjectClasses(Dictionary properties, Set objectClasses) { + String value = properties.get(LdapAttrs.objectClasses.name()).toString(); + Set currentObjectClasses = new TreeSet<>(Arrays.asList(value.toString().split("\n"))); + currentObjectClasses.addAll(objectClasses); + StringJoiner values = new StringJoiner("\n"); + currentObjectClasses.forEach((s) -> values.add(s)); + properties.put(LdapAttrs.objectClasses.name(), values.toString()); + } } diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java index 8579bc5fd..bd12244ea 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java @@ -1,6 +1,5 @@ package org.argeo.util.directory.ldap; -import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; @@ -10,12 +9,12 @@ import org.argeo.util.directory.HierarchyUnit; public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit { private final boolean functional; - public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) { - super(directory, dn, attributes); + public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn) { + super(directory, dn); Rdn rdn = LdapNameUtils.getLastRdn(dn); functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn) - || directory.getSystemRoleBaseRdn().equals(rdn) ); + || directory.getSystemRoleBaseRdn().equals(rdn)); } @Override @@ -41,7 +40,7 @@ public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit } @Override - public String getContext() { + public String getBase() { return getDn().toString(); } diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java index 689ef2329..88d317542 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/LdapNameUtils.java @@ -54,6 +54,10 @@ public class LdapNameUtils { return getLastRdn(dn).toString(); } + public static String getLastRdnValue(String dn) { + return getLastRdnValue(toLdapName(dn)); + } + public static String getLastRdnValue(LdapName dn) { return getLastRdn(dn).getValue().toString(); } diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java b/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java index d74ac166f..27a934377 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java @@ -36,9 +36,10 @@ import org.osgi.service.useradmin.Role; /** A user admin based on a LDIF files. */ public class LdifDao extends AbstractLdapDirectoryDao { private NavigableMap entries = new TreeMap<>(); - private NavigableMap hierarchy = new TreeMap<>(); + private NavigableMap values = new TreeMap<>(); + public LdifDao(AbstractLdapDirectory directory) { super(directory); } @@ -104,6 +105,8 @@ public class LdifDao extends AbstractLdapDirectoryDao { lowerCase.add(id); } + values.put(key, attributes); + // analyse object classes NamingEnumeration objectClasses = attributes.get(objectClass.name()).getAll(); // System.out.println(key); @@ -111,14 +114,14 @@ public class LdifDao extends AbstractLdapDirectoryDao { String objectClass = objectClasses.next().toString(); // System.out.println(" " + objectClass); if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) { - entries.put(key, newUser(key, attributes)); + entries.put(key, newUser(key)); break objectClasses; } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) { - entries.put(key, newGroup(key, attributes)); + entries.put(key, newGroup(key)); break objectClasses; } else if (objectClass.equalsIgnoreCase(LdapObjs.organizationalUnit.name())) { // TODO skip if it does not contain groups or users - hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key, attributes)); + hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key)); break objectClasses; } } @@ -151,11 +154,9 @@ public class LdifDao extends AbstractLdapDirectoryDao { @Override public Attributes doGetAttributes(LdapName name) { - try { - return doGetEntry(name).getAttributes(); - } catch (NameNotFoundException e) { - throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn(), e); - } + if (!values.containsKey(name)) + throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn()); + return values.get(name); } @Override diff --git a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java index ebafd267f..ddb605a19 100644 --- a/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java +++ b/org.argeo.util/src/org/argeo/util/transaction/WorkingCopyXaResource.java @@ -48,8 +48,11 @@ public class WorkingCopyXaResource> implements X } private synchronized void cleanUp(Xid xid) { - wc(xid).cleanUp(); - workingCopies.remove(xid); + WC wc = workingCopies.get(xid); + if (wc != null) { + wc.cleanUp(); + workingCopies.remove(xid); + } editingXid = null; } diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java index d120ae9a2..00b519d5c 100644 --- a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java @@ -91,7 +91,7 @@ public class UserAdminWrapper { Map dns = new HashMap(); for (UserDirectory userDirectory : userDirectories.keySet()) { Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getContext(); + String baseDn = userDirectory.getBase(); if (onlyWritable && readOnly) continue; -- 2.30.2