]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
Introduce transaction working copy abstraction
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / osgi / useradmin / AbstractUserDirectory.java
index 716ddb5edfa72975fecfd3fef81812d4e7554505..838c2ce0b15f886234136ff1275c24de880e8b48 100644 (file)
@@ -18,6 +18,7 @@ import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
+import java.util.StringJoiner;
 
 import javax.naming.InvalidNameException;
 import javax.naming.NameNotFoundException;
@@ -29,9 +30,14 @@ import javax.naming.directory.BasicAttribute;
 import javax.naming.directory.BasicAttributes;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
+import javax.transaction.xa.XAResource;
 
-import org.argeo.osgi.transaction.WorkControl;
 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;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -41,13 +47,16 @@ import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
 
 /** Base class for a {@link UserDirectory}. */
-abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
+abstract class AbstractUserDirectory
+               implements UserAdmin, UserDirectory, WorkingCopyProcessor<DirectoryUserWorkingCopy>, XAResourceProvider {
        static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
        static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
 
        private final Hashtable<String, Object> properties;
-       private final LdapName baseDn, userBaseDn, groupBaseDn;
-       private final String userObjectClass, userBase, groupObjectClass, groupBase;
+       private final LdapName baseDn;
+       // private final LdapName userBaseDn, groupBaseDn;
+       private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
+       private final String userObjectClass, groupObjectClass;
 
        private final boolean readOnly;
        private final boolean disabled;
@@ -67,7 +76,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        // Transaction
 //     private TransactionManager transactionManager;
        private WorkControl transactionControl;
-       private WcXaResource xaResource = new WcXaResource(this);
+       private WorkingCopyXaResource<DirectoryUserWorkingCopy> xaResource = new WorkingCopyXaResource<>(this);
 
        private String forcedPassword;
 
@@ -93,23 +102,31 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
 
                userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
-               userBase = UserAdminConf.userBase.getValue(properties);
+               String userBase = UserAdminConf.userBase.getValue(properties);
                groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
-               groupBase = UserAdminConf.groupBase.getValue(properties);
+               String groupBase = UserAdminConf.groupBase.getValue(properties);
+               String systemRoleBase = UserAdminConf.systemRoleBase.getValue(properties);
                try {
                        baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
-                       userBaseDn = new LdapName(userBase + "," + baseDn);
-                       groupBaseDn = new LdapName(groupBase + "," + baseDn);
+                       userBaseRdn = new Rdn(userBase);
+//                     userBaseDn = new LdapName(userBase + "," + baseDn);
+                       groupBaseRdn = new Rdn(groupBase);
+//                     groupBaseDn = new LdapName(groupBase + "," + baseDn);
+                       systemRoleBaseRdn = new Rdn(systemRoleBase);
                } catch (InvalidNameException e) {
                        throw new IllegalArgumentException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties),
                                        e);
                }
+
+               // read only
                String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
                if (readOnlyStr == null) {
                        readOnly = readOnlyDefault(uri);
                        properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
                } else
                        readOnly = Boolean.parseBoolean(readOnlyStr);
+
+               // disabled
                String disabledStr = UserAdminConf.disabled.getValue(properties);
                if (disabledStr != null)
                        disabled = Boolean.parseBoolean(disabledStr);
@@ -117,6 +134,10 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        disabled = false;
        }
 
+       /*
+        * ABSTRACT METHODS
+        */
+
        /** Returns the groups this user is a direct member of. */
        protected abstract List<LdapName> getDirectGroups(LdapName dn);
 
@@ -128,6 +149,14 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        protected abstract AbstractUserDirectory scope(User user);
 
+       protected abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
+
+       protected abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+
+       /*
+        * INITIALIZATION
+        */
+
        public void init() {
 
        }
@@ -136,11 +165,78 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        }
 
+       /*
+        * PATHS
+        */
+
        @Override
