Make CmsUserManager an API.
authorMathieu <mbaudier@argeo.org>
Sat, 12 Nov 2022 07:14:34 +0000 (08:14 +0100)
committerMathieu <mbaudier@argeo.org>
Sat, 12 Nov 2022 07:14:34 +0000 (08:14 +0100)
13 files changed:
org.argeo.api.cms/bnd.bnd
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java
org.argeo.cms/OSGI-INF/cmsContentRepository.xml
org.argeo.cms/OSGI-INF/cmsUserManager.xml
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java [deleted file]
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java
org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java

index 51c4e663c9a1a6e5cc7f1507e230b6dd61e42840..3517484841e881db7bbf6cf8c3327cc949de3978 100644 (file)
@@ -1,4 +1,5 @@
 Import-Package: \
-javax.security.*
+javax.security.*,\
+org.osgi.service.useradmin,\
 
 Export-Package: org.argeo.api.cms.*
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java
new file mode 100644 (file)
index 0000000..422cade
--- /dev/null
@@ -0,0 +1,119 @@
+package org.argeo.api.cms.directory;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provide method interfaces to manage user concepts without accessing directly
+ * the userAdmin.
+ */
+public interface CmsUserManager {
+       Map<String, String> getKnownBaseDns(boolean onlyWritable);
+
+       Set<UserDirectory> getUserDirectories();
+
+       // CurrentUser
+       /** Returns the e-mail of the current logged in user */
+       String getMyMail();
+
+       // Other users
+       /** Returns a {@link User} given a username */
+       CmsUser getUser(String username);
+
+       /** Can be a group or a user */
+       String getUserDisplayName(String dn);
+
+       /** Can be a group or a user */
+       String getUserMail(String dn);
+
+       /** Lists all roles of the given user */
+       String[] getUserRoles(String dn);
+
+       /** Checks if the passed user belongs to the passed role */
+       boolean isUserInRole(String userDn, String roleDn);
+
+       // Search
+       /** Returns a filtered list of roles */
+       Role[] getRoles(String filter);
+
+       /** Recursively lists users in a given group. */
+       Set<CmsUser> listUsersInGroup(String groupDn, String filter);
+
+       /** Search among groups including system roles and users if needed */
+       List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
+
+//     /**
+//      * Lists functional accounts, that is users with regular access to the system
+//      * under this functional hierarchy unit (which probably have technical direct
+//      * sub hierarchy units), excluding groups which are not explicitly users.
+//      */
+//     Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep);
+
+       /*
+        * EDITION
+        */
+       /** Creates a new user. */
+       CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials);
+
+       /** Creates a group. */
+       CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName);
+
+       /** Creates a new system role. */
+       CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole);
+
+       /** Add additional object classes to this role. */
+       void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties);
+
+       /** Add additional object classes to this hierarchy unit. */
+       void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+                       Map<String, Object> additionalProperties);
+
+       /** Add a member to this group. */
+       void addMember(CmsGroup group, Role role);
+
+       void edit(Runnable action);
+
+       /* MISCELLANEOUS */
+       /** Returns the dn of a role given its local ID */
+       String buildDefaultDN(String localId, int type);
+
+       /** Exposes the main default domain name for this instance */
+       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.
+        */
+       CmsUser getUserFromLocalId(String localId);
+
+       void changeOwnPassword(char[] oldPassword, char[] newPassword);
+
+       void resetPassword(String username, char[] newPassword);
+
+       @Deprecated
+       String addSharedSecret(String username, int hours);
+
+//     String addSharedSecret(String username, String authInfo, String authToken);
+
+       void addAuthToken(String userDn, String token, Integer hours, String... roles);
+
+       void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
+
+       void expireAuthToken(String token);
+
+       void expireAuthTokens(Subject subject);
+
+       UserDirectory getDirectory(Role role);
+
+       /** Create a new hierarchy unit. Does nothing if it already exists. */
+       HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path);
+}
\ No newline at end of file
index e170609bb6a2bed41cd660b2d8c5c807b3447e62..c355ecd8d93eb8f570a2f22476a4dc64fae359b8 100644 (file)
@@ -17,7 +17,7 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.argeo.api.acr.ldap.NamingUtils;
 import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsUserManager;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.cms.auth.RemoteAuthCallback;
 import org.argeo.cms.auth.RemoteAuthCallbackHandler;
 import org.argeo.cms.servlet.ServletHttpRequest;
index b7a13b4d10041b5d1399831ec51a409fbb5ac298..306717b1c24fdaed17065ef18760ac352bc6380e 100644 (file)
@@ -8,5 +8,5 @@
    </service>
    <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
    <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
-   <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+   <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.api.cms.directory.CmsUserManager" name="CmsUserManager" policy="static"/>
 </scr:component>
index 1f0033171a68ef45b270b66cb6bf998e591d7e2a..d76c89a1b858d4e1e5f145ba31ae1674817dc05c 100644 (file)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="User Admin Service">
-   <implementation class="org.argeo.cms.internal.auth.CmsUserManagerImpl"/>
+   <implementation class="org.argeo.cms.internal.runtime.CmsUserManagerImpl"/>
    <service>
