Separate LDIF and LDAP DAOs
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Jun 2022 11:13:19 +0000 (13:13 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 22 Jun 2022 11:13:19 +0000 (13:13 +0200)
20 files changed:
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java
org.argeo.util/src/org/argeo/util/directory/Directory.java
org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java
org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectoryDao.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapEntry.java
org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/ldap/LdapDirectoryDao.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/directory/ldap/LdapEntry.java
org.argeo.util/src/org/argeo/util/directory/ldap/LdapHierarchyUnit.java
org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java [new file with mode: 0644]

index 64b25c99e80caf71a1ad90c054f2dabba673868c..64e32b16a36a7d90e8645777b5e8727c789ec04b 100644 (file)
@@ -33,10 +33,8 @@ import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.internal.http.client.HttpCredentialProvider;
 import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
+import org.argeo.osgi.useradmin.DirectoryUserAdmin;
 import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.LdapUserAdmin;
-import org.argeo.osgi.useradmin.LdifUserAdmin;
-import org.argeo.osgi.useradmin.OsUserDirectory;
 import org.argeo.osgi.useradmin.UserDirectory;
 import org.argeo.util.directory.DirectoryConf;
 import org.argeo.util.naming.dns.DnsBrowser;
@@ -95,18 +93,18 @@ public class CmsUserAdmin extends AggregatingUserAdmin {
                }
 
                // Create
-               UserDirectory userDirectory;
-               if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
-                               || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
-                       userDirectory = new LdapUserAdmin(properties);
-               } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
-                       userDirectory = new LdifUserAdmin(u, properties);
-               } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
-                       userDirectory = new OsUserDirectory(u, properties);
-                       singleUser = true;
-               } else {
-                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
-               }
+               UserDirectory userDirectory = new DirectoryUserAdmin(u, properties);
+//             if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+//                             || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+//                     userDirectory = new LdapUserAdmin(properties);
+//             } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+//                     userDirectory = new LdifUserAdmin(u, properties);
+//             } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+//                     userDirectory = new OsUserDirectory(u, properties);
+//                     singleUser = true;
+//             } else {
+//                     throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+//             }
                String basePath = userDirectory.getContext();
 
                addUserDirectory(userDirectory);
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
deleted file mode 100644 (file)
index 4b13728..0000000
+++ /dev/null
@@ -1,405 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.extensibleObject;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-import static org.argeo.util.naming.LdapObjs.organizationalPerson;
-import static org.argeo.util.naming.LdapObjs.person;
-import static org.argeo.util.naming.LdapObjs.top;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.util.directory.HierarchyUnit;
-import org.argeo.util.directory.ldap.AbstractLdapDirectory;
-import org.argeo.util.directory.ldap.LdapEntry;
-import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
-import org.argeo.util.directory.ldap.LdapNameUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Base class for a {@link UserDirectory}. */
-abstract class AbstractUserDirectory extends AbstractLdapDirectory implements UserAdmin, UserDirectory {
-
-       private UserAdmin externalRoles;
-       // private List<String> indexedUserProperties = Arrays
-       // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
-       // LdapAttrs.cn.name() });
-
-       // Transaction
-//     private TransactionManager transactionManager;
-       AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
-               super(uriArg, props, scoped);
-       }
-
-       /*
-        * ABSTRACT METHODS
-        */
-
-       protected abstract AbstractLdapDirectory scope(User user);
-
-       /** Returns the groups this user is a direct member of. */
-       protected abstract List<LdapName> getDirectGroups(LdapName dn);
-
-       /*
-        * INITIALIZATION
-        */
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       @Override
-       public String getRolePath(Role role) {
-               return nameToRelativePath(((DirectoryUser) role).getDn());
-       }
-
-       @Override
-       public String getRoleSimpleName(Role role) {
-               LdapName dn = LdapNameUtils.toLdapName(role.getName());
-               String name = LdapNameUtils.getLastRdnValue(dn);
-               return name;
-       }
-
-       @Override
-       public Role getRoleByPath(String path) {
-               return doGetRole(pathToName(path));
-       }
-
-       protected List<Role> getAllRoles(DirectoryUser user) {
-               List<Role> allRoles = new ArrayList<Role>();
-               if (user != null) {
-                       collectRoles(user, allRoles);
-                       allRoles.add(user);
-               } else
-                       collectAnonymousRoles(allRoles);
-               return allRoles;
-       }
-
-       private void collectRoles(DirectoryUser user, List<Role> allRoles) {
-               Attributes attrs = user.getAttributes();
-               // TODO centralize attribute name
-               Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
-               // if user belongs to this directory, we only check meberOf
-               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
-                       try {
-                               NamingEnumeration<?> values = memberOf.getAll();
-                               while (values.hasMore()) {
-                                       Object value = values.next();
-                                       LdapName groupDn = new LdapName(value.toString());
-                                       DirectoryUser group = doGetRole(groupDn);
-                                       if (group != null)
-                                               allRoles.add(group);
-                               }
-                       } catch (NamingException e) {
-                               throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
-                       }
-               } else {
-                       for (LdapName groupDn : getDirectGroups(user.getDn())) {
-                               // TODO check for loops
-                               DirectoryUser group = doGetRole(groupDn);
-                               if (group != null) {
-                                       allRoles.add(group);
-                                       collectRoles(group, allRoles);
-                               }
-                       }
-               }
-       }
-
-       private void collectAnonymousRoles(List<Role> allRoles) {
-               // TODO gather anonymous roles
-       }
-
-       // USER ADMIN
-       @Override
-       public Role getRole(String name) {
-               return doGetRole(toLdapName(name));
-       }
-
-       protected DirectoryUser doGetRole(LdapName dn) {
-               LdapEntryWorkingCopy wc = getWorkingCopy();
-               DirectoryUser user;
-               try {
-                       user = (DirectoryUser) daoGetEntry(dn);
-               } catch (NameNotFoundException e) {
-                       user = null;
-               }
-               if (wc != null) {
-                       if (user == null && wc.getNewData().containsKey(dn))
-                               user = (DirectoryUser) wc.getNewData().get(dn);
-                       else if (wc.getDeletedData().containsKey(dn))
-                               user = null;
-               }
-               return user;
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               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 {
-               LdapEntryWorkingCopy wc = getWorkingCopy();
-               Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
-               List<LdapEntry> searchRes = doGetEntries(searchBase, f, deep);
-               List<DirectoryUser> res = new ArrayList<>();
-               for (LdapEntry entry : searchRes)
-                       res.add((DirectoryUser) entry);
-               if (wc != null) {
-                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
-                               DirectoryUser user = (DirectoryUser) it.next();
-                               LdapName dn = user.getDn();
-                               if (wc.getDeletedData().containsKey(dn))
-                                       it.remove();
-                       }
-                       for (LdapEntry ldapEntry : wc.getNewData().values()) {
-                               DirectoryUser user = (DirectoryUser) ldapEntry;
-                               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
-               }
-
-               // 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
-       public User getUser(String key, String value) {
-               // TODO check value null or empty
-               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
-               if (key != null) {
-                       doGetUser(key, value, collectedUsers);
-               } else {
-                       throw new IllegalArgumentException("Key cannot be null");
-               }
-
-               if (collectedUsers.size() == 1) {
-                       return collectedUsers.get(0);
-               } else if (collectedUsers.size() > 1) {
-                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
-                       // "") + value);
-               }
-               return null;
-       }
-
-       protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
-               try {
-                       Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
-                       List<LdapEntry> users = doGetEntries(getBaseDn(), f, true);
-                       for (LdapEntry entry : users)
-                               collectedUsers.add((DirectoryUser) entry);
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get user with " + key + "=" + value, e);
-               }
-       }
-
-       @Override
-       public Authorization getAuthorization(User user) {
-               if (user == null || user instanceof DirectoryUser) {
-                       return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
-               } else {
-                       // bind
-                       AbstractUserDirectory scopedUserAdmin = (AbstractUserDirectory) scope(user);
-                       try {
-                               DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
-                               if (directoryUser == null)
-                                       throw new IllegalStateException("No scoped user found for " + user);
-                               LdifAuthorization authorization = new LdifAuthorization(directoryUser,
-                                               scopedUserAdmin.getAllRoles(directoryUser));
-                               return authorization;
-                       } finally {
-                               scopedUserAdmin.destroy();
-                       }
-               }
-       }
-
-       @Override
-       public Role createRole(String name, int type) {
-               checkEdit();
-               LdapEntryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toLdapName(name);
-               if ((daoHasEntry(dn) && !wc.getDeletedData().containsKey(dn)) || wc.getNewData().containsKey(dn))
-                       throw new IllegalArgumentException("Already a role " + name);
-               BasicAttributes attrs = new BasicAttributes(true);
-               // attrs.put(LdifName.dn.name(), dn.toString());
-               Rdn nameRdn = dn.getRdn(dn.size() - 1);
-               // TODO deal with multiple attr RDN
-               attrs.put(nameRdn.getType(), nameRdn.getValue());
-               if (wc.getDeletedData().containsKey(dn)) {
-                       wc.getDeletedData().remove(dn);
-                       wc.getModifiedData().put(dn, attrs);
-                       return getRole(name);
-               } else {
-                       wc.getModifiedData().put(dn, attrs);
-                       DirectoryUser newRole = newRole(dn, type, attrs);
-                       wc.getNewData().put(dn, newRole);
-                       return newRole;
-               }
-       }
-
-       protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
-               DirectoryUser newRole;
-               BasicAttribute objClass = new BasicAttribute(objectClass.name());
-               if (type == Role.USER) {
-                       String userObjClass = newUserObjectClass(dn);
-                       objClass.add(userObjClass);
-                       if (inetOrgPerson.name().equals(userObjClass)) {
-                               objClass.add(organizationalPerson.name());
-                               objClass.add(person.name());
-                       } else if (organizationalPerson.name().equals(userObjClass)) {
-                               objClass.add(person.name());
-                       }
-                       objClass.add(top.name());
-                       objClass.add(extensibleObject.name());
-                       attrs.put(objClass);
-                       newRole = newUser(dn, attrs);
-               } 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);
-               } else
-                       throw new IllegalArgumentException("Unsupported type " + type);
-               return newRole;
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               checkEdit();
-               LdapEntryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toLdapName(name);
-               boolean actuallyDeleted;
-               if (daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
-                       DirectoryUser user = (DirectoryUser) getRole(name);
-                       wc.getDeletedData().put(dn, user);
-                       actuallyDeleted = true;
-               } else {// just removing from groups (e.g. system roles)
-                       actuallyDeleted = false;
-               }
-               for (LdapName groupDn : getDirectGroups(dn)) {
-                       DirectoryUser group = doGetRole(groupDn);
-                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
-               }
-               return actuallyDeleted;
-       }
-
-       /*
-        * HIERARCHY
-        */
-       @Override
-       public HierarchyUnit getHierarchyUnit(Role role) {
-               LdapName dn = LdapNameUtils.toLdapName(role.getName());
-               LdapName huDn = LdapNameUtils.getParent(dn);
-               HierarchyUnit hierarchyUnit = doGetHierarchyUnit(huDn);
-               if (hierarchyUnit == null)
-                       throw new IllegalStateException("No hierarchy unit found for " + role);
-               return hierarchyUnit;
-       }
-
-       @Override
-       public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
-               LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getContext());
-               try {
-                       return getRoles(dn, filter, deep);
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
-               }
-       }
-
-       /*
-        * ROLES CREATION
-        */
-       protected DirectoryUser newUser(LdapName name, Attributes attrs) {
-               // TODO support devices, applications, etc.
-               return new LdifUser.LdifPerson(this, name, attrs);
-       }
-
-       protected DirectoryGroup newGroup(LdapName name, Attributes attrs) {
-               if (LdapNameUtils.getParentRdn(name).equals(getSystemRoleBaseRdn()))
-                       return new LdifGroup.LdifSystemPermissions(this, name, attrs);
-
-               if (hasObjectClass(attrs, LdapObjs.organization))
-                       return new LdifGroup.LdifOrganization(this, name, attrs);
-               else
-                       return new LdifGroup.LdifFunctionalGroup(this, name, attrs);
-
-       }
-
-       // GETTERS
-       protected UserAdmin getExternalRoles() {
-               return externalRoles;
-       }
-
-       protected int roleType(LdapName dn) {
-               Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
-               if (getGroupBaseRdn().equals(technicalRdn) || getSystemRoleBaseRdn().equals(technicalRdn))
-                       return Role.GROUP;
-               else if (getUserBaseRdn().equals(technicalRdn))
-                       return Role.USER;
-               else
-                       throw new IllegalArgumentException(
-                                       "Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
-       }
-
-       public void setExternalRoles(UserAdmin externalRoles) {
-               this.externalRoles = externalRoles;
-       }
-
-//     public void setTransactionManager(TransactionManager transactionManager) {
-//             this.transactionManager = transactionManager;
-//     }
-
-       /*
-        * STATIC UTILITIES
-        */
-       static LdapName toLdapName(String name) {
-               try {
-                       return new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException(name + " is not an LDAP name", e);
-               }
-       }
-}
index 955178ce4042a3ee9a9782260bcbfcb4b164d00a..3857b08d0607027cf55e0a4b72528de70135b7e7 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.osgi.useradmin;
 