-       public String getBasePath() {
+       public String getContext() {
                return getBaseDn().toString();
        }
 
+       @Override
+       public String getName() {
+               return nameToSimple(getBaseDn(), ".");
+       }
+
+       @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;
+       }
+
+       protected String nameToRelativePath(LdapName dn) {
+               LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
+               return nameToSimple(name, "/");
+       }
+
+       protected String nameToSimple(LdapName name, String separator) {
+               StringJoiner path = new StringJoiner(separator);
+               for (int i = 0; i < name.size(); i++) {
+                       path.add(name.getRdn(i).getValue().toString());
+               }
+               return path.toString();
+
+       }
+
+       protected LdapName pathToName(String path) {
+               try {
+                       LdapName name = (LdapName) getBaseDn().clone();
+                       String[] segments = path.split("/");
+                       Rdn parentRdn = null;
+                       for (String segment : segments) {
+                               // TODO make attr names configurable ?
+                               String attr = LdapAttrs.ou.name();
+                               if (parentRdn != null) {
+                                       if (getUserBaseRdn().equals(parentRdn))
+                                               attr = LdapAttrs.uid.name();
+                                       else if (getGroupBaseRdn().equals(parentRdn))
+                                               attr = LdapAttrs.cn.name();
+                                       else if (getSystemRoleBaseRdn().equals(parentRdn))
+                                               attr = LdapAttrs.cn.name();
+                               }
+                               Rdn rdn = new Rdn(attr, segment);
+                               name.add(rdn);
+                               parentRdn = rdn;
+                       }
+                       return name;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot get role " + path, e);
+               }
+
+       }
+
+       @Override
+       public Role getRoleByPath(String path) {
+               return doGetRole(pathToName(path));
+       }
+
        @Override
        public Optional<String> getRealm() {
                Object realm = getProperties().get(UserAdminConf.realm.name());
@@ -149,12 +245,16 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return Optional.of(realm.toString());
        }
 
+       /*
+        * EDITION
+        */
+
        protected boolean isEditing() {
                return xaResource.wc() != null;
        }
 
-       protected UserDirectoryWorkingCopy getWorkingCopy() {
-               UserDirectoryWorkingCopy wc = xaResource.wc();
+       protected DirectoryUserWorkingCopy getWorkingCopy() {
+               DirectoryUserWorkingCopy wc = xaResource.wc();
                if (wc == null)
                        return null;
                return wc;
@@ -222,7 +322,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        protected DirectoryUser doGetRole(LdapName dn) {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                DirectoryUser user;
                try {
                        user = daoGetRole(dn);
@@ -230,9 +330,9 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        user = null;
                }
                if (wc != null) {
-                       if (user == null && wc.getNewUsers().containsKey(dn))
-                               user = wc.getNewUsers().get(dn);
-                       else if (wc.getDeletedUsers().containsKey(dn))
+                       if (user == null && wc.getNewData().containsKey(dn))
+                               user = wc.getNewData().get(dn);
+                       else if (wc.getDeletedData().containsKey(dn))
                                user = null;
                }
                return user;
@@ -240,39 +340,22 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
 
        @Override
        public Role[] getRoles(String filter) throws InvalidSyntaxException {
-//             UserDirectoryWorkingCopy wc = getWorkingCopy();
-//             Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
-//             List<DirectoryUser> res = doGetRoles(getBaseDn(), f, true);
-//             if (wc != null) {
-//                     for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
-//                             DirectoryUser user = it.next();
-//                             LdapName dn = user.getDn();
-//                             if (wc.getDeletedUsers().containsKey(dn))
-//                                     it.remove();
-//                     }
-//                     for (DirectoryUser user : wc.getNewUsers().values()) {
-//                             if (f == null || f.match(user.getProperties()))
-//                                     res.add(user);
-//                     }
-//                     // no need to check modified users,
-//                     // since doGetRoles was already based on the modified attributes
-//             }
                List<? extends Role> res = getRoles(getBaseDn(), filter, true);
                return res.toArray(new Role[res.size()]);
        }
 
        List<DirectoryUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
                List<DirectoryUser> res = doGetRoles(searchBase, f, deep);
                if (wc != null) {
                        for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
                                DirectoryUser user = it.next();
                                LdapName dn = user.getDn();
-                               if (wc.getDeletedUsers().containsKey(dn))
+                               if (wc.getDeletedData().containsKey(dn))
                                        it.remove();
                        }
-                       for (DirectoryUser user : wc.getNewUsers().values()) {
+                       for (DirectoryUser user : wc.getNewData().values()) {
                                if (f == null || f.match(user.getProperties()))
                                        res.add(user);
                        }
@@ -281,19 +364,19 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                }
 
                // 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);
-                       }
-               }
+//             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;
        }
 
