Improve Directory framework.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 17 Aug 2022 12:08:58 +0000 (14:08 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 17 Aug 2022 12:08:58 +0000 (14:08 +0200)
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
org.argeo.util/src/org/argeo/util/LangUtils.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/DefaultLdapEntry.java
org.argeo.util/src/org/argeo/util/directory/ldap/LdapDao.java

index 9af578c962ab8370b0d7ceb9dc72f4024f88eb1b..040138878603fc1f0cade62ef1cd00be7d58a471 100644 (file)
@@ -8,6 +8,7 @@ import java.util.Set;
 import javax.security.auth.Subject;
 
 import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.directory.HierarchyUnit;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
@@ -17,52 +18,59 @@ import org.osgi.service.useradmin.User;
  * the userAdmin.
  */
 public interface CmsUserManager {
-       public Map<String, String> getKnownBaseDns(boolean onlyWritable);
-       public Set<UserDirectory> getUserDirectories();
-       
+       Map<String, String> getKnownBaseDns(boolean onlyWritable);
+
+       Set<UserDirectory> getUserDirectories();
+
        // CurrentUser
        /** Returns the e-mail of the current logged in user */
-       public String getMyMail();
+       String getMyMail();
 
        // Other users
        /** Returns a {@link User} given a username */
-       public User getUser(String username);
+       User getUser(String username);
 
        /** Can be a group or a user */
-       public String getUserDisplayName(String dn);
+       String getUserDisplayName(String dn);
 
        /** Can be a group or a user */
-       public String getUserMail(String dn);
+       String getUserMail(String dn);
 
        /** Lists all roles of the given user */
-       public String[] getUserRoles(String dn);
+       String[] getUserRoles(String dn);
 
        /** Checks if the passed user belongs to the passed role */
-       public boolean isUserInRole(String userDn, String roleDn);
+       boolean isUserInRole(String userDn, String roleDn);
 
        // Search
        /** Returns a filtered list of roles */
-       public Role[] getRoles(String filter) throws InvalidSyntaxException;
+       Role[] getRoles(String filter) throws InvalidSyntaxException;
 
        /** Recursively lists users in a given group. */
-       public Set<User> listUsersInGroup(String groupDn, String filter);
+       Set<User> listUsersInGroup(String groupDn, String filter);
 
        /** Search among groups including system roles and users if needed */
-       public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
+       List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
+
+       /*
+        * EDITION
+        */
+       /** Creates a new user.*/
+       User createUser(String username, Map<String, Object> properties, Map<String, Object> credentials);
 
        /* MISCELLANEOUS */
        /** Returns the dn of a role given its local ID */
-       public String buildDefaultDN(String localId, int type);
+       String buildDefaultDN(String localId, int type);
 
        /** Exposes the main default domain name for this instance */
-       public String getDefaultDomainName();
+       String getDefaultDomainName();
 
        /**
         * Search for a {@link User} (might also be a group) whose uid or cn is equals
         * to localId within the various user repositories defined in the current
         * context.
         */
-       public User getUserFromLocalId(String localId);
+       User getUserFromLocalId(String localId);
 
        void changeOwnPassword(char[] oldPassword, char[] newPassword);
 
@@ -80,14 +88,9 @@ public interface CmsUserManager {
        void expireAuthToken(String token);
 
        void expireAuthTokens(Subject subject);
-       
-       UserDirectory getDirectory(Role role);
 
-//     User createUserFromPerson(Node person);
+       UserDirectory getDirectory(Role role);
 
-//     @Deprecated
-//     public UserAdmin getUserAdmin();
-//
-//     @Deprecated
-//     public UserTransaction getUserTransaction();
+       /** Create a new hierarchy unit. Does nothing if it already exists. */
+       HierarchyUnit createHierarchyUnit(UserDirectory directory, String path);
 }
\ No newline at end of file
index dfb0a56a27a554691706907b22f014f22a63ff23..36ae6e6d6612d4ea4c673e0077a1017626b5f785 100644 (file)
@@ -32,6 +32,7 @@ import org.argeo.osgi.useradmin.AggregatingUserAdmin;
 import org.argeo.osgi.useradmin.TokenUtils;
 import org.argeo.osgi.useradmin.UserDirectory;
 import org.argeo.util.directory.DirectoryConf;
+import org.argeo.util.directory.HierarchyUnit;
 import org.argeo.util.directory.ldap.SharedSecret;
 import org.argeo.util.naming.LdapAttrs;
 import org.argeo.util.naming.NamingUtils;
@@ -62,6 +63,9 @@ public class CmsUserManagerImpl implements CmsUserManager {
 //     private Map<String, String> serviceProperties;
        private WorkTransaction userTransaction;
 
+       private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
+                       LdapAttrs.uid.name() };
+
 //     private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
 //                     .synchronizedMap(new LinkedHashMap<>());
 
@@ -124,9 +128,6 @@ public class CmsUserManagerImpl implements CmsUserManager {
                return false;
        }
 
-       private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
-                       LdapAttrs.uid.name() };
-
        public Set<User> listUsersInGroup(String groupDn, String filter) {
                Group group = (Group) userAdmin.getRole(groupDn);
                if (group == null)
@@ -205,6 +206,37 @@ public class CmsUserManagerImpl implements CmsUserManager {
                return buildDistinguishedName(localId, getDefaultDomainName(), type);
        }
 
+       /*
+        * EDITION
+        */
+       @Override
+       public User createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.createRole(username, Role.USER);
+                       if (properties != null) {
+                               for (String key : properties.keySet())
+                                       user.getProperties().put(key, properties.get(key));
+                       }
+                       if (credentials != null) {
+                               for (String key : credentials.keySet())
+                                       user.getCredentials().put(key, credentials.get(key));
+                       }
+                       userTransaction.commit();
+                       return user;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create user " + username, e);
+               }
+       }
+
        @Override
        public String getDefaultDomainName() {
                Map<String, String> dns = getKnownBaseDns(true);
@@ -215,27 +247,6 @@ public class CmsUserManagerImpl implements CmsUserManager {
                                        + dns.keySet().toString() + ". Unable to chose a default one.");
        }
 