-import static org.argeo.osgi.useradmin.AbstractUserDirectory.toLdapName;
+import static org.argeo.osgi.useradmin.DirectoryUserAdmin.toLdapName;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -30,9 +30,9 @@ public class AggregatingUserAdmin implements UserAdmin {
        private final LdapName tokensBaseDn;
 
        // DAOs
-       private AbstractUserDirectory systemRoles = null;
-       private AbstractUserDirectory tokens = null;
-       private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
+       private DirectoryUserAdmin systemRoles = null;
+       private DirectoryUserAdmin tokens = null;
+       private Map<LdapName, DirectoryUserAdmin> businessRoles = new HashMap<LdapName, DirectoryUserAdmin>();
 
        // TODO rather use an empty constructor and an init method
        public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
@@ -91,7 +91,7 @@ public class AggregatingUserAdmin implements UserAdmin {
                if (user == null) {// anonymous
                        return systemRoles.getAuthorization(null);
                }
-               AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
+               DirectoryUserAdmin userReferentialOfThisUser = findUserAdmin(user.getName());
                Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
                String usernameToUse;
                String displayNameToUse;
@@ -113,11 +113,11 @@ public class AggregatingUserAdmin implements UserAdmin {
                }
 
                // gather roles from other referentials
-               final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
+               final DirectoryUserAdmin userAdminToUse;// possibly scoped when authenticating
                if (user instanceof DirectoryUser) {
                        userAdminToUse = userReferentialOfThisUser;
                } else if (user instanceof AuthenticatingUser) {
-                       userAdminToUse = (AbstractUserDirectory) userReferentialOfThisUser.scope(user);
+                       userAdminToUse = (DirectoryUserAdmin) userReferentialOfThisUser.scope(user);
                } else {
                        throw new IllegalArgumentException("Unsupported user type " + user.getClass());
                }
@@ -157,9 +157,9 @@ public class AggregatingUserAdmin implements UserAdmin {
        // USER ADMIN AGGREGATOR
        //
        protected void addUserDirectory(UserDirectory ud) {
-               if (!(ud instanceof AbstractUserDirectory))
-                       throw new IllegalArgumentException("Only " + AbstractUserDirectory.class.getName() + " is supported");
-               AbstractUserDirectory userDirectory = (AbstractUserDirectory) ud;
+               if (!(ud instanceof DirectoryUserAdmin))
+                       throw new IllegalArgumentException("Only " + DirectoryUserAdmin.class.getName() + " is supported");
+               DirectoryUserAdmin userDirectory = (DirectoryUserAdmin) ud;
                String basePath = userDirectory.getContext();
                if (isSystemRolesBaseDn(basePath)) {
                        this.systemRoles = userDirectory;
@@ -181,7 +181,7 @@ public class AggregatingUserAdmin implements UserAdmin {
        protected void postAdd(UserDirectory userDirectory) {
        }
 
-       private AbstractUserDirectory findUserAdmin(String name) {
+       private DirectoryUserAdmin findUserAdmin(String name) {
                try {
                        return findUserAdmin(new LdapName(name));
                } catch (InvalidNameException e) {
@@ -189,14 +189,14 @@ public class AggregatingUserAdmin implements UserAdmin {
                }
        }
 
-       private AbstractUserDirectory findUserAdmin(LdapName name) {
+       private DirectoryUserAdmin findUserAdmin(LdapName name) {
                if (name.startsWith(systemRolesBaseDn))
                        return systemRoles;
                if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
                        return tokens;
-               List<AbstractUserDirectory> res = new ArrayList<>(1);
+               List<DirectoryUserAdmin> res = new ArrayList<>(1);
                userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(baseDn);
+                       DirectoryUserAdmin userDirectory = businessRoles.get(baseDn);
                        if (name.startsWith(baseDn)) {
                                if (userDirectory.isDisabled())
                                        continue userDirectories;
@@ -240,7 +240,7 @@ public class AggregatingUserAdmin implements UserAdmin {
 
        public void destroy() {
                for (LdapName name : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(name);
+                       DirectoryUserAdmin userDirectory = businessRoles.get(name);
                        destroy(userDirectory);
                }
                businessRoles.clear();
@@ -249,7 +249,7 @@ public class AggregatingUserAdmin implements UserAdmin {
                systemRoles = null;
        }
 
-       private void destroy(AbstractUserDirectory userDirectory) {
+       private void destroy(DirectoryUserAdmin userDirectory) {
                preDestroy(userDirectory);
                userDirectory.destroy();
        }
@@ -260,7 +260,7 @@ public class AggregatingUserAdmin implements UserAdmin {
                LdapName baseDn = toLdapName(basePath);
                if (!businessRoles.containsKey(baseDn))
                        throw new IllegalStateException("No user directory registered for " + baseDn);
-               AbstractUserDirectory userDirectory = businessRoles.remove(baseDn);
+               DirectoryUserAdmin userDirectory = businessRoles.remove(baseDn);
                destroy(userDirectory);
        }
 
index 7f8046313fae7b3ba9987cf3fa940732ebe2e053..1d58a2dae57f1588fde9ff70ce6f8e1b80ef5243 100644 (file)
@@ -1,12 +1,8 @@
 package org.argeo.osgi.useradmin;
 
-import java.util.List;
-
-import javax.naming.ldap.LdapName;
-
 import org.osgi.service.useradmin.Group;
 
 /** A group in a user directroy. */
 interface DirectoryGroup extends Group, DirectoryUser {
-       List<LdapName> getMemberNames();
+//     List<LdapName> getMemberNames();
 }
index c82c5a01ddbc811606acf0774dd8f244075b9cfa..18b28a288872ce6cbcf9555905d56d14f22b83f8 100644 (file)
@@ -1,8 +1,7 @@
 package org.argeo.osgi.useradmin;
 
-import org.argeo.util.directory.ldap.LdapEntry;
 import org.osgi.service.useradmin.User;
 
 /** A user in a user directory. */
-interface DirectoryUser extends User, LdapEntry {
+interface DirectoryUser extends User {
 }
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUserAdmin.java
new file mode 100644 (file)
index 0000000..9f6d62d
--- /dev/null
@@ -0,0 +1,420 @@
+package org.argeo.osgi.useradmin;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapObjs.extensibleObject;
+import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
+import static org.argeo.util.naming.LdapObjs.organizationalPerson;
+import static org.argeo.util.naming.LdapObjs.person;
+import static org.argeo.util.naming.LdapObjs.top;
+
+import java.net.URI;
+import java.nio.channels.UnsupportedAddressTypeException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.DirectoryDigestUtils;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.AbstractLdapDirectory;
+import org.argeo.util.directory.ldap.LdapEntry;
+import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.util.directory.ldap.LdapNameUtils;
+import org.argeo.util.directory.ldap.LdifDao;
+import org.argeo.util.naming.LdapObjs;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Base class for a {@link UserDirectory}. */
+public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdmin, UserDirectory {
+
+       private UserAdmin externalRoles;
+       // private List<String> indexedUserProperties = Arrays
+       // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
+       // LdapAttrs.cn.name() });
+
+       // Transaction
+//     private TransactionManager transactionManager;
+       public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props) {
+               this(uriArg, props, false);
+       }
+
+       public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+               super(uriArg, props, scoped);
+       }
+
+       public DirectoryUserAdmin(Dictionary<String, ?> props) {
+               this(null, props);
+       }
+
+       /*
+        * ABSTRACT METHODS
+        */
+
+       protected AbstractLdapDirectory scope(User user) {
+               throw new UnsupportedAddressTypeException();
+       }
+
+       protected DirectoryUserAdmin scopeLdap(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Dictionary<String, Object> properties = cloneProperties();
+               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+               } else {
+                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+               }
+               return new DirectoryUserAdmin(null, properties, true);
+       }
+
+       protected DirectoryUserAdmin scopeLdif(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+                       User directoryUser = (User) getRole(username);
+                       if (!directoryUser.hasCredential(null, password))
+                               throw new IllegalStateException("Invalid credentials");
+               } else {
+                       throw new IllegalStateException("Password is required");
+               }
+               Dictionary<String, Object> properties = cloneProperties();
+               properties.put(DirectoryConf.readOnly.name(), "true");
+               DirectoryUserAdmin scopedUserAdmin = new DirectoryUserAdmin(null, properties, true);
+//             scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
+//             scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
+               // FIXME do it better
+               ((LdifDao) getDirectoryDao()).scope((LdifDao) scopedUserAdmin.getDirectoryDao());
+               return scopedUserAdmin;
+       }
+
+       @Override
+       public String getRolePath(Role role) {
+               return nameToRelativePath(LdapNameUtils.toLdapName(role.getName()));
+       }
+
+       @Override
+       public String getRoleSimpleName(Role role) {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               String name = LdapNameUtils.getLastRdnValue(dn);
+               return name;
+       }
+
+       @Override
+       public Role getRoleByPath(String path) {
+               return (Role) doGetRole(pathToName(path));
+       }
+
+       protected List<Role> getAllRoles(DirectoryUser user) {
+               List<Role> allRoles = new ArrayList<Role>();
+               if (user != null) {
+                       collectRoles(user, allRoles);
+                       allRoles.add(user);
+               } else
+                       collectAnonymousRoles(allRoles);
+               return allRoles;
+       }
+
+       private void collectRoles(DirectoryUser user, List<Role> allRoles) {
+               List<LdapEntry> allEntries = new ArrayList<>();
+               LdapEntry entry = (LdapEntry) user;
+               collectGroups(entry, allEntries);
+               for (LdapEntry e : allEntries) {
+                       allRoles.add((Role) e);
+               }
+//             Attributes attrs = user.getAttributes();
+//             // TODO centralize attribute name
+//             Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
+//             // if user belongs to this directory, we only check memberOf
+//             if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
+//                     try {
+//                             NamingEnumeration<?> values = memberOf.getAll();
+//                             while (values.hasMore()) {
+//                                     Object value = values.next();
+//                                     LdapName groupDn = new LdapName(value.toString());
+//                                     DirectoryUser group = doGetRole(groupDn);
+//                                     if (group != null)
+//                                             allRoles.add(group);
+//                             }
+//                     } catch (NamingException e) {
+//                             throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
+//                     }
+//             } else {
+//                     for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
+//                             // TODO check for loops
+//                             DirectoryUser group = doGetRole(groupDn);
+//                             if (group != null) {
+//                                     allRoles.add(group);
+//                                     collectRoles(group, allRoles);
+//                             }
+//                     }
+//             }
+       }
+
+       private void collectAnonymousRoles(List<Role> allRoles) {
+               // TODO gather anonymous roles
+       }
+
+       // USER ADMIN
+       @Override
+       public Role getRole(String name) {
+               return (Role) doGetRole(toLdapName(name));
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               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 {
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+//             Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+               List<LdapEntry> searchRes = getDirectoryDao().doGetEntries(searchBase, filter, deep);
+               List<DirectoryUser> res = new ArrayList<>();
+               for (LdapEntry entry : searchRes)
+                       res.add((DirectoryUser) entry);
+               if (wc != null) {
+                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
+                               DirectoryUser user = (DirectoryUser) it.next();
+                               LdapName dn = LdapNameUtils.toLdapName(user.getName());
+                               if (wc.getDeletedData().containsKey(dn))
+                                       it.remove();
+                       }
+                       Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+                       for (LdapEntry ldapEntry : wc.getNewData().values()) {
+                               DirectoryUser user = (DirectoryUser) ldapEntry;
+                               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
+               }
+
+               // 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
+       public User getUser(String key, String value) {
+               // TODO check value null or empty
+               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
+               if (key != null) {
+                       doGetUser(key, value, collectedUsers);
+               } else {
+                       throw new IllegalArgumentException("Key cannot be null");
+               }
+
+               if (collectedUsers.size() == 1) {
+                       return collectedUsers.get(0);
+               } else if (collectedUsers.size() > 1) {
+                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
+                       // "") + value);
+               }
+               return null;
+       }
+
+       protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
+               String f = "(" + key + "=" + value + ")";
+               List<LdapEntry> users = getDirectoryDao().doGetEntries(getBaseDn(), f, true);
+               for (LdapEntry entry : users)
+                       collectedUsers.add((DirectoryUser) entry);
+       }
+
+       @Override
+       public Authorization getAuthorization(User user) {
+               if (user == null || user instanceof DirectoryUser) {
+                       return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
+               } else {
+                       // bind
+                       DirectoryUserAdmin scopedUserAdmin = (DirectoryUserAdmin) scope(user);
+                       try {
+                               DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
+                               if (directoryUser == null)
+                                       throw new IllegalStateException("No scoped user found for " + user);
+                               LdifAuthorization authorization = new LdifAuthorization(directoryUser,
+                                               scopedUserAdmin.getAllRoles(directoryUser));
+                               return authorization;
+                       } finally {
+                               scopedUserAdmin.destroy();
+                       }
+               }
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = toLdapName(name);
+               if ((getDirectoryDao().daoHasEntry(dn) && !wc.getDeletedData().containsKey(dn))
+                               || wc.getNewData().containsKey(dn))
+                       throw new IllegalArgumentException("Already a role " + name);
+               BasicAttributes attrs = new BasicAttributes(true);
+               // attrs.put(LdifName.dn.name(), dn.toString());
+               Rdn nameRdn = dn.getRdn(dn.size() - 1);
+               // TODO deal with multiple attr RDN
+               attrs.put(nameRdn.getType(), nameRdn.getValue());
+               if (wc.getDeletedData().containsKey(dn)) {
+                       wc.getDeletedData().remove(dn);
+                       wc.getModifiedData().put(dn, attrs);
+                       return getRole(name);
+               } else {
+                       wc.getModifiedData().put(dn, attrs);
+                       LdapEntry newRole = newRole(dn, type, attrs);
+                       wc.getNewData().put(dn, newRole);
+                       return (Role) newRole;
+               }
+       }
+
+       protected LdapEntry newRole(LdapName dn, int type, Attributes attrs) {
+               LdapEntry newRole;
+               BasicAttribute objClass = new BasicAttribute(objectClass.name());
+               if (type == Role.USER) {
+                       String userObjClass = getUserObjectClass();
+                       objClass.add(userObjClass);
+                       if (inetOrgPerson.name().equals(userObjClass)) {
+                               objClass.add(organizationalPerson.name());
+                               objClass.add(person.name());
+                       } else if (organizationalPerson.name().equals(userObjClass)) {
+                               objClass.add(person.name());
+                       }
+                       objClass.add(top.name());
+                       objClass.add(extensibleObject.name());
+                       attrs.put(objClass);
+                       newRole = newUser(dn, attrs);
+               } 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);
+               } else
+                       throw new IllegalArgumentException("Unsupported type " + type);
+               return newRole;
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               return removeEntry(LdapNameUtils.toLdapName(name));
+//             checkEdit();
+//             LdapEntryWorkingCopy wc = getWorkingCopy();
+//             LdapName dn = toLdapName(name);
+//             boolean actuallyDeleted;
+//             if (getDirectoryDao().daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
+//                     DirectoryUser user = (DirectoryUser) getRole(name);
+//                     wc.getDeletedData().put(dn, user);
+//                     actuallyDeleted = true;
+//             } else {// just removing from groups (e.g. system roles)
+//                     actuallyDeleted = false;
+//             }
+//             for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
+//                     LdapEntry group = doGetRole(groupDn);
+//                     group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
+//             }
+//             return actuallyDeleted;
+       }
+
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public HierarchyUnit getHierarchyUnit(Role role) {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               LdapName huDn = LdapNameUtils.getParent(dn);
+               HierarchyUnit hierarchyUnit = getDirectoryDao().doGetHierarchyUnit(huDn);
+               if (hierarchyUnit == null)
+                       throw new IllegalStateException("No hierarchy unit found for " + role);
+               return hierarchyUnit;
+       }
+
+       @Override
+       public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
+               LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getContext());
+               try {
+                       return getRoles(dn, filter, deep);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
+               }
+       }
+
+       /*
+        * ROLES CREATION
+        */
+       protected LdapEntry newUser(LdapName name, Attributes attrs) {
+               // TODO support devices, applications, etc.
+               return new LdifUser.LdifPerson(this, name, attrs);
+       }
+
+       protected LdapEntry newGroup(LdapName name, Attributes attrs) {
+               if (LdapNameUtils.getParentRdn(name).equals(getSystemRoleBaseRdn()))
+                       return new LdifGroup.LdifSystemPermissions(this, name, attrs);
+
+               if (hasObjectClass(attrs, LdapObjs.organization))
+                       return new LdifGroup.LdifOrganization(this, name, attrs);
+               else
+                       return new LdifGroup.LdifFunctionalGroup(this, name, attrs);
+
+       }
+
+       // GETTERS
+       protected UserAdmin getExternalRoles() {
+               return externalRoles;
+       }
+
+       public void setExternalRoles(UserAdmin externalRoles) {
+               this.externalRoles = externalRoles;
+       }
+
+//     public void setTransactionManager(TransactionManager transactionManager) {
+//             this.transactionManager = transactionManager;
+//     }
+
+       /*
+        * STATIC UTILITIES
+        */
+       static LdapName toLdapName(String name) {
+               try {
+                       return new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException(name + " is not an LDAP name", e);
+               }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
deleted file mode 100644 (file)
index 36419d9..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.AuthenticationNotSupportedException;
-import javax.naming.Binding;
-import javax.naming.Context;
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.directory.DirectoryDigestUtils;
-import org.argeo.util.directory.HierarchyUnit;
-import org.argeo.util.directory.ldap.LdapConnection;
-import org.argeo.util.directory.ldap.LdapEntry;
-import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
-import org.argeo.util.directory.ldap.LdapHierarchyUnit;
-import org.argeo.util.naming.LdapObjs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDAP server. */
-public class LdapUserAdmin extends AbstractUserDirectory {
-       private LdapConnection ldapConnection;
-
-       public LdapUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-               ldapConnection = new LdapConnection(getUri().toString(), properties);
-       }
-
-       public void destroy() {
-               ldapConnection.destroy();
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
-                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
-               } else {
-                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
-               }
-               return new LdapUserAdmin(properties, true);
-       }
-
-//     protected InitialLdapContext getLdapContext() {
-//             return initialLdapContext;
-//     }
-
-       @Override
-       protected Boolean daoHasEntry(LdapName dn) {
-               try {
-                       return daoGetEntry(dn) != null;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
-       @Override
-       protected DirectoryUser daoGetEntry(LdapName name) throws NameNotFoundException {
-               try {
-                       Attributes attrs = ldapConnection.getAttributes(name);
-                       if (attrs.size() == 0)
-                               return null;
-                       int roleType = roleType(name);
-                       DirectoryUser res;
-                       if (roleType == Role.GROUP)
-                               res = newGroup(name, attrs);
-                       else if (roleType == Role.USER)
-                               res = newUser(name, attrs);
-                       else
-                               throw new IllegalArgumentException("Unsupported LDAP type for " + name);
-                       return res;
-               } catch (NameNotFoundException e) {
-                       throw e;
-               } catch (NamingException e) {
-                       return null;
-               }
-       }
-
-       @Override
-       protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
-               ArrayList<LdapEntry> res = new ArrayList<>();
-               try {
-                       String searchFilter = f != null ? f.toString()
-                                       : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
-                                                       + getGroupObjectClass() + "))";
-                       SearchControls searchControls = new SearchControls();
-                       // FIXME make one level consistent with deep
-                       searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
-
-                       // LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       results: while (results.hasMoreElements()) {
-                               SearchResult searchResult = results.next();
-                               Attributes attrs = searchResult.getAttributes();
-                               Attribute objectClassAttr = attrs.get(objectClass.name());
-                               LdapName dn = toDn(searchBase, searchResult);
-                               DirectoryUser role;
-                               if (objectClassAttr.contains(getGroupObjectClass())
-                                               || objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
-                                       role = newGroup(dn, attrs);
-                               else if (objectClassAttr.contains(getUserObjectClass())
-                                               || objectClassAttr.contains(getUserObjectClass().toLowerCase()))
-                                       role = newUser(dn, attrs);
-                               else {
-//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
-                                       continue results;
-                               }
-                               res.add(role);
-                       }
-                       return res;
-               } catch (AuthenticationNotSupportedException e) {
-                       // ignore (typically an unsupported anonymous bind)
-                       // TODO better logging
-                       return res;
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot get roles for filter " + f, e);
-               }
-       }
-
-       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
-               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               try {
-                       String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
-                                       + "=" + dn + "))";
-
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                       LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       while (results.hasMoreElements()) {
-                               SearchResult searchResult = (SearchResult) results.nextElement();
-                               directGroups.add(toDn(searchBase, searchResult));
-                       }
-                       return directGroups;
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot populate direct members of " + dn, e);
-               }
-       }
-
-       @Override
-       public void prepare(LdapEntryWorkingCopy wc) {
-               try {
-                       ldapConnection.prepareChanges(wc);
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot prepare LDAP", e);
-               }
-       }
-
-       @Override
-       public void commit(LdapEntryWorkingCopy wc) {
-               try {
-                       ldapConnection.commitChanges(wc);
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot commit LDAP", e);
-               }
-       }
-
-       @Override
-       public void rollback(LdapEntryWorkingCopy wc) {
-               // prepare not impacting
-       }
-
-       /*
-        * HIERARCHY
-        */
-
-       @Override
-       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
-               List<HierarchyUnit> res = new ArrayList<>();
-               try {
-                       String searchFilter = "(|(" + objectClass + "=" + LdapObjs.organizationalUnit.name() + ")(" + objectClass
-                                       + "=" + LdapObjs.organization.name() + "))";
-
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
-
-                       NamingEnumeration<SearchResult> 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(this, dn, attrs);
-                               if (functionalOnly) {
-                                       if (hierarchyUnit.isFunctional())
-                                               res.add(hierarchyUnit);
-                               } else {
-                                       res.add(hierarchyUnit);
-                               }
-                       }
-                       return res;
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot get direct hierarchy units ", e);
-               }
-       }
-
-       @Override
-       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
-               try {
-                       Attributes attrs = ldapConnection.getAttributes(dn);
-                       return new LdapHierarchyUnit(this, dn, attrs);
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot get hierarchy unit " + dn, e);
-               }
-       }
-
-}
index 72b08a8c3d0fc59c78fd3a0f3dc35ec45a0ffbd1..7aad15a8c4d404453079b64e2da0dbc03d9816c1 100644 (file)
@@ -4,8 +4,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 import javax.naming.InvalidNameException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
@@ -13,13 +11,14 @@ import javax.naming.ldap.LdapName;
 import org.argeo.util.directory.FunctionalGroup;
 import org.argeo.util.directory.Organization;
 import org.argeo.util.directory.SystemPermissions;
+import org.argeo.util.directory.ldap.AbstractLdapDirectory;
 import org.osgi.service.useradmin.Role;
 
 /** Directory group implementation */
 abstract class LdifGroup extends LdifUser implements DirectoryGroup {
        private final String memberAttributeId;
 
-       LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+       LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
                super(userAdmin, dn, attributes);
                memberAttributeId = userAdmin.getMemberAttributeId();
        }
@@ -74,7 +73,7 @@ abstract class LdifGroup extends LdifUser implements DirectoryGroup {
        @Override
        public Role[] getMembers() {
                List<Role> directMembers = new ArrayList<Role>();
-               for (LdapName ldapName : getMemberNames()) {
+               for (LdapName ldapName : getReferences(memberAttributeId)) {
                        Role role = findRole(ldapName);
                        if (role == null) {
                                throw new IllegalStateException("Role " + ldapName + " not found.");
@@ -98,23 +97,23 @@ abstract class LdifGroup extends LdifUser implements DirectoryGroup {
                return role;
        }
 
-       @Override
-       public List<LdapName> getMemberNames() {
-               Attribute memberAttribute = getAttributes().get(memberAttributeId);
-               if (memberAttribute == null)
-                       return new ArrayList<LdapName>();
-               try {
-                       List<LdapName> roles = new ArrayList<LdapName>();
-                       NamingEnumeration<?> values = memberAttribute.getAll();
-                       while (values.hasMore()) {
-                               LdapName dn = new LdapName(values.next().toString());
-                               roles.add(dn);
-                       }
-                       return roles;
-               } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot get members", e);
-               }
-       }
+//     @Override
+//     public List<LdapName> getMemberNames() {
+//             Attribute memberAttribute = getAttributes().get(memberAttributeId);
+//             if (memberAttribute == null)
+//                     return new ArrayList<LdapName>();
+//             try {
+//                     List<LdapName> roles = new ArrayList<LdapName>();
+//                     NamingEnumeration<?> values = memberAttribute.getAll();
+//                     while (values.hasMore()) {
+//                             LdapName dn = new LdapName(values.next().toString());
+//                             roles.add(dn);
+//                     }
+//                     return roles;
+//             } catch (NamingException e) {
+//                     throw new IllegalStateException("Cannot get members", e);
+//             }
+//     }
 
        @Override
        public Role[] getRequiredMembers() {
@@ -131,7 +130,7 @@ abstract class LdifGroup extends LdifUser implements DirectoryGroup {
         */
        static class LdifFunctionalGroup extends LdifGroup implements FunctionalGroup {
 
-               public LdifFunctionalGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               public LdifFunctionalGroup(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
                        super(userAdmin, dn, attributes);
                }
 
@@ -139,7 +138,7 @@ abstract class LdifGroup extends LdifUser implements DirectoryGroup {
 
        static class LdifOrganization extends LdifGroup implements Organization {
 
-               public LdifOrganization(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               public LdifOrganization(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
                        super(userAdmin, dn, attributes);
                }
 
@@ -147,7 +146,7 @@ abstract class LdifGroup extends LdifUser implements DirectoryGroup {
 
        static class LdifSystemPermissions extends LdifGroup implements SystemPermissions {
 
-               public LdifSystemPermissions(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               public LdifSystemPermissions(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
                        super(userAdmin, dn, attributes);
                }
 
index 6cf6725ccb065640f172a96e8d04c0825d05eff5..cceb6e4611c92fa06530c73e34471726da24df23 100644 (file)
@@ -23,6 +23,7 @@ import javax.naming.ldap.LdapName;
 
 import org.argeo.util.directory.DirectoryDigestUtils;
 import org.argeo.util.directory.Person;
+import org.argeo.util.directory.ldap.AbstractLdapDirectory;
 import org.argeo.util.directory.ldap.AbstractLdapEntry;
 import org.argeo.util.directory.ldap.AuthPassword;
 import org.argeo.util.naming.LdapAttrs;
@@ -34,7 +35,7 @@ abstract class LdifUser extends AbstractLdapEntry implements DirectoryUser {
        private final AttributeDictionary properties;
        private final AttributeDictionary credentials;
 
-       LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+       LdifUser(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
                super(userAdmin, dn, attributes);
                properties = new AttributeDictionary(false);
                credentials = new AttributeDictionary(true);
@@ -160,8 +161,8 @@ abstract class LdifUser extends AbstractLdapEntry implements DirectoryUser {
 //             return hashedPassword;
 //     }
 
-       protected AbstractUserDirectory getUserAdmin() {
-               return (AbstractUserDirectory) getDirectory();
+       protected DirectoryUserAdmin getUserAdmin() {
+               return (DirectoryUserAdmin) getDirectory();
        }
 
        private class AttributeDictionary extends Dictionary<String, Object> {
@@ -357,7 +358,7 @@ abstract class LdifUser extends AbstractLdapEntry implements DirectoryUser {
 
        static class LdifPerson extends LdifUser implements Person {
 
-               public LdifPerson(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               public LdifPerson(DirectoryUserAdmin userAdmin, LdapName dn, Attributes attributes) {
                        super(userAdmin, dn, attributes);
                }
 
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
deleted file mode 100644 (file)
index c978af4..0000000
+++ /dev/null
@@ -1,379 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.directory.DirectoryConf;
-import org.argeo.util.directory.DirectoryDigestUtils;
-import org.argeo.util.directory.HierarchyUnit;
-import org.argeo.util.directory.ldap.LdapEntry;
-import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
-import org.argeo.util.directory.ldap.LdapHierarchyUnit;
-import org.argeo.util.directory.ldap.LdifParser;
-import org.argeo.util.directory.ldap.LdifWriter;
-import org.argeo.util.naming.LdapObjs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDIF files. */
-public class LdifUserAdmin extends AbstractUserDirectory {
-       private NavigableMap<LdapName, LdapEntry> users = new TreeMap<>();
-       private NavigableMap<LdapName, LdapEntry> groups = new TreeMap<>();
-
-       private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
-//     private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
-
-       public LdifUserAdmin(String uri, String baseDn) {
-               this(fromUri(uri, baseDn), false);
-       }
-
-       public LdifUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-       }
-
-       public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
-               super(uri, properties, false);
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
-                       User directoryUser = (User) getRole(username);
-                       if (!directoryUser.hasCredential(null, password))
-                               throw new IllegalStateException("Invalid credentials");
-               } else {
-                       throw new IllegalStateException("Password is required");
-               }
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(DirectoryConf.readOnly.name(), "true");
-               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
-               scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
-               scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
-               return scopedUserAdmin;
-       }
-
-       private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
-               Hashtable<String, Object> res = new Hashtable<String, Object>();
-               res.put(DirectoryConf.uri.name(), uri);
-               res.put(DirectoryConf.baseDn.name(), baseDn);
-               return res;
-       }
-
-       public void init() {
-
-               try {
-                       URI u = new URI(getUri());
-                       if (u.getScheme().equals("file")) {
-                               File file = new File(u);
-                               if (!file.exists())
-                                       return;
-                       }
-                       load(u.toURL().openStream());
-               } catch (IOException | URISyntaxException e) {
-                       throw new IllegalStateException("Cannot open URL " + getUri(), e);
-               }
-       }
-
-       public void save() {
-               if (getUri() == null)
-                       throw new IllegalStateException("Cannot save LDIF user admin: no URI is set");
-               if (isReadOnly())
-                       throw new IllegalStateException("Cannot save LDIF user admin: " + getUri() + " is read-only");
-               try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
-                       save(out);
-               } catch (IOException | URISyntaxException e) {
-                       throw new IllegalStateException("Cannot save user admin to " + getUri(), e);
-               }
-       }
-
-       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())
-                               ldifWriter.writeEntry(name, users.get(name).getAttributes());
-               } finally {
-                       out.close();
-               }
-       }
-
-       protected void load(InputStream in) {
-               try {
-                       users.clear();
-                       groups.clear();
-                       hierarchy.clear();
-
-                       LdifParser ldifParser = new LdifParser();
-                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
-                       for (LdapName key : allEntries.keySet()) {
-                               Attributes attributes = allEntries.get(key);
-                               // check for inconsistency
-                               Set<String> lowerCase = new HashSet<String>();
-                               NamingEnumeration<String> ids = attributes.getIDs();
-                               while (ids.hasMoreElements()) {
-                                       String id = ids.nextElement().toLowerCase();
-                                       if (lowerCase.contains(id))
-                                               throw new IllegalStateException(key + " has duplicate id " + id);
-                                       lowerCase.add(id);
-                               }
-
-                               // analyse object classes
-                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
-                               // System.out.println(key);
-                               objectClasses: while (objectClasses.hasMore()) {
-                                       String objectClass = objectClasses.next().toString();
-                                       // System.out.println(" " + objectClass);
-                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
-                                               users.put(key, newUser(key, attributes));
-                                               break objectClasses;
-                                       } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
-                                               groups.put(key, newGroup(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 LdapHierarchyUnit(this, key, 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 = null;
-//                                     continue hierachyUnits;
-//                             }
-//                             parent.children.add(unit);
-//                             unit.parent = parent;
-//                     }
-               } catch (NamingException | IOException e) {
-                       throw new IllegalStateException("Cannot load user admin service from LDIF", e);
-               }
-       }
-
-       public void destroy() {
-               if (users == null || groups == null)
-                       throw new IllegalStateException("User directory " + getBaseDn() + " is already destroyed");
-               users = null;
-               groups = null;
-       }
-
-       /*
-        * USER ADMIN
-        */
-
-       @Override
-       protected DirectoryUser daoGetEntry(LdapName key) throws NameNotFoundException {
-               if (groups.containsKey(key))
-                       return (DirectoryUser) groups.get(key);
-               if (users.containsKey(key))
-                       return (DirectoryUser) users.get(key);
-               throw new NameNotFoundException(key + " not persisted");
-       }
-
-       @Override
-       protected Boolean daoHasEntry(LdapName dn) {
-               return users.containsKey(dn) || groups.containsKey(dn);
-       }
-
-       @Override
-       protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
-               Objects.requireNonNull(searchBase);
-               ArrayList<LdapEntry> res = new ArrayList<>();
-               if (f == null && deep && getBaseDn().equals(searchBase)) {
-                       res.addAll(users.values());
-                       res.addAll(groups.values());
-               } else {
-                       filterRoles(users, searchBase, f, deep, res);
-                       filterRoles(groups, searchBase, f, deep, res);
-               }
-               return res;
-       }
-
-       private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, Filter f, boolean deep,
-                       List<LdapEntry> res) {
-               // TODO reduce map with search base ?
-               roles: for (LdapEntry 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(((DirectoryUser) user).getProperties()))
-                                               res.add(user);
-                               }
-                       }
-               }
-
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               for (LdapName name : groups.keySet()) {
-                       DirectoryGroup group;
-                       try {
-                               group = (DirectoryGroup) daoGetEntry(name);
-                       } catch (NameNotFoundException e) {
-                               throw new IllegalArgumentException("Group " + dn + " not found", e);
-                       }
-                       if (group.getMemberNames().contains(dn))
-                               directGroups.add(group.getDn());
-               }
-               return directGroups;
-       }
-
-       @Override
-       public void prepare(LdapEntryWorkingCopy wc) {
-               // delete
-               for (LdapName dn : wc.getDeletedData().keySet()) {
-                       if (users.containsKey(dn))
-                               users.remove(dn);
-                       else if (groups.containsKey(dn))
-                               groups.remove(dn);
-                       else
-                               throw new IllegalStateException("User to delete not found " + dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewData().keySet()) {
-                       DirectoryUser user = (DirectoryUser) wc.getNewData().get(dn);
-                       if (users.containsKey(dn) || groups.containsKey(dn))
-                               throw new IllegalStateException("User to create found " + dn);
-                       else if (Role.USER == user.getType())
-                               users.put(dn, user);
-                       else if (Role.GROUP == user.getType())
-                               groups.put(dn, (DirectoryGroup) user);
-                       else
-                               throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedData().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
-                       DirectoryUser user;
-                       try {
-                               user = daoGetEntry(dn);
-                       } catch (NameNotFoundException e) {
-                               throw new IllegalStateException("User to modify no found " + dn, e);
-                       }
-                       if (user == null)
-                               throw new IllegalStateException("User to modify no found " + dn);
-                       user.publishAttributes(modifiedAttrs);
-               }
-       }
-
-       @Override
-       public void commit(LdapEntryWorkingCopy wc) {
-               save();
-       }
-
-       @Override
-       public void rollback(LdapEntryWorkingCopy wc) {
-               init();
-       }
-
-       /*
-        * HIERARCHY
-        */
-
-//     @Override
-//     public int getHierarchyChildCount() {
-//             return rootHierarchyUnits.size();
-//     }
-//
-//     @Override
-//     public HierarchyUnit getHierarchyChild(int i) {
-//             return rootHierarchyUnits.get(i);
-//     }
-       @Override
-       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
-               return hierarchy.get(dn);
-       }
-
-       @Override
-       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
-               List<HierarchyUnit> res = new ArrayList<>();
-               for (LdapName n : hierarchy.keySet()) {
-                       if (n.size() == searchBase.size() + 1) {
-                               if (n.startsWith(searchBase)) {
-                                       HierarchyUnit hu = hierarchy.get(n);
-                                       if (functionalOnly) {
-                                               if (hu.isFunctional())
-                                                       res.add(hu);
-                                       } else {
-                                               res.add(hu);
-                                       }
-                               }
-                       }
-               }
-               return res;
-       }
-
-//     @Override
-//     public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
-//             if (functionalOnly) {
-//                     List<HierarchyUnit> res = new ArrayList<>();
-//                     for (HierarchyUnit hu : rootHierarchyUnits) {
-//                             if (hu.isFunctional())
-//                                     res.add(hu);
-//                     }
-//                     return res;
-//
-//             } else {
-//                     return rootHierarchyUnits;
-//             }
-//     }
-
-}
index 1f428ecbd9841702011224d4a7dc5ff5eaa51ea2..1adc7e0dfe6f1806b0b7186a8a5ef2d75e6aa5f7 100644 (file)
@@ -1,8 +1,6 @@
 package org.argeo.osgi.useradmin;
 
-import java.net.URI;
 import java.util.ArrayList;
-import java.util.Dictionary;
 import java.util.List;
 
 import javax.naming.NameNotFoundException;
@@ -12,23 +10,23 @@ import javax.naming.directory.BasicAttributes;
 import javax.naming.ldap.LdapName;
 
 import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.directory.ldap.AbstractLdapDirectory;
+import org.argeo.util.directory.ldap.AbstractLdapDirectoryDao;
 import org.argeo.util.directory.ldap.LdapEntry;
 import org.argeo.util.directory.ldap.LdapEntryWorkingCopy;
 import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.User;
 
 /** Pseudo user directory to be used when logging in as OS user. */
-public class OsUserDirectory extends AbstractUserDirectory {
+public class OsUserDirectory extends AbstractLdapDirectoryDao {
        private final String osUsername = System.getProperty("user.name");
        private final LdapName osUserDn;
-       private final DirectoryUser osUser;
+       private final LdapEntry osUser;
 
-       public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
-               super(uriArg, props, false);
+       public OsUserDirectory(AbstractLdapDirectory directory) {
+               super(directory);
                try {
-                       osUserDn = new LdapName(
-                                       LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBaseRdn() + "," + getBaseDn());
+                       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);
@@ -38,17 +36,17 @@ public class OsUserDirectory extends AbstractUserDirectory {
        }
 
        @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
+       public List<LdapName> getDirectGroups(LdapName dn) {
                return new ArrayList<>();
        }
 
        @Override
-       protected Boolean daoHasEntry(LdapName dn) {
+       public Boolean daoHasEntry(LdapName dn) {
                return osUserDn.equals(dn);
        }
 
        @Override
-       protected DirectoryUser daoGetEntry(LdapName key) throws NameNotFoundException {
+       public LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException {
                if (osUserDn.equals(key))
                        return osUser;
                else
@@ -56,18 +54,13 @@ public class OsUserDirectory extends AbstractUserDirectory {
        }
 
        @Override
-       protected List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep) {
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
                List<LdapEntry> res = new ArrayList<>();
-               if (f == null || f.match(osUser.getProperties()))
-                       res.add(osUser);
+//             if (f == null || f.match(osUser.getProperties()))
+               res.add(osUser);
                return res;
        }
 
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               throw new UnsupportedOperationException();
-       }
-
        @Override
        public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
                return null;
@@ -90,4 +83,17 @@ public class OsUserDirectory extends AbstractUserDirectory {
 
        }
 
+       @Override
+       public void init() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       @Override
+       public void destroy() {
+               // TODO Auto-generated method stub
+               
+       }
+
+       
 }
index b3dfa8b05cc8ae208ec65508eec06f42f5453b25..05808908d109adc00da816b0a19b615bc1737e33 100644 (file)
@@ -17,10 +17,6 @@ public interface Directory {
 
        boolean isDisabled();
 
-       String getUserObjectClass();
-
-       String getGroupObjectClass();
-
        Optional<String> getRealm();
 
        void setTransactionControl(WorkControl transactionControl);
index 27f9c55e3db314e1686694e7216cc1cfece150cf..d8e8e7d2127a27de54c2aa0906021c6bc6a585ee 100644 (file)
@@ -23,19 +23,17 @@ import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 import javax.transaction.xa.XAResource;
 
+import org.argeo.osgi.useradmin.OsUserDirectory;
 import org.argeo.util.directory.Directory;
 import org.argeo.util.directory.DirectoryConf;
 import org.argeo.util.directory.HierarchyUnit;
 import org.argeo.util.naming.LdapAttrs;
 import org.argeo.util.naming.LdapObjs;
 import org.argeo.util.transaction.WorkControl;
-import org.argeo.util.transaction.WorkingCopyProcessor;
 import org.argeo.util.transaction.WorkingCopyXaResource;
 import org.argeo.util.transaction.XAResourceProvider;
-import org.osgi.framework.Filter;
 
-public abstract class AbstractLdapDirectory
-               implements Directory, WorkingCopyProcessor<LdapEntryWorkingCopy>, XAResourceProvider {
+public abstract class AbstractLdapDirectory implements Directory, XAResourceProvider {
        protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
        protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
 
@@ -43,6 +41,7 @@ public abstract class AbstractLdapDirectory
        protected final Hashtable<String, Object> properties;
        private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
        private final String userObjectClass, groupObjectClass;
+       private String memberAttributeId = "member";
 
        private final boolean readOnly;
        private final boolean disabled;
@@ -52,12 +51,13 @@ public abstract class AbstractLdapDirectory
 
        private final boolean scoped;
 
-       private String memberAttributeId = "member";
        private List<String> credentialAttributeIds = Arrays
                        .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
 
        private WorkControl transactionControl;
-       private WorkingCopyXaResource<LdapEntryWorkingCopy> xaResource = new WorkingCopyXaResource<>(this);
+       private WorkingCopyXaResource<LdapEntryWorkingCopy> xaResource;
+
+       private LdapDirectoryDao directoryDao;
 
        public AbstractLdapDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
                this.properties = new Hashtable<String, Object>();
@@ -82,8 +82,9 @@ public abstract class AbstractLdapDirectory
                forcedPassword = DirectoryConf.forcedPassword.getValue(properties);
 
                userObjectClass = DirectoryConf.userObjectClass.getValue(properties);
-               String userBase = DirectoryConf.userBase.getValue(properties);
                groupObjectClass = DirectoryConf.groupObjectClass.getValue(properties);
+
+               String userBase = DirectoryConf.userBase.getValue(properties);
                String groupBase = DirectoryConf.groupBase.getValue(properties);
                String systemRoleBase = DirectoryConf.systemRoleBase.getValue(properties);
                try {
@@ -112,21 +113,56 @@ public abstract class AbstractLdapDirectory
                        disabled = Boolean.parseBoolean(disabledStr);
                else
                        disabled = false;
+
+               URI u = URI.create(uri);
+               if (!getRealm().isEmpty() || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+                               || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+                       directoryDao = new LdapDao(this);
+               } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+                       directoryDao = new LdifDao(this);
+               } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+                       directoryDao = new OsUserDirectory(this);
+                       // singleUser = true;
+               } else {
+                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+               }
+               xaResource = new WorkingCopyXaResource<>(directoryDao);
        }
 
        /*
         * ABSTRACT METHODS
         */
 
-       public abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
+//     public abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
+//
+//     public abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+//
+//     protected abstract Boolean daoHasEntry(LdapName dn);
+//
+//     protected abstract LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException;
+//
+//     protected abstract List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep);
+//
+//     /** Returns the groups this user is a direct member of. */
+//     protected abstract List<LdapName> getDirectGroups(LdapName dn);
+       /*
+        * INITIALIZATION
+        */
 
-       public abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+       public void init() {
+               getDirectoryDao().init();
+       }
 
-       protected abstract Boolean daoHasEntry(LdapName dn);
+       public void destroy() {
+               getDirectoryDao().destroy();
+       }
 
-       protected abstract LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException;
+       /*
+        * CREATION
+        */
+       protected abstract LdapEntry newUser(LdapName name, Attributes attrs);
 
-       protected abstract List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep);
+       protected abstract LdapEntry newGroup(LdapName name, Attributes attrs);
 
        /*
         * EDITION
@@ -162,9 +198,73 @@ public abstract class AbstractLdapDirectory
                return xaResource;
        }
 
-       @Override
-       public LdapEntryWorkingCopy newWorkingCopy() {
-               return new LdapEntryWorkingCopy();
+       public boolean removeEntry(LdapName dn) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               boolean actuallyDeleted;
+               if (getDirectoryDao().daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
+                       LdapEntry user = doGetRole(dn);
+                       wc.getDeletedData().put(dn, user);
+                       actuallyDeleted = true;
+               } else {// just removing from groups (e.g. system roles)
+                       actuallyDeleted = false;
+               }
+               for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
+                       LdapEntry group = doGetRole(groupDn);
+                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
+               }
+               return actuallyDeleted;
+       }
+
+       /*
+        * RETRIEVAL
+        */
+
+       protected LdapEntry doGetRole(LdapName dn) {
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapEntry user;
+               try {
+                       user = getDirectoryDao().daoGetEntry(dn);
+               } catch (NameNotFoundException e) {
+                       user = null;
+               }
+               if (wc != null) {
+                       if (user == null && wc.getNewData().containsKey(dn))
+                               user = wc.getNewData().get(dn);
+                       else if (wc.getDeletedData().containsKey(dn))
+                               user = null;
+               }
+               return user;
+       }
+
+       protected void collectGroups(LdapEntry user, List<LdapEntry> allRoles) {
+               Attributes attrs = user.getAttributes();
+               // TODO centralize attribute name
+               Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
+               // if user belongs to this directory, we only check memberOf
+               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
+                       try {
+                               NamingEnumeration<?> values = memberOf.getAll();
+                               while (values.hasMore()) {
+                                       Object value = values.next();
+                                       LdapName groupDn = new LdapName(value.toString());
+                                       LdapEntry group = doGetRole(groupDn);
+                                       if (group != null)
+                                               allRoles.add(group);
+                               }
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
+                       }
+               } else {
+                       for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
+                               // TODO check for loops
+                               LdapEntry group = doGetRole(groupDn);
+                               if (group != null) {
+                                       allRoles.add(group);
+                                       collectGroups(group, allRoles);
+                               }
+                       }
+               }
        }
 
        /*
@@ -173,12 +273,12 @@ public abstract class AbstractLdapDirectory
        @Override
        public HierarchyUnit getHierarchyUnit(String path) {
                LdapName dn = pathToName(path);
-               return doGetHierarchyUnit(dn);
+               return directoryDao.doGetHierarchyUnit(dn);
        }
 
        @Override
        public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
-               return doGetDirectHierarchyUnits(baseDn, functionalOnly);
+               return directoryDao.doGetDirectHierarchyUnits(baseDn, functionalOnly);
        }
 
        /*
@@ -239,20 +339,23 @@ public abstract class AbstractLdapDirectory
        /*
         * UTILITIES
         */
-
        protected static boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) {
+               return hasObjectClass(attrs, objectClass.name());
+       }
+
+       protected static boolean hasObjectClass(Attributes attrs, String objectClass) {
                try {
                        Attribute attr = attrs.get(LdapAttrs.objectClass.name());
                        NamingEnumeration<?> en = attr.getAll();
                        while (en.hasMore()) {
                                String v = en.next().toString();
-                               if (v.equalsIgnoreCase(objectClass.name()))
+                               if (v.equalsIgnoreCase(objectClass))
                                        return true;
 
                        }
                        return false;
                } catch (NamingException e) {
-                       throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e);
+                       throw new IllegalStateException("Cannot search for objectClass " + objectClass, e);
                }
        }
 
@@ -294,7 +397,7 @@ public abstract class AbstractLdapDirectory
                return Optional.of(realm.toString());
        }
 
-       protected LdapName getBaseDn() {
+       public LdapName getBaseDn() {
                return (LdapName) baseDn.clone();
        }
 
@@ -306,23 +409,10 @@ public abstract class AbstractLdapDirectory
                return disabled;
        }
 
-       /** dn can be null, in that case a default should be returned. */
-       public String getUserObjectClass() {
-               return userObjectClass;
-       }
-
        public Rdn getUserBaseRdn() {
                return userBaseRdn;
        }
 
-       protected String newUserObjectClass(LdapName dn) {
-               return getUserObjectClass();
-       }
-
-       public String getGroupObjectClass() {
-               return groupObjectClass;
-       }
-
        public Rdn getGroupBaseRdn() {
                return groupBaseRdn;
        }
@@ -347,18 +437,31 @@ public abstract class AbstractLdapDirectory
                return scoped;
        }
 
-       public String getMemberAttributeId() {
-               return memberAttributeId;
-       }
-
        public List<String> getCredentialAttributeIds() {
                return credentialAttributeIds;
        }
 
-       protected String getUri() {
+       public String getUri() {
                return uri;
        }
 
+       public LdapDirectoryDao getDirectoryDao() {
+               return directoryDao;
+       }
+
+       /** dn can be null, in that case a default should be returned. */
+       public String getUserObjectClass() {
+               return userObjectClass;
+       }
+
+       public String getGroupObjectClass() {
+               return groupObjectClass;
+       }
+
+       public String getMemberAttributeId() {
+               return memberAttributeId;
+       }
+
        /*
         * OBJECT METHODS
         */
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
new file mode 100644 (file)
index 0000000..8c87170
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.util.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+public abstract class AbstractLdapDirectoryDao implements LdapDirectoryDao {
+
+       private AbstractLdapDirectory directory;
+
+       public AbstractLdapDirectoryDao(AbstractLdapDirectory directory) {
+               this.directory = directory;
+
+       }
+
+       public AbstractLdapDirectory getDirectory() {
+               return directory;
+       }
+
+       @Override
+       public LdapEntryWorkingCopy newWorkingCopy() {
+               return new LdapEntryWorkingCopy();
+       }
+
+       @Override
+       public LdapEntry newUser(LdapName name, Attributes attrs) {
+               return getDirectory().newUser(name, attrs);
+       }
+
+       @Override
+       public LdapEntry newGroup(LdapName name, Attributes attrs) {
+               return getDirectory().newGroup(name, attrs);
+       }
+
+}
index be919c020174053af3aa120126f92f3ef429de08..25f233a0104b3805a7e471d45c9800c950678360 100644 (file)
@@ -1,8 +1,16 @@
 package org.argeo.util.directory.ldap;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
 
+/** An entry in an LDAP (or LDIF) directory. */
 public abstract class AbstractLdapEntry implements LdapEntry {
        private final AbstractLdapDirectory directory;
 
@@ -10,8 +18,10 @@ public abstract class AbstractLdapEntry implements LdapEntry {
 
        private Attributes publishedAttributes;
 
-       protected AbstractLdapEntry(AbstractLdapDirectory userAdmin, LdapName dn, Attributes attributes) {
-               this.directory = userAdmin;
+       protected AbstractLdapEntry(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
+               Objects.requireNonNull(directory);
+               Objects.requireNonNull(dn);
+               this.directory = directory;
                this.dn = dn;
                this.publishedAttributes = attributes;
        }
@@ -24,6 +34,25 @@ public abstract class AbstractLdapEntry implements LdapEntry {
        public synchronized Attributes getAttributes() {
                return isEditing() ? getModifiedAttributes() : publishedAttributes;
        }
+       
+       @Override
+       public List<LdapName> getReferences(String attributeId){
+               Attribute memberAttribute = getAttributes().get(attributeId);
+               if (memberAttribute == null)
+                       return new ArrayList<LdapName>();
+               try {
+                       List<LdapName> roles = new ArrayList<LdapName>();
+                       NamingEnumeration<?> values = memberAttribute.getAll();
+                       while (values.hasMore()) {
+                               LdapName dn = new LdapName(values.next().toString());
+                               roles.add(dn);
+                       }
+                       return roles;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get members", e);
+               }
+               
+       }
 
        /** Should only be called from working copy thread. */
        protected synchronized Attributes getModifiedAttributes() {
@@ -53,10 +82,14 @@ public abstract class AbstractLdapEntry implements LdapEntry {
                publishedAttributes = modifiedAttributes;
        }
 
-       protected AbstractLdapDirectory getDirectory() {
+       public AbstractLdapDirectory getDirectory() {
                return directory;
        }
 
+       public LdapDirectoryDao getDirectoryDao() {
+               return directory.getDirectoryDao();
+       }
+
        @Override
        public int hashCode() {
                return dn.hashCode();
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
new file mode 100644 (file)
index 0000000..a2d9e7f
--- /dev/null
@@ -0,0 +1,247 @@
+package org.argeo.util.directory.ldap;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.AuthenticationNotSupportedException;
+import javax.naming.Binding;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.naming.LdapObjs;
+
+/** A user admin based on a LDAP server. */
+public class LdapDao extends AbstractLdapDirectoryDao {
+       private LdapConnection ldapConnection;
+
+//     public LdapUserAdmin(Dictionary<String, ?> properties) {
+//             this(properties, false);
+//     }
+
+       public LdapDao(AbstractLdapDirectory directory) {
+               super(directory);
+       }
+
+       @Override
+       public void init() {
+               ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().getProperties());
+       }
+
+       public void destroy() {
+               ldapConnection.destroy();
+       }
+
+//     @Override
+//     protected AbstractUserDirectory scope(User user) {
+//             Dictionary<String, Object> credentials = user.getCredentials();
+//             String username = (String) credentials.get(SHARED_STATE_USERNAME);
+//             if (username == null)
+//                     username = user.getName();
+//             Dictionary<String, Object> properties = cloneProperties();
+//             properties.put(Context.SECURITY_PRINCIPAL, username.toString());
+//             Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+//             byte[] pwd = (byte[]) pwdCred;
+//             if (pwd != null) {
+//                     char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+//                     properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+//             } else {
+//                     properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+//             }
+//             return new LdapUserAdmin(properties, true);
+//     }
+
+//     protected InitialLdapContext getLdapContext() {
+//             return initialLdapContext;
+//     }
+
+       @Override
+       public Boolean daoHasEntry(LdapName dn) {
+               try {
+                       return daoGetEntry(dn) != null;
+               } catch (NameNotFoundException e) {
+                       return false;
+               }
+       }
+
+       @Override
+       public LdapEntry daoGetEntry(LdapName name) throws NameNotFoundException {
+               try {
+                       Attributes attrs = ldapConnection.getAttributes(name);
+                       if (attrs.size() == 0)
+                               return null;
+//                     int roleType = roleType(name);
+                       LdapEntry res;
+                       if (isGroup(name))
+                               res = newGroup(name, attrs);
+                       else
+                               res = newUser(name, attrs);
+//                     else
+//                             throw new IllegalArgumentException("Unsupported LDAP type for " + name);
+                       return res;
+               } catch (NameNotFoundException e) {
+                       throw e;
+               } catch (NamingException e) {
+                       return null;
+               }
+       }
+
+       protected boolean isGroup(LdapName dn) {
+               Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
+               if (getDirectory().getGroupBaseRdn().equals(technicalRdn)
+                               || getDirectory().getSystemRoleBaseRdn().equals(technicalRdn))
+                       return true;
+               else if (getDirectory().getUserBaseRdn().equals(technicalRdn))
+                       return false;
+               else
+                       throw new IllegalArgumentException(
+                                       "Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               ArrayList<LdapEntry> res = new ArrayList<>();
+               try {
+                       String searchFilter = f != null ? f.toString()
+                                       : "(|(" + objectClass + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass + "="
+                                                       + getDirectory().getGroupObjectClass() + "))";
+                       SearchControls searchControls = new SearchControls();
+                       // FIXME make one level consistent with deep
+                       searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
+
+                       // LdapName searchBase = getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       results: while (results.hasMoreElements()) {
+                               SearchResult searchResult = results.next();
+                               Attributes attrs = searchResult.getAttributes();
+                               Attribute objectClassAttr = attrs.get(objectClass.name());
+                               LdapName dn = toDn(searchBase, searchResult);
+                               LdapEntry role;
+                               if (objectClassAttr.contains(getDirectory().getGroupObjectClass())
+                                               || objectClassAttr.contains(getDirectory().getGroupObjectClass().toLowerCase()))
+                                       role = newGroup(dn, attrs);
+                               else if (objectClassAttr.contains(getDirectory().getUserObjectClass())
+                                               || objectClassAttr.contains(getDirectory().getUserObjectClass().toLowerCase()))
+                                       role = newUser(dn, attrs);
+                               else {
+//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
+                                       continue results;
+                               }
+                               res.add(role);
+                       }
+                       return res;
+               } catch (AuthenticationNotSupportedException e) {
+                       // ignore (typically an unsupported anonymous bind)
+                       // TODO better logging
+                       return res;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get roles for filter " + f, e);
+               }
+       }
+
+       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
+               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               try {
+                       String searchFilter = "(&(" + objectClass + "=" + getDirectory().getGroupObjectClass() + ")("
+                                       + getDirectory().getMemberAttributeId() + "=" + dn + "))";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+                       LdapName searchBase = getDirectory().getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       while (results.hasMoreElements()) {
+                               SearchResult searchResult = (SearchResult) results.nextElement();
+                               directGroups.add(toDn(searchBase, searchResult));
+                       }
+                       return directGroups;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot populate direct members of " + dn, e);
+               }
+       }
+
+       @Override
+       public void prepare(LdapEntryWorkingCopy wc) {
+               try {
+                       ldapConnection.prepareChanges(wc);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot prepare LDAP", e);
+               }
+       }
+
+       @Override
+       public void commit(LdapEntryWorkingCopy wc) {
+               try {
+                       ldapConnection.commitChanges(wc);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot commit LDAP", e);
+               }
+       }
+
+       @Override
+       public void rollback(LdapEntryWorkingCopy wc) {
+               // prepare not impacting
+       }
+
+       /*
+        * HIERARCHY
+        */
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               List<HierarchyUnit> res = new ArrayList<>();
+               try {
+                       String searchFilter = "(|(" + objectClass + "=" + LdapObjs.organizationalUnit.name() + ")(" + objectClass
+                                       + "=" + LdapObjs.organization.name() + "))";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+
+                       NamingEnumeration<SearchResult> 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);
+                               if (functionalOnly) {
+                                       if (hierarchyUnit.isFunctional())
+                                               res.add(hierarchyUnit);
+                               } else {
+                                       res.add(hierarchyUnit);
+                               }
+                       }
+                       return res;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get direct hierarchy units ", e);
+               }
+       }
+
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               try {
+                       Attributes attrs = ldapConnection.getAttributes(dn);
+                       return new LdapHierarchyUnit(getDirectory(), dn, attrs);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get hierarchy unit " + dn, 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
new file mode 100644 (file)
index 0000000..3a0f4e6
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.util.directory.ldap;
+
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.transaction.WorkingCopyProcessor;
+
+public interface LdapDirectoryDao extends WorkingCopyProcessor<LdapEntryWorkingCopy> {
+       Boolean daoHasEntry(LdapName dn);
+
+       LdapEntry daoGetEntry(LdapName name) throws NameNotFoundException;
+
+       List<LdapEntry> doGetEntries(LdapName searchBase, String filter, boolean deep);
+
+       List<LdapName> getDirectGroups(LdapName dn);
+
+       Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+
+       HierarchyUnit doGetHierarchyUnit(LdapName dn);
+
+       LdapEntry newUser(LdapName name, Attributes attrs);
+
+       LdapEntry newGroup(LdapName name, Attributes attrs);
+       
+       void init();
+       
+       void destroy();
+}
index c145a6f0ab6e0f8258279011e4158fe685cf9ca9..3fa23e5f1bd32a5851892bd7e0b29df1f52d0fd3 100644 (file)
@@ -1,5 +1,7 @@
 package org.argeo.util.directory.ldap;
 
+import java.util.List;
+
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
 
@@ -10,4 +12,5 @@ public interface LdapEntry {
 
        void publishAttributes(Attributes modifiedAttributes);
 
+       public List<LdapName> getReferences(String attributeId);
 }
index d76c449b0ad84601def94459661c9f000535e339..5cfca3192b0ce985f8724dac573380e551dbf454 100644 (file)
@@ -1,32 +1,17 @@
 package org.argeo.util.directory.ldap;
 
-import java.util.Objects;
-
 import javax.naming.directory.Attributes;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
-import org.argeo.util.directory.Directory;
 import org.argeo.util.directory.HierarchyUnit;
 
 /** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
-public class LdapHierarchyUnit implements HierarchyUnit {
-       private final AbstractLdapDirectory directory;
-
-       private final LdapName dn;
+public class LdapHierarchyUnit extends AbstractLdapEntry implements HierarchyUnit {
        private final boolean functional;
-       private final Attributes attributes;
-
-//     HierarchyUnit parent;
-//     List<HierarchyUnit> children = new ArrayList<>();
 
        public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn, Attributes attributes) {
-               Objects.requireNonNull(directory);
-               Objects.requireNonNull(dn);
-
-               this.directory = directory;
-               this.dn = dn;
-               this.attributes = attributes;
+               super(directory, dn, attributes);
 
                Rdn rdn = LdapNameUtils.getLastRdn(dn);
                functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)
@@ -35,21 +20,12 @@ public class LdapHierarchyUnit implements HierarchyUnit {
 
        @Override
        public HierarchyUnit getParent() {
-               return directory.doGetHierarchyUnit(LdapNameUtils.getParent(dn));
+               return getDirectoryDao().doGetHierarchyUnit(LdapNameUtils.getParent(getDn()));
        }
 
        @Override
        public Iterable<HierarchyUnit> getDirectHierachyUnits(boolean functionalOnly) {
-//             List<HierarchyUnit> res = new ArrayList<>();
-//             if (functionalOnly)
-//                     for (HierarchyUnit hu : children) {
-//                             if (hu.isFunctional())
-//                                     res.add(hu);
-//                     }
-//             else
-//                     res.addAll(children);
-//             return Collections.unmodifiableList(res);
-               return directory.doGetDirectHierarchyUnits(dn, functionalOnly);
+               return getDirectoryDao().doGetDirectHierarchyUnits(getDn(), functionalOnly);
        }
 
        @Override
@@ -59,40 +35,19 @@ public class LdapHierarchyUnit implements HierarchyUnit {
 
        @Override
        public String getHierarchyUnitName() {
-               String name = LdapNameUtils.getLastRdnValue(dn);
+               String name = LdapNameUtils.getLastRdnValue(getDn());
                // TODO check ou, o, etc.
                return name;
        }
 
-       public Attributes getAttributes() {
-               return attributes;
-       }
-
        @Override
        public String getContext() {
-               return dn.toString();
-       }
-
-       @Override
-       public Directory getDirectory() {
-               return directory;
-       }
-
-       @Override
-       public int hashCode() {
-               return dn.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof LdapHierarchyUnit))
-                       return false;
-               return ((LdapHierarchyUnit) obj).dn.equals(dn);
+               return getDn().toString();
        }
 
        @Override
        public String toString() {
-               return "Hierarchy Unit " + dn.toString();
+               return "Hierarchy Unit " + getDn().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
new file mode 100644 (file)
index 0000000..c805d12
--- /dev/null
@@ -0,0 +1,408 @@
+package org.argeo.util.directory.ldap;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.HierarchyUnit;
+import org.argeo.util.naming.LdapObjs;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+
+/** A user admin based on a LDIF files. */
+public class LdifDao extends AbstractLdapDirectoryDao {
+//     private NavigableMap<LdapName, LdapEntry> users = new TreeMap<>();
+//     private NavigableMap<LdapName, LdapEntry> groups = new TreeMap<>();
+       private NavigableMap<LdapName, LdapEntry> entries = new TreeMap<>();
+
+       private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
+//     private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
+
+//     public LdifUserAdmin(String uri, String baseDn) {
+//             this(fromUri(uri, baseDn), false);
+//     }
+
+       public LdifDao(AbstractLdapDirectory directory) {
+               super(directory);
+       }
+
+//     protected LdifUserAdmin(Hashtable<String, ?> properties, boolean scoped) {
+//             super( properties, scoped);
+//     }
+
+//     public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
+//             super(uri, properties, false);
+//     }
+
+//     @Override
+//     protected AbstractUserDirectory scope(User user) {
+//             Dictionary<String, Object> credentials = user.getCredentials();
+//             String username = (String) credentials.get(SHARED_STATE_USERNAME);
+//             if (username == null)
+//                     username = user.getName();
+//             Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+//             byte[] pwd = (byte[]) pwdCred;
+//             if (pwd != null) {
+//                     char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+//                     User directoryUser = (User) getRole(username);
+//                     if (!directoryUser.hasCredential(null, password))
+//                             throw new IllegalStateException("Invalid credentials");
+//             } else {
+//                     throw new IllegalStateException("Password is required");
+//             }
+//             Dictionary<String, Object> properties = cloneProperties();
+//             properties.put(DirectoryConf.readOnly.name(), "true");
+//             LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
+////           scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
+////           scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
+//             scopedUserAdmin.entries = Collections.unmodifiableNavigableMap(entries);
+//             return scopedUserAdmin;
+//     }
+
+       private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
+               Hashtable<String, Object> res = new Hashtable<String, Object>();
+               res.put(DirectoryConf.uri.name(), uri);
+               res.put(DirectoryConf.baseDn.name(), baseDn);
+               return res;
+       }
+
+       public void init() {
+
+               try {
+                       URI u = new URI(getDirectory().getUri());
+                       if (u.getScheme().equals("file")) {
+                               File file = new File(u);
+                               if (!file.exists())
+                                       return;
+                       }
+                       load(u.toURL().openStream());
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
+               }
+       }
+
+       public void save() {
+               if (getDirectory().getUri() == null)
+                       throw new IllegalStateException("Cannot save LDIF user admin: no URI is set");
+               if (getDirectory().isReadOnly())
+                       throw new IllegalStateException(
+                                       "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
+               try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
+                       save(out);
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
+               }
+       }
+
+       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())
+//                             ldifWriter.writeEntry(name, users.get(name).getAttributes());
+                       for (LdapName name : entries.keySet())
+                               ldifWriter.writeEntry(name, entries.get(name).getAttributes());
+               } finally {
+                       out.close();
+               }
+       }
+
+       public void load(InputStream in) {
+               try {
+//                     users.clear();
+//                     groups.clear();
+                       entries.clear();
+                       hierarchy.clear();
+
+                       LdifParser ldifParser = new LdifParser();
+                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
+                       for (LdapName key : allEntries.keySet()) {
+                               Attributes attributes = allEntries.get(key);
+                               // check for inconsistency
+                               Set<String> lowerCase = new HashSet<String>();
+                               NamingEnumeration<String> ids = attributes.getIDs();
+                               while (ids.hasMoreElements()) {
+                                       String id = ids.nextElement().toLowerCase();
+                                       if (lowerCase.contains(id))
+                                               throw new IllegalStateException(key + " has duplicate id " + id);
+                                       lowerCase.add(id);
+                               }
+
+                               // analyse object classes
+                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
+                               // System.out.println(key);
+                               objectClasses: while (objectClasses.hasMore()) {
+                                       String objectClass = objectClasses.next().toString();
+                                       // System.out.println(" " + objectClass);
+                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
+                                               entries.put(key, newUser(key, attributes));
+                                               break objectClasses;
+                                       } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
+                                               entries.put(key, newGroup(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).toStrindirectoryDaog();
+//                                             if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
+//                                                     break objectClasses; // skip
+                                               // TODO skip if it does not contain groups or users
+                                               hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key, 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 = null;
+//                                     continue hierachyUnits;
+//                             }
+//                             parent.children.add(unit);
+//                             unit.parent = parent;
+//                     }
+               } catch (NamingException | IOException e) {
+                       throw new IllegalStateException("Cannot load user admin service from LDIF", e);
+               }
+       }
+
+       public void destroy() {
+//             if (users == null || groups == null)
+               if (entries == null)
+                       throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
+//             users = null;
+//             groups = null;
+               entries = null;
+       }
+
+       /*
+        * USER ADMIN
+        */
+
+       @Override
+       public LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException {
+//             if (groups.containsKey(key))
+//                     return groups.get(key);
+//             if (users.containsKey(key))
+//                     return users.get(key);
+               if (entries.containsKey(key))
+                       return entries.get(key);
+               throw new NameNotFoundException(key + " not persisted");
+       }
+
+       @Override
+       public Boolean daoHasEntry(LdapName dn) {
+               return entries.containsKey(dn);// || groups.containsKey(dn);
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               Objects.requireNonNull(searchBase);
+               ArrayList<LdapEntry> res = new ArrayList<>();
+               if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
+//                     res.addAll(users.values());
+//                     res.addAll(groups.values());
+                       res.addAll(entries.values());
+               } else {
+//                     filterRoles(users, searchBase, f, deep, res);
+//                     filterRoles(groups, searchBase, f, deep, res);
+                       filterRoles(entries, searchBase, f, deep, res);
+               }
+               return res;
+       }
+
+       private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
+                       List<LdapEntry> res) {
+               // FIXME get rid of OSGi references
+               try {
+                       // TODO reduce map with search base ?
+                       Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
+                       roles: for (LdapEntry user : map.values()) {
+                               LdapName dn = user.getDn();
+                               if (dn.startsWith(searchBase)) {
+                                       if (!deep && dn.size() != (searchBase.size() + 1))
+                                               continue roles;
+                                       if (filter == null)
+                                               res.add(user);
+                                       else {
+                                               if (user instanceof Role) {
+                                                       if (filter.match(((Role) user).getProperties()))
+                                                               res.add(user);
+                                               }
+                                       }
+                               }
+                       }
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot create filter " + f, e);
+               }
+
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               entries: for (LdapName name : entries.keySet()) {
+                       LdapEntry group;
+                       try {
+                               LdapEntry entry = daoGetEntry(name);
+                               if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
+                                       group = entry;
+                               } else {
+                                       continue entries;
+                               }
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalArgumentException("Group " + dn + " not found", e);
+                       }
+                       if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
+                               directGroups.add(group.getDn());
+                       }
+               }
+               return directGroups;
+       }
+
+       @Override
+       public void prepare(LdapEntryWorkingCopy wc) {
+               // delete
+               for (LdapName dn : wc.getDeletedData().keySet()) {
+                       if (entries.containsKey(dn))
+                               entries.remove(dn);
+//                     if (users.containsKey(dn))
+//                             users.remove(dn);
+//                     else if (groups.containsKey(dn))
+//                             groups.remove(dn);
+                       else
+                               throw new IllegalStateException("User to delete not found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
+//                     if (users.containsKey(dn) || groups.containsKey(dn))
+                       if (entries.containsKey(dn))
+                               throw new IllegalStateException("User to create found " + dn);
+                       entries.put(dn, user);
+//                     else if (Role.USER == user.getType())
+//                             users.put(dn, user);
+//                     else if (Role.GROUP == user.getType())
+//                             groups.put(dn, (DirectoryGroup) user);
+//                     else
+//                             throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+                       LdapEntry user;
+                       try {
+                               user = daoGetEntry(dn);
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalStateException("User to modify no found " + dn, e);
+                       }
+                       if (user == null)
+                               throw new IllegalStateException("User to modify no found " + dn);
+                       user.publishAttributes(modifiedAttrs);
+               }
+       }
+
+       @Override
+       public void commit(LdapEntryWorkingCopy wc) {
+               save();
+       }
+
+       @Override
+       public void rollback(LdapEntryWorkingCopy wc) {
+               init();
+       }
+
+       /*
+        * HIERARCHY
+        */
+
+//     @Override
+//     public int getHierarchyChildCount() {
+//             return rootHierarchyUnits.size();
+//     }
+//
+//     @Override
+//     public HierarchyUnit getHierarchyChild(int i) {
+//             return rootHierarchyUnits.get(i);
+//     }
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               return hierarchy.get(dn);
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               List<HierarchyUnit> res = new ArrayList<>();
+               for (LdapName n : hierarchy.keySet()) {
+                       if (n.size() == searchBase.size() + 1) {
+                               if (n.startsWith(searchBase)) {
+                                       HierarchyUnit hu = hierarchy.get(n);
+                                       if (functionalOnly) {
+                                               if (hu.isFunctional())
+                                                       res.add(hu);
+                                       } else {
+                                               res.add(hu);
+                                       }
+                               }
+                       }
+               }
+               return res;
+       }
+
+       public void scope(LdifDao scoped) {
+               scoped.entries = Collections.unmodifiableNavigableMap(entries);
+       }
+
+//     @Override
+//     public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+//             if (functionalOnly) {
+//                     List<HierarchyUnit> res = new ArrayList<>();
+//                     for (HierarchyUnit hu : rootHierarchyUnits) {
+//                             if (hu.isFunctional())
+//                                     res.add(hu);
+//                     }
+//                     return res;
+//
+//             } else {
+//                     return rootHierarchyUnits;
+//             }
+//     }
+
+}