@@ -349,29 +432,29 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public Role createRole(String name, int type) {
                checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                LdapName dn = toLdapName(name);
-               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
+               if ((daoHasRole(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.getDeletedUsers().containsKey(dn)) {
-                       wc.getDeletedUsers().remove(dn);
-                       wc.getModifiedUsers().put(dn, attrs);
+               if (wc.getDeletedData().containsKey(dn)) {
+                       wc.getDeletedData().remove(dn);
+                       wc.getModifiedData().put(dn, attrs);
                        return getRole(name);
                } else {
-                       wc.getModifiedUsers().put(dn, attrs);
+                       wc.getModifiedData().put(dn, attrs);
                        DirectoryUser newRole = newRole(dn, type, attrs);
-                       wc.getNewUsers().put(dn, newRole);
+                       wc.getNewData().put(dn, newRole);
                        return newRole;
                }
        }
 
        protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
-               LdifUser newRole;
+               DirectoryUser newRole;
                BasicAttribute objClass = new BasicAttribute(objectClass.name());
                if (type == Role.USER) {
                        String userObjClass = newUserObjectClass(dn);
@@ -385,14 +468,14 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                        objClass.add(top.name());
                        objClass.add(extensibleObject.name());
                        attrs.put(objClass);
-                       newRole = new LdifUser(this, dn, attrs);
+                       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 = new LdifGroup(this, dn, attrs);
+                       newRole = newGroup(dn, attrs);
                } else
                        throw new IllegalArgumentException("Unsupported type " + type);
                return newRole;
@@ -401,12 +484,12 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        @Override
        public boolean removeRole(String name) {
                checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUserWorkingCopy wc = getWorkingCopy();
                LdapName dn = toLdapName(name);
                boolean actuallyDeleted;
-               if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
+               if (daoHasRole(dn) || wc.getNewData().containsKey(dn)) {
                        DirectoryUser user = (DirectoryUser) getRole(name);
-                       wc.getDeletedUsers().put(dn, user);
+                       wc.getDeletedData().put(dn, user);
                        actuallyDeleted = true;
                } else {// just removing from groups (e.g. system roles)
                        actuallyDeleted = false;
@@ -418,50 +501,70 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return actuallyDeleted;
        }
 
-       // TRANSACTION
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void commit(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-
+       /*
+        * TRANSACTION
+        */
+       @Override
+       public DirectoryUserWorkingCopy newWorkingCopy() {
+               return new DirectoryUserWorkingCopy();
        }
 
        /*
         * HIERARCHY
         */
        @Override
-       public int getHierarchyChildCount() {
-               return 0;
+       public HierarchyUnit getHierarchyUnit(String path) {
+               LdapName dn = pathToName(path);
+               return doGetHierarchyUnit(dn);
        }
 
        @Override
-       public HierarchyUnit getHierarchyChild(int i) {
-               throw new IllegalArgumentException("No child hierarchy unit available");
+       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 int getHierarchyUnitType() {
-               return 0;
+       public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+               return doGetDirectHierarchyUnits(baseDn, functionalOnly);
        }
 
-       @Override
-       public String getHierarchyUnitName() {
-               String name = baseDn.getRdn(baseDn.size() - 1).getValue().toString();
-               // TODO check ou, o, etc.
-               return name;
+       /*
+        * ROLES CREATION
+        */
+       protected DirectoryUser newUser(LdapName name, Attributes attrs) {
+               // TODO support devices, applications, etc.
+               return new LdifUser.LdifPerson(this, name, attrs);
        }
 
-       @Override
-       public List<? extends Role> getRoles(String filter, boolean deep) {
+       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);
+
+       }
+
+       private boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) {
                try {
-                       return getRoles(getBaseDn(), filter, deep);
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot filter " + filter + " " + getBaseDn(), e);
+                       Attribute attr = attrs.get(LdapAttrs.objectClass.name());
+                       NamingEnumeration<?> en = attr.getAll();
+                       while (en.hasMore()) {
+                               String v = en.next().toString();
+                               if (v.equalsIgnoreCase(objectClass.name()))
+                                       return true;
+
+                       }
+                       return false;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e);
                }
        }
 
@@ -518,12 +621,14 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
        }
 
        protected int roleType(LdapName dn) {
-               if (dn.startsWith(groupBaseDn))
+               Rdn technicalRdn = LdapNameUtils.getParentRdn(dn);
+               if (getGroupBaseRdn().equals(technicalRdn) || getSystemRoleBaseRdn().equals(technicalRdn))
                        return Role.GROUP;
-               else if (dn.startsWith(userBaseDn))
+               else if (userBaseRdn.equals(technicalRdn))
                        return Role.USER;
                else
-                       return Role.GROUP;
+                       throw new IllegalArgumentException(
+                                       "Cannot dind role type, " + technicalRdn + " is not a technical RDN for " + dn);
        }
 
        /** dn can be null, in that case a default should be returned. */
@@ -531,8 +636,8 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return userObjectClass;
        }
 
-       public String getUserBase() {
-               return userBase;
+       Rdn getUserBaseRdn() {
+               return userBaseRdn;
        }
 
        protected String newUserObjectClass(LdapName dn) {
@@ -543,11 +648,15 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                return groupObjectClass;
        }
 
-       public String getGroupBase() {
-               return groupBase;
+       Rdn getGroupBaseRdn() {
+               return groupBaseRdn;
+       }
+
+       Rdn getSystemRoleBaseRdn() {
+               return systemRoleBaseRdn;
        }
 
-       public LdapName getBaseDn() {
+       LdapName getBaseDn() {
                return (LdapName) baseDn.clone();
        }
 
@@ -575,7 +684,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
                this.transactionControl = transactionControl;
        }
 
-       public WcXaResource getXaResource() {
+       public XAResource getXaResource() {
                return xaResource;
        }