-      <provide interface="org.argeo.cms.CmsUserManager"/>
+      <provide interface="org.argeo.api.cms.directory.CmsUserManager"/>
    </service>
    <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
    <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
deleted file mode 100644 (file)
index f87d28b..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.argeo.cms;
-
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.cms.directory.CmsGroup;
-import org.argeo.api.cms.directory.CmsUser;
-import org.argeo.api.cms.directory.HierarchyUnit;
-import org.argeo.api.cms.directory.UserDirectory;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/**
- * Provide method interfaces to manage user concepts without accessing directly
- * the userAdmin.
- */
-public interface CmsUserManager {
-       Map<String, String> getKnownBaseDns(boolean onlyWritable);
-
-       Set<UserDirectory> getUserDirectories();
-
-       // CurrentUser
-       /** Returns the e-mail of the current logged in user */
-       String getMyMail();
-
-       // Other users
-       /** Returns a {@link User} given a username */
-       CmsUser getUser(String username);
-
-       /** Can be a group or a user */
-       String getUserDisplayName(String dn);
-
-       /** Can be a group or a user */
-       String getUserMail(String dn);
-
-       /** Lists all roles of the given user */
-       String[] getUserRoles(String dn);
-
-       /** Checks if the passed user belongs to the passed role */
-       boolean isUserInRole(String userDn, String roleDn);
-
-       // Search
-       /** Returns a filtered list of roles */
-       Role[] getRoles(String filter) throws InvalidSyntaxException;
-
-       /** Recursively lists users in a given group. */
-       Set<CmsUser> listUsersInGroup(String groupDn, String filter);
-
-       /** Search among groups including system roles and users if needed */
-       List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
-
-//     /**
-//      * Lists functional accounts, that is users with regular access to the system
-//      * under this functional hierarchy unit (which probably have technical direct
-//      * sub hierarchy units), excluding groups which are not explicitly users.
-//      */
-//     Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep);
-
-       /*
-        * EDITION
-        */
-       /** Creates a new user. */
-       CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials);
-
-       /** Creates a group. */
-       CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName);
-
-       /** Creates a new system role. */
-       CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole);
-
-       /** Add additional object classes to this role. */
-       void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties);
-
-       /** Add additional object classes to this hierarchy unit. */
-       void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
-                       Map<String, Object> additionalProperties);
-
-       /** Add a member to this group. */
-       void addMember(CmsGroup group, Role role);
-
-       void edit(Runnable action);
-
-       /* MISCELLANEOUS */
-       /** Returns the dn of a role given its local ID */
-       String buildDefaultDN(String localId, int type);
-
-       /** Exposes the main default domain name for this instance */
-       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.
-        */
-       CmsUser getUserFromLocalId(String localId);
-
-       void changeOwnPassword(char[] oldPassword, char[] newPassword);
-
-       void resetPassword(String username, char[] newPassword);
-
-       @Deprecated
-       String addSharedSecret(String username, int hours);
-
-//     String addSharedSecret(String username, String authInfo, String authToken);
-
-       void addAuthToken(String userDn, String token, Integer hours, String... roles);
-
-       void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
-
-       void expireAuthToken(String token);
-
-       void expireAuthTokens(Subject subject);
-
-       UserDirectory getDirectory(Role role);
-
-       /** Create a new hierarchy unit. Does nothing if it already exists. */
-       HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path);
-}
\ No newline at end of file
index ed27ce8e8366a24c5fcd2ce0e285026c3ff1afa9..d324ac475283530ae658498f0bce39f305897225 100644 (file)
@@ -16,9 +16,9 @@ import org.argeo.api.acr.ContentSession;
 import org.argeo.api.acr.DName;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
-import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.util.CurrentSubject;
 import org.osgi.service.useradmin.Role;
 
index 9af83a33072bf2fc56695beae5e992942b15fff4..68314b91210da4d4fc96cd4bbe345d720b627b1f 100644 (file)
@@ -14,9 +14,9 @@ import org.argeo.api.acr.ContentNotFoundException;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.directory.HierarchyUnit;
 import org.argeo.api.cms.directory.UserDirectory;