-//     public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-//             Map<String, String> dns = new HashMap<String, String>();
-//             String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
-//             for (String uri : propertyKeys) {
-//                     if (!uri.startsWith("/"))
-//                             continue;
-//                     Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
-//                     String readOnly = UserAdminConf.readOnly.getValue(props);
-//                     String baseDn = UserAdminConf.baseDn.getValue(props);
-//
-//                     if (onlyWritable && "true".equals(readOnly))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
-//                             continue;
-//                     dns.put(baseDn, uri);
-//             }
-//             return dns;
-//     }
-
        public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
                Map<String, String> dns = new HashMap<String, String>();
                for (UserDirectory userDirectory : userDirectories) {
@@ -462,6 +473,27 @@ public class CmsUserManagerImpl implements CmsUserManager {
                return possible.lastEntry().getValue();
        }
 
+       public HierarchyUnit createHierarchyUnit(UserDirectory directory, String path) {
+               HierarchyUnit hi = directory.getHierarchyUnit(path);
+               if (hi != null)
+                       return hi;
+               try {
+                       userTransaction.begin();
+                       HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path);
+                       userTransaction.commit();
+                       return hierarchyUnit;
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1);
+               }
+       }
+
 //     public User createUserFromPerson(Node person) {
 //             String email = JcrUtils.get(person, LdapAttrs.mail.property());
 //             String dn = buildDefaultDN(email, Role.USER);
index 1177d4f5354c53addedc51fddfa10f5d2d71ec25..1aee28c03c776d7438cc266e4842dee7e21af671 100644 (file)
@@ -94,6 +94,20 @@ public class LangUtils {
                return res;
        }
 
+       /** Converts a {@link Dictionary} to a {@link Map}. */
+       public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
+               if (properties == null) {
+                       return null;
+               }
+               Map<String, Object> res = new HashMap<>(properties.size());
+               Enumeration<String> keys = properties.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       res.put(key, properties.get(key));
+               }
+               return res;
+       }
+
        /**
         * Get a string property from this map, expecting to find it, or
         * <code>null</code> if not found.
index 351a608bc53c9c3dbfdb81910c858a54b1a3e56e..11e8e6285e64bcb0bf809e5adc22e2c56a76b953 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Optional;
 
 import org.argeo.util.transaction.WorkControl;
 
+/** An information directory (typicylly LDAP). */
 public interface Directory extends HierarchyUnit {
        /**
         * The base of the hierarchy defined by this directory. This could typically be
@@ -14,25 +15,33 @@ public interface Directory extends HierarchyUnit {
 
        String getName();
 
+       /** Whether this directory is read only. */
        boolean isReadOnly();
 
+       /** Whether this directory is disabled. */
        boolean isDisabled();
 
+       /** The realm (typically Kerberos) of this directory. */
        Optional<String> getRealm();
 
+       /** Sets the transaction control used by this directory when editing. */
        void setTransactionControl(WorkControl transactionControl);
 
        /*
         * METADATA
         */
+       /** Metadata of this directory. */
        public Dictionary<String, Object> getProperties();
 
        /*
         * HIERARCHY
         */
-
+       /** The first level of hierarchy units. */
        Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly);
 
+       /** The hierarchy unit at this path. */
        HierarchyUnit getHierarchyUnit(String path);
 
+       /** Create a new hierarchy unit. */
+       HierarchyUnit createHierarchyUnit(String path);
 }
index 36047d53e7e8786751fb0547fb1247346ab07e47..27c2b9531d6465b3ade47fce8dafcf69cfbc243b 100644 (file)
@@ -34,6 +34,7 @@ import org.argeo.util.transaction.WorkControl;
 import org.argeo.util.transaction.WorkingCopyXaResource;
 import org.argeo.util.transaction.XAResourceProvider;
 