-import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.acr.AbstractContent;
 import org.argeo.cms.acr.ContentUtils;
 import org.osgi.service.useradmin.User;
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
deleted file mode 100644 (file)
index 94262a5..0000000
+++ /dev/null
@@ -1,714 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import static org.argeo.api.acr.ldap.LdapAttrs.cn;
-import static org.argeo.api.acr.ldap.LdapAttrs.description;
-import static org.argeo.api.acr.ldap.LdapAttrs.owner;
-
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.UUID;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.api.acr.ldap.LdapAttrs;
-import org.argeo.api.acr.ldap.NamingUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.directory.CmsGroup;
-import org.argeo.api.cms.directory.CmsUser;
-import org.argeo.api.cms.directory.HierarchyUnit;
-import org.argeo.api.cms.directory.UserDirectory;
-import org.argeo.api.cms.transaction.WorkTransaction;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.directory.ldap.LdapEntry;
-import org.argeo.cms.directory.ldap.SharedSecret;
-import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.cms.osgi.useradmin.TokenUtils;
-import org.argeo.cms.runtime.DirectoryConf;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Canonical implementation of the people {@link CmsUserManager}. Wraps
- * interaction with users and groups.
- * 
- * In a *READ-ONLY* mode. We want to be able to:
- * <ul>
- * <li>Retrieve my user and corresponding information (main info,
- * groups...)</li>
- * <li>List all local groups (not the system roles)</li>
- * <li>If sufficient rights: retrieve a given user and its information</li>
- * </ul>
- */
-public class CmsUserManagerImpl implements CmsUserManager {
-       private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
-
-       private UserAdmin userAdmin;
-//     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<>());
-
-       private Set<UserDirectory> userDirectories = new HashSet<>();
-
-       public void start() {
-               log.debug(() -> "CMS user manager available");
-       }
-
-       public void stop() {
-
-       }
-
-       @Override
-       public String getMyMail() {
-               return getUserMail(CurrentUser.getUsername());
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               return userAdmin.getRoles(filter);
-       }
-
-       // ALL USER: WARNING access to this will be later reduced
-
-       /** Retrieve a user given his dn, or <code>null</code> if it doesn't exist. */
-       public CmsUser getUser(String dn) {
-               return (CmsUser) getUserAdmin().getRole(dn);
-       }
-
-       /** Can be a group or a user */
-       public String getUserDisplayName(String dn) {
-               // FIXME: during initialisation phase, the system logs "admin" as user
-               // name rather than the corresponding dn
-               if ("admin".equals(dn))
-                       return "System Administrator";
-               else
-                       return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
-       }
-
-       @Override
-       public String getUserMail(String dn) {
-               return UserAdminUtils.getUserMail(getUserAdmin(), dn);
-       }
-
-       /** Lists all roles of the given user */
-       @Override
-       public String[] getUserRoles(String dn) {
-               Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
-               return currAuth.getRoles();
-       }
-
-       @Override
-       public boolean isUserInRole(String userDn, String roleDn) {
-               String[] roles = getUserRoles(userDn);
-               for (String role : roles) {
-                       if (role.equalsIgnoreCase(roleDn))
-                               return true;
-               }
-               return false;
-       }
-
-       public Set<CmsUser> listUsersInGroup(String groupDn, String filter) {
-               Group group = (Group) userAdmin.getRole(groupDn);
-               if (group == null)
-                       throw new IllegalArgumentException("Group " + groupDn + " not found");
-               Set<CmsUser> users = new HashSet<>();
-               addUsers(users, group, filter);
-               return users;
-       }
-
-//     @Override
-//     public Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep) {
-//             if(!hierarchyUnit.isFunctional())
-//                     throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional");
-//             UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory();
-//             Set<User> res = new HashSet<>();
-//             for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) {
-//                     if(technicalHu.isFunctional())
-//                             continue;
-//                     for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) {
-//                             if(role)
-//                     }
-//             }
-//             return res;
-//     }
-
-       /** Recursively add users to list */
-       private void addUsers(Set<CmsUser> users, Group group, String filter) {
-               Role[] roles = group.getMembers();
-               for (Role role : roles) {
-                       if (role.getType() == Role.GROUP) {
-                               addUsers(users, (CmsGroup) role, filter);
-                       } else if (role.getType() == Role.USER) {
-                               if (match(role, filter))
-                                       users.add((CmsUser) role);
-                       } else {
-                               // ignore
-                       }
-               }
-       }
-
-       public List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
-               Role[] roles = null;
-               try {
-                       roles = getUserAdmin().getRoles(filter);
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
-               }
-
-               List<CmsUser> users = new ArrayList<>();
-               for (Role role : roles) {
-                       if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
-                                       && (includeSystemRoles
-                                                       || !role.getName().toLowerCase().endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))) {
-                               if (match(role, filter))
-                                       users.add((CmsUser) role);
-                       }
-               }
-               return users;
-       }
-
-       private boolean match(Role role, String filter) {
-               boolean doFilter = filter != null && !"".equals(filter);
-               if (doFilter) {
-                       for (String prop : knownProps) {
-                               Object currProp = null;
-                               try {
-                                       currProp = role.getProperties().get(prop);
-                               } catch (Exception e) {
-                                       throw e;
-                               }
-                               if (currProp != null) {
-                                       String currPropStr = ((String) currProp).toLowerCase();
-                                       if (currPropStr.contains(filter.toLowerCase())) {
-                                               return true;
-                                       }
-                               }
-                       }
-                       return false;
-               } else
-                       return true;
-       }
-
-       @Override
-       public CmsUser getUserFromLocalId(String localId) {
-               CmsUser user = (CmsUser) getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
-               if (user == null)
-                       user = (CmsUser) getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
-               return user;
-       }
-
-       @Override
-       public String buildDefaultDN(String localId, int type) {
-               return buildDistinguishedName(localId, getDefaultDomainName(), type);
-       }
-
-       /*
-        * EDITION
-        */
-       @Override
-       public CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
-               try {
-                       userTransaction.begin();
-                       CmsUser user = (CmsUser) 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 CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName) {
-               try {
-                       String dn = LdapAttrs.cn.name() + "=" + commonName + "," + groups.getBase();
-                       CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
-                       if (group != null)
-                               return group;
-                       userTransaction.begin();
-                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
-                       userTransaction.commit();
-                       return group;
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e);
-               }
-       }
-
-       @Override
-       public CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole) {
-               try {
-                       String dn = LdapAttrs.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole) + ","
-                                       + roles.getBase();
-                       CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
-                       if (group != null)
-                               return group;
-                       userTransaction.begin();
-                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
-                       userTransaction.commit();
-                       return group;
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e);
-               }
-       }
-
-       @Override
-       public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) {
-               HierarchyUnit hi = directory.getHierarchyUnit(path);
-               if (hi != null)
-                       return hi;
-               try {
-                       userTransaction.begin();
-                       HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path);
-                       userTransaction.commit();
-                       return hierarchyUnit;
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1);
-               }
-       }
-
-       @Override
-       public void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
-               try {
-                       userTransaction.begin();
-                       LdapEntry.addObjectClasses(role.getProperties(), objectClasses);
-                       for (String key : additionalProperties.keySet()) {
-                               role.getProperties().put(key, additionalProperties.get(key));
-                       }
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1);
-               }
-       }
-
-       @Override
-       public void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
-                       Map<String, Object> additionalProperties) {
-               try {
-                       userTransaction.begin();
-                       LdapEntry.addObjectClasses(hierarchyUnit.getProperties(), objectClasses);
-                       for (String key : additionalProperties.keySet()) {
-                               hierarchyUnit.getProperties().put(key, additionalProperties.get(key));
-                       }
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + hierarchyUnit, e1);
-               }
-       }
-
-       @Override
-       public void edit(Runnable action) {
-               Objects.requireNonNull(action);
-               try {
-                       userTransaction.begin();
-                       action.run();
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot edit", e1);
-               }
-       }
-
-       @Override
-       public void addMember(CmsGroup group, Role role) {
-               try {
-                       userTransaction.begin();
-                       group.addMember(role);
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add object classes " + role + " to group " + group, e1);
-               }
-       }
-
-       @Override
-       public String getDefaultDomainName() {
-               Map<String, String> dns = getKnownBaseDns(true);
-               if (dns.size() == 1)
-                       return dns.keySet().iterator().next();
-               else
-                       throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
-                                       + dns.keySet().toString() + ". Unable to chose a default one.");
-       }
-
-       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-               Map<String, String> dns = new HashMap<String, String>();
-               for (UserDirectory userDirectory : userDirectories) {
-                       Boolean readOnly = userDirectory.isReadOnly();
-                       String baseDn = userDirectory.getBase();
-
-                       if (onlyWritable && readOnly)
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN))
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
-                               continue;
-                       dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString());
-
-               }
-               return dns;
-       }
-
-       public Set<UserDirectory> getUserDirectories() {
-               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
-               res.addAll(userDirectories);
-               return res;
-       }
-
-       public String buildDistinguishedName(String localId, String baseDn, int type) {
-               Map<String, String> dns = getKnownBaseDns(true);
-               Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(baseDn));
-               String dn = null;
-               if (Role.GROUP == type)
-                       dn = LdapAttrs.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn;
-               else if (Role.USER == type)
-                       dn = LdapAttrs.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn;
-               else
-                       throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
-               return dn;
-       }
-
-       @Override
-       public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
-               String name = CurrentUser.getUsername();
-               LdapName dn;
-               try {
-                       dn = new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Invalid user dn " + name, e);
-               }
-               User user = (User) userAdmin.getRole(dn.toString());
-               if (!user.hasCredential(null, oldPassword))
-                       throw new IllegalArgumentException("Invalid password");
-               if (Arrays.equals(newPassword, new char[0]))
-                       throw new IllegalArgumentException("New password empty");
-               try {
-                       userTransaction.begin();
-                       user.getCredentials().put(null, newPassword);
-                       userTransaction.commit();
-               } 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 change password", e);
-               }
-       }
-
-       public void resetPassword(String username, char[] newPassword) {
-               LdapName dn;
-               try {
-                       dn = new LdapName(username);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Invalid user dn " + username, e);
-               }
-               User user = (User) userAdmin.getRole(dn.toString());
-               if (Arrays.equals(newPassword, new char[0]))
-                       throw new IllegalArgumentException("New password empty");
-               try {
-                       userTransaction.begin();
-                       user.getCredentials().put(null, newPassword);
-                       userTransaction.commit();
-               } 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 change password", e);
-               }
-       }
-
-       public String addSharedSecret(String email, int hours) {
-               User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
-               try {
-                       userTransaction.begin();
-                       String uuid = UUID.randomUUID().toString();
-                       SharedSecret sharedSecret = new SharedSecret(hours, uuid);
-                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
-                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
-                       userTransaction.commit();
-                       return tokenStr;
-               } 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 change password", e);
-               }
-       }
-
-       @Deprecated
-       public String addSharedSecret(String username, String authInfo, String authToken) {
-               try {
-                       userTransaction.begin();
-                       User user = (User) userAdmin.getRole(username);
-                       SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
-                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
-                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
-                       userTransaction.commit();
-                       return tokenStr;
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add shared secret", e1);
-               }
-       }
-
-       @Override
-       public void expireAuthToken(String token) {
-               try {
-                       userTransaction.begin();
-                       String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
-                       Group tokenGroup = (Group) userAdmin.getRole(dn);
-                       String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
-                       tokenGroup.getProperties().put(description.name(), ldapDate);
-                       userTransaction.commit();
-                       if (log.isDebugEnabled())
-                               log.debug("Token " + token + " expired.");
-               } 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 expire token", e1);
-               }
-       }
-
-       @Override
-       public void expireAuthTokens(Subject subject) {
-               Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
-               for (String token : tokens)
-                       expireAuthToken(token);
-       }
-
-       @Override
-       public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
-               addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
-       }
-
-       @Override
-       public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
-               try {
-                       userTransaction.begin();
-                       User user = (User) userAdmin.getRole(userDn);
-                       String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
-                       Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
-                       if (roles != null)
-                               for (String role : roles) {
-                                       Role r = userAdmin.getRole(role);
-                                       if (r != null)
-                                               tokenGroup.addMember(r);
-                                       else {
-                                               if (!role.equals(CmsConstants.ROLE_USER)) {
-                                                       throw new IllegalStateException(
-                                                                       "Cannot add role " + role + " to token " + token + " for " + userDn);
-                                               }
-                                       }
-                               }
-                       tokenGroup.getProperties().put(owner.name(), user.getName());
-                       if (expiryDate != null) {
-                               String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
-                               tokenGroup.getProperties().put(description.name(), ldapDate);
-                       }
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add token", e1);
-               }
-       }
-
-       @Override
-       public UserDirectory getDirectory(Role user) {
-               String name = user.getName();
-               NavigableMap<String, UserDirectory> possible = new TreeMap<>();
-               for (UserDirectory userDirectory : userDirectories) {
-                       if (name.endsWith(userDirectory.getBase())) {
-                               possible.put(userDirectory.getBase(), userDirectory);
-                       }
-               }
-               if (possible.size() == 0)
-                       throw new IllegalStateException("No user directory found for user " + name);
-               return possible.lastEntry().getValue();
-       }
-
-//     public User createUserFromPerson(Node person) {
-//             String email = JcrUtils.get(person, LdapAttrs.mail.property());
-//             String dn = buildDefaultDN(email, Role.USER);
-//             User user;
-//             try {
-//                     userTransaction.begin();
-//                     user = (User) userAdmin.createRole(dn, Role.USER);
-//                     Dictionary<String, Object> userProperties = user.getProperties();
-//                     String name = JcrUtils.get(person, LdapAttrs.displayName.property());
-//                     userProperties.put(LdapAttrs.cn.name(), name);
-//                     userProperties.put(LdapAttrs.displayName.name(), name);
-//                     String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
-//                     String surname = JcrUtils.get(person, LdapAttrs.sn.property());
-//                     userProperties.put(LdapAttrs.givenName.name(), givenName);
-//                     userProperties.put(LdapAttrs.sn.name(), surname);
-//                     userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
-//                     userTransaction.commit();
-//             } 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", e);
-//             }
-//             return user;
-//     }
-
-       public UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-//     public UserTransaction getUserTransaction() {
-//             return userTransaction;
-//     }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdmin(UserAdmin userAdmin) {
-               this.userAdmin = userAdmin;
-
-               if (userAdmin instanceof AggregatingUserAdmin) {
-                       userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories();
-               } else {
-                       throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported.");
-               }
-
-//             this.serviceProperties = serviceProperties;
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-//     public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
-//             userDirectories.put(userDirectory, new Hashtable<>(properties));
-//     }
-//
-//     public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
-//             userDirectories.remove(userDirectory);
-//     }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java
new file mode 100644 (file)
index 0000000..6c60015
--- /dev/null
@@ -0,0 +1,717 @@
+package org.argeo.cms.internal.runtime;
+
+import static org.argeo.api.acr.ldap.LdapAttrs.cn;
+import static org.argeo.api.acr.ldap.LdapAttrs.description;
+import static org.argeo.api.acr.ldap.LdapAttrs.owner;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttrs;
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.SharedSecret;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.TokenUtils;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Canonical implementation of the people {@link CmsUserManager}. Wraps
+ * interaction with users and groups.
+ * 
+ * In a *READ-ONLY* mode. We want to be able to:
+ * <ul>
+ * <li>Retrieve my user and corresponding information (main info,
+ * groups...)</li>
+ * <li>List all local groups (not the system roles)</li>
+ * <li>If sufficient rights: retrieve a given user and its information</li>
+ * </ul>
+ */
+public class CmsUserManagerImpl implements CmsUserManager {
+       private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
+
+       private UserAdmin userAdmin;
+//     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<>());
+
+       private Set<UserDirectory> userDirectories = new HashSet<>();
+
+       public void start() {
+               log.debug(() -> "CMS user manager available");
+       }
+
+       public void stop() {
+
+       }
+
+       @Override
+       public String getMyMail() {
+               return getUserMail(CurrentUser.getUsername());
+       }
+
+       @Override
+       public Role[] getRoles(String filter) {
+               try {
+                       return userAdmin.getRoles(filter);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Invalid filter " + filter, e);
+               }
+       }
+
+       // ALL USER: WARNING access to this will be later reduced
+
+       /** Retrieve a user given his dn, or <code>null</code> if it doesn't exist. */
+       public CmsUser getUser(String dn) {
+               return (CmsUser) getUserAdmin().getRole(dn);
+       }
+
+       /** Can be a group or a user */
+       public String getUserDisplayName(String dn) {
+               // FIXME: during initialisation phase, the system logs "admin" as user
+               // name rather than the corresponding dn
+               if ("admin".equals(dn))
+                       return "System Administrator";
+               else
+                       return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
+       }
+
+       @Override
+       public String getUserMail(String dn) {
+               return UserAdminUtils.getUserMail(getUserAdmin(), dn);
+       }
+
+       /** Lists all roles of the given user */
+       @Override
+       public String[] getUserRoles(String dn) {
+               Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
+               return currAuth.getRoles();
+       }
+
+       @Override
+       public boolean isUserInRole(String userDn, String roleDn) {
+               String[] roles = getUserRoles(userDn);
+               for (String role : roles) {
+                       if (role.equalsIgnoreCase(roleDn))
+                               return true;
+               }
+               return false;
+       }
+
+       public Set<CmsUser> listUsersInGroup(String groupDn, String filter) {
+               Group group = (Group) userAdmin.getRole(groupDn);
+               if (group == null)
+                       throw new IllegalArgumentException("Group " + groupDn + " not found");
+               Set<CmsUser> users = new HashSet<>();
+               addUsers(users, group, filter);
+               return users;
+       }
+
+//     @Override
+//     public Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep) {
+//             if(!hierarchyUnit.isFunctional())
+//                     throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional");
+//             UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory();
+//             Set<User> res = new HashSet<>();
+//             for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) {
+//                     if(technicalHu.isFunctional())
+//                             continue;
+//                     for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) {
+//                             if(role)
+//                     }
+//             }
+//             return res;
+//     }
+
+       /** Recursively add users to list */
+       private void addUsers(Set<CmsUser> users, Group group, String filter) {
+               Role[] roles = group.getMembers();
+               for (Role role : roles) {
+                       if (role.getType() == Role.GROUP) {
+                               addUsers(users, (CmsGroup) role, filter);
+                       } else if (role.getType() == Role.USER) {
+                               if (match(role, filter))
+                                       users.add((CmsUser) role);
+                       } else {
+                               // ignore
+                       }
+               }
+       }
+
+       public List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
+               Role[] roles = null;
+               try {
+                       roles = getUserAdmin().getRoles(filter);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
+               }
+
+               List<CmsUser> users = new ArrayList<>();
+               for (Role role : roles) {
+                       if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
+                                       && (includeSystemRoles
+                                                       || !role.getName().toLowerCase().endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))) {
+                               if (match(role, filter))
+                                       users.add((CmsUser) role);
+                       }
+               }
+               return users;
+       }
+
+       private boolean match(Role role, String filter) {
+               boolean doFilter = filter != null && !"".equals(filter);
+               if (doFilter) {
+                       for (String prop : knownProps) {
+                               Object currProp = null;
+                               try {
+                                       currProp = role.getProperties().get(prop);
+                               } catch (Exception e) {
+                                       throw e;
+                               }
+                               if (currProp != null) {
+                                       String currPropStr = ((String) currProp).toLowerCase();
+                                       if (currPropStr.contains(filter.toLowerCase())) {
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               } else
+                       return true;
+       }
+
+       @Override
+       public CmsUser getUserFromLocalId(String localId) {
+               CmsUser user = (CmsUser) getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
+               if (user == null)
+                       user = (CmsUser) getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
+               return user;
+       }
+
+       @Override
+       public String buildDefaultDN(String localId, int type) {
+               return buildDistinguishedName(localId, getDefaultDomainName(), type);
+       }
+
+       /*
+        * EDITION
+        */
+       @Override
+       public CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
+               try {
+                       userTransaction.begin();
+                       CmsUser user = (CmsUser) 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 CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName) {
+               try {
+                       String dn = LdapAttrs.cn.name() + "=" + commonName + "," + groups.getBase();
+                       CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+                       if (group != null)
+                               return group;
+                       userTransaction.begin();
+                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+                       userTransaction.commit();
+                       return group;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e);
+               }
+       }
+
+       @Override
+       public CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole) {
+               try {
+                       String dn = LdapAttrs.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole) + "," + roles.getBase();
+                       CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+                       if (group != null)
+                               return group;
+                       userTransaction.begin();
+                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+                       userTransaction.commit();
+                       return group;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e);
+               }
+       }
+
+       @Override
+       public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) {
+               HierarchyUnit hi = directory.getHierarchyUnit(path);
+               if (hi != null)
+                       return hi;
+               try {
+                       userTransaction.begin();
+                       HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path);
+                       userTransaction.commit();
+                       return hierarchyUnit;
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1);
+               }
+       }
+
+       @Override
+       public void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
+               try {
+                       userTransaction.begin();
+                       LdapEntry.addObjectClasses(role.getProperties(), objectClasses);
+                       for (String key : additionalProperties.keySet()) {
+                               role.getProperties().put(key, additionalProperties.get(key));
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1);
+               }
+       }
+
+       @Override
+       public void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+                       Map<String, Object> additionalProperties) {
+               try {
+                       userTransaction.begin();
+                       LdapEntry.addObjectClasses(hierarchyUnit.getProperties(), objectClasses);
+                       for (String key : additionalProperties.keySet()) {
+                               hierarchyUnit.getProperties().put(key, additionalProperties.get(key));
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + hierarchyUnit, e1);
+               }
+       }
+
+       @Override
+       public void edit(Runnable action) {
+               Objects.requireNonNull(action);
+               try {
+                       userTransaction.begin();
+                       action.run();
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot edit", e1);
+               }
+       }
+
+       @Override
+       public void addMember(CmsGroup group, Role role) {
+               try {
+                       userTransaction.begin();
+                       group.addMember(role);
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add object classes " + role + " to group " + group, e1);
+               }
+       }
+
+       @Override
+       public String getDefaultDomainName() {
+               Map<String, String> dns = getKnownBaseDns(true);
+               if (dns.size() == 1)
+                       return dns.keySet().iterator().next();
+               else
+                       throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
+                                       + dns.keySet().toString() + ". Unable to chose a default one.");
+       }
+
+       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
+               Map<String, String> dns = new HashMap<String, String>();
+               for (UserDirectory userDirectory : userDirectories) {
+                       Boolean readOnly = userDirectory.isReadOnly();
+                       String baseDn = userDirectory.getBase();
+
+                       if (onlyWritable && readOnly)
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN))
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
+                               continue;
+                       dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString());
+
+               }
+               return dns;
+       }
+
+       public Set<UserDirectory> getUserDirectories() {
+               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
+               res.addAll(userDirectories);
+               return res;
+       }
+
+       public String buildDistinguishedName(String localId, String baseDn, int type) {
+               Map<String, String> dns = getKnownBaseDns(true);
+               Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(baseDn));
+               String dn = null;
+               if (Role.GROUP == type)
+                       dn = LdapAttrs.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn;
+               else if (Role.USER == type)
+                       dn = LdapAttrs.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn;
+               else
+                       throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
+               return dn;
+       }
+
+       @Override
+       public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
+               String name = CurrentUser.getUsername();
+               LdapName dn;
+               try {
+                       dn = new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid user dn " + name, e);
+               }
+               User user = (User) userAdmin.getRole(dn.toString());
+               if (!user.hasCredential(null, oldPassword))
+                       throw new IllegalArgumentException("Invalid password");
+               if (Arrays.equals(newPassword, new char[0]))
+                       throw new IllegalArgumentException("New password empty");
+               try {
+                       userTransaction.begin();
+                       user.getCredentials().put(null, newPassword);
+                       userTransaction.commit();
+               } 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 change password", e);
+               }
+       }
+
+       public void resetPassword(String username, char[] newPassword) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid user dn " + username, e);
+               }
+               User user = (User) userAdmin.getRole(dn.toString());
+               if (Arrays.equals(newPassword, new char[0]))
+                       throw new IllegalArgumentException("New password empty");
+               try {
+                       userTransaction.begin();
+                       user.getCredentials().put(null, newPassword);
+                       userTransaction.commit();
+               } 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 change password", e);
+               }
+       }
+
+       public String addSharedSecret(String email, int hours) {
+               User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
+               try {
+                       userTransaction.begin();
+                       String uuid = UUID.randomUUID().toString();
+                       SharedSecret sharedSecret = new SharedSecret(hours, uuid);
+                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+                       userTransaction.commit();
+                       return tokenStr;
+               } 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 change password", e);
+               }
+       }
+
+       @Deprecated
+       public String addSharedSecret(String username, String authInfo, String authToken) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.getRole(username);
+                       SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
+                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+                       userTransaction.commit();
+                       return tokenStr;
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add shared secret", e1);
+               }
+       }
+
+       @Override
+       public void expireAuthToken(String token) {
+               try {
+                       userTransaction.begin();
+                       String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+                       Group tokenGroup = (Group) userAdmin.getRole(dn);
+                       String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
+                       tokenGroup.getProperties().put(description.name(), ldapDate);
+                       userTransaction.commit();
+                       if (log.isDebugEnabled())
+                               log.debug("Token " + token + " expired.");
+               } 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 expire token", e1);
+               }
+       }
+
+       @Override
+       public void expireAuthTokens(Subject subject) {
+               Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
+               for (String token : tokens)
+                       expireAuthToken(token);
+       }
+
+       @Override
+       public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
+               addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
+       }
+
+       @Override
+       public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.getRole(userDn);
+                       String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+                       Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
+                       if (roles != null)
+                               for (String role : roles) {
+                                       Role r = userAdmin.getRole(role);
+                                       if (r != null)
+                                               tokenGroup.addMember(r);
+                                       else {
+                                               if (!role.equals(CmsConstants.ROLE_USER)) {
+                                                       throw new IllegalStateException(
+                                                                       "Cannot add role " + role + " to token " + token + " for " + userDn);
+                                               }
+                                       }
+                               }
+                       tokenGroup.getProperties().put(owner.name(), user.getName());
+                       if (expiryDate != null) {
+                               String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
+                               tokenGroup.getProperties().put(description.name(), ldapDate);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add token", e1);
+               }
+       }
+
+       @Override
+       public UserDirectory getDirectory(Role user) {
+               String name = user.getName();
+               NavigableMap<String, UserDirectory> possible = new TreeMap<>();
+               for (UserDirectory userDirectory : userDirectories) {
+                       if (name.endsWith(userDirectory.getBase())) {
+                               possible.put(userDirectory.getBase(), userDirectory);
+                       }
+               }
+               if (possible.size() == 0)
+                       throw new IllegalStateException("No user directory found for user " + name);
+               return possible.lastEntry().getValue();
+       }
+
+//     public User createUserFromPerson(Node person) {
+//             String email = JcrUtils.get(person, LdapAttrs.mail.property());
+//             String dn = buildDefaultDN(email, Role.USER);
+//             User user;
+//             try {
+//                     userTransaction.begin();
+//                     user = (User) userAdmin.createRole(dn, Role.USER);
+//                     Dictionary<String, Object> userProperties = user.getProperties();
+//                     String name = JcrUtils.get(person, LdapAttrs.displayName.property());
+//                     userProperties.put(LdapAttrs.cn.name(), name);
+//                     userProperties.put(LdapAttrs.displayName.name(), name);
+//                     String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
+//                     String surname = JcrUtils.get(person, LdapAttrs.sn.property());
+//                     userProperties.put(LdapAttrs.givenName.name(), givenName);
+//                     userProperties.put(LdapAttrs.sn.name(), surname);
+//                     userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
+//                     userTransaction.commit();
+//             } 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", e);
+//             }
+//             return user;
+//     }
+
+       public UserAdmin getUserAdmin() {
+               return userAdmin;
+       }
+
+//     public UserTransaction getUserTransaction() {
+//             return userTransaction;
+//     }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdmin(UserAdmin userAdmin) {
+               this.userAdmin = userAdmin;
+
+               if (userAdmin instanceof AggregatingUserAdmin) {
+                       userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories();
+               } else {
+                       throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported.");
+               }
+
+//             this.serviceProperties = serviceProperties;
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+//     public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+//             userDirectories.put(userDirectory, new Hashtable<>(properties));
+//     }
+//
+//     public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+//             userDirectories.remove(userDirectory);
+//     }
+
+}
index 8358105e2982db480f79369db507b14f471b0d90..0fd0a63edb4cb47acce19bbb38f4fc07b4edd672 100644 (file)
@@ -5,7 +5,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsUserManager;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.cms.acr.CmsContentRepository;
 import org.argeo.cms.acr.directory.DirectoryContentProvider;
 import org.argeo.cms.acr.fs.FsContentProvider;