+/** A {@link Directory} based either on LDAP or LDIF. */
 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";
@@ -146,23 +147,7 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv
        }
 
        /*
-        * ABSTRACT METHODS
-        */
-
-//     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
+        * INITIALISATION
         */
 
        public void init() {
@@ -327,6 +312,25 @@ public abstract class AbstractLdapDirectory implements Directory, XAResourceProv
                return this;
        }
 
+       @Override
+       public HierarchyUnit createHierarchyUnit(String path) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = pathToName(path);
+               if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
+                               || wc.getNewData().containsKey(dn))
+                       throw new IllegalArgumentException("Already a hierarchy unit " + path);
+               BasicAttributes attrs = new BasicAttributes(true);
+               attrs.put(LdapAttrs.objectClass.name(), LdapObjs.organizationalUnit.name());
+               Rdn nameRdn = dn.getRdn(dn.size() - 1);
+               // TODO deal with multiple attr RDN
+               attrs.put(nameRdn.getType(), nameRdn.getValue());
+               wc.getModifiedData().put(dn, attrs);
+               LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn, attrs);
+               wc.getNewData().put(dn, newHierarchyUnit);
+               return newHierarchyUnit;
+       }
+
        /*
         * PATHS
         */
index 7166710f61191e594dd776714d6b7bd56f73d459..14a7eb87cc2f5b78dc931ab0e5dd2eb47750550b 100644 (file)
@@ -119,7 +119,7 @@ public class DefaultLdapEntry implements LdapEntry {
 
        public Dictionary<String, Object> getCredentials() {
                if (credentials == null)
-                       credentials = new AttributeDictionary(false);
+                       credentials = new AttributeDictionary(true);
                return credentials;
        }
 
@@ -374,29 +374,43 @@ public class DefaultLdapEntry implements LdapEntry {
 
                @Override
                public Object put(String key, Object value) {
-                       if (key == null) {
-                               // TODO persist to other sources (like PKCS12)
-                               char[] password = DirectoryDigestUtils.bytesToChars(value);
-                               byte[] hashedPassword = sha1hash(password);
-                               return put(LdapAttrs.userPassword.name(), hashedPassword);
-                       }
-                       if (key.startsWith("X-")) {
-                               return put(LdapAttrs.authPassword.name(), value);
-                       }
+                       try {
+                               if (key == null) {
+                                       // TODO persist to other sources (like PKCS12)
+                                       char[] password = DirectoryDigestUtils.bytesToChars(value);
+                                       byte[] hashedPassword = sha1hash(password);
+                                       return put(LdapAttrs.userPassword.name(), hashedPassword);
+                               }
+                               if (key.startsWith("X-")) {
+                                       return put(LdapAttrs.authPassword.name(), value);
+                               }
+                               if (key.equals(LdapAttrs.objectClasses.name())) {
+                                       Attribute attribute = new BasicAttribute(LdapAttrs.objectClass.name());
+                                       String[] objectClasses = value.toString().split("\n");
+                                       for (String objectClass : objectClasses) {
+                                               if (objectClass.trim().equals(""))
+                                                       continue;
+                                               attribute.add(objectClass);
+                                       }
+                                       Attribute previousAttribute = getModifiedAttributes().put(attribute);
+                                       if (previousAttribute != null)
+                                               return previousAttribute.get();
+                                       else
+                                               return null;
+                               }
 
-                       getDirectory().checkEdit();
-                       if (!isEditing())
-                               startEditing();
+                               getDirectory().checkEdit();
+                               if (!isEditing())
+                                       startEditing();
 
-                       if (!(value instanceof String || value instanceof byte[]))
-                               throw new IllegalArgumentException("Value must be String or byte[]");
+                               if (!(value instanceof String || value instanceof byte[]))
+                                       throw new IllegalArgumentException("Value must be String or byte[]");
 
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
+                               if (includeFilter && !attrFilter.contains(key))
+                                       throw new IllegalArgumentException("Key " + key + " not included");
+                               else if (!includeFilter && attrFilter.contains(key))
+                                       throw new IllegalArgumentException("Key " + key + " excluded");
 
-                       try {
                                Attribute attribute = getModifiedAttributes().get(key.toString());
                                // if (attribute == null) // block unit tests
                                attribute = new BasicAttribute(key.toString());
index fac7dd1acf582bb47d7b080bdf70fb8f9cf09b8c..b1c0c9849a14cb6abdd5214ad239912e591d887f 100644 (file)
@@ -278,6 +278,8 @@ public class LdapDao extends AbstractLdapDirectoryDao {
                                throw new IllegalArgumentException(dn + " does not start with base DN " + getDirectory().getBaseDn());
                        Attributes attrs = ldapConnection.getAttributes(dn);
                        return new LdapHierarchyUnit(getDirectory(), dn, attrs);
+               } catch (NameNotFoundException e) {
+                       return null;
                } catch (NamingException e) {
                        throw new IllegalStateException("Cannot get hierarchy unit " + dn, e);
                }