index 0034e3f7b1a0ef60930a9316d3257931f32d1afe..76775fed8060f912bed6a725df629113fc8f211e 100644 (file)
@@ -9,6 +9,7 @@ import org.argeo.api.acr.spi.ProvidedRepository;
 import org.argeo.api.cms.CmsContext;
 import org.argeo.api.cms.CmsDeployment;
 import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.transaction.SimpleTransactionManager;
 import org.argeo.api.cms.transaction.WorkControl;
 import org.argeo.api.cms.transaction.WorkTransaction;
@@ -16,13 +17,12 @@ import org.argeo.api.register.Component;
 import org.argeo.api.register.ComponentRegister;
 import org.argeo.api.register.SimpleRegister;
 import org.argeo.api.uuid.UuidFactory;
-import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.acr.CmsUuidFactory;
-import org.argeo.cms.internal.auth.CmsUserManagerImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
 import org.argeo.cms.internal.runtime.CmsStateImpl;
 import org.argeo.cms.internal.runtime.CmsUserAdmin;
+import org.argeo.cms.internal.runtime.CmsUserManagerImpl;
 import org.argeo.cms.internal.runtime.DeployedContentRepository;
 import org.osgi.service.useradmin.UserAdmin;
 
index dedf61dea0f31acd0a4ac01e90a00218d5b2abc9..296247094b4d1f863629f490a6d1ab765f41eee0 100644 (file)
@@ -4,9 +4,9 @@ import java.util.Arrays;
 import java.util.concurrent.Callable;
 
 import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsUserManager;
 import org.argeo.api.cms.ux.CmsView;
 import org.argeo.cms.CmsMsg;
-import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.swt.CmsSwtUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Composite;