Introduce CmsUserManager (from Argeo Connect's UserAdminService)
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 2 Mar 2020 08:22:35 +0000 (09:22 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 2 Mar 2020 08:22:35 +0000 (09:22 +0100)
22 files changed:
org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java
org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java
org.argeo.cms.ui/src/org/argeo/cms/util/UserAdminUtils.java [deleted file]
org.argeo.cms/.project
org.argeo.cms/OSGI-INF/cmsUserManager.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/build.properties
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java [new file with mode: 0644]

index 518be8ce59db78715ecf839d64d55612576811a4..3c9347a71381f4f8f3644b521e35627bce6a4ee7 100644 (file)
@@ -22,10 +22,10 @@ import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
 import org.argeo.cms.ui.eclipse.forms.IManagedForm;
 import org.argeo.cms.ui.eclipse.forms.ManagedForm;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.naming.LdapAttrs;
 import org.eclipse.core.runtime.IProgressMonitor;
index b6108600ed19bdddfb1ff9ec1fcc9724be4ec70e..a2c920aa8e8e4ba9729e6eba7ee50989dc45122b 100644 (file)
@@ -15,7 +15,7 @@
  */
 package org.argeo.cms.e4.users;
 
-import static org.argeo.cms.util.UserAdminUtils.setProperty;
+import static org.argeo.cms.auth.UserAdminUtils.setProperty;
 import static org.argeo.naming.LdapAttrs.businessCategory;
 import static org.argeo.naming.LdapAttrs.description;
 import static org.argeo.node.NodeInstance.WORKGROUP;
@@ -35,6 +35,7 @@ import javax.naming.ldap.LdapName;
 import javax.transaction.UserTransaction;
 
 import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.providers.CommonNameLP;
 import org.argeo.cms.e4.users.providers.MailLP;
 import org.argeo.cms.e4.users.providers.RoleIconLP;
@@ -42,7 +43,6 @@ import org.argeo.cms.e4.users.providers.UserFilter;
 import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
 import org.argeo.cms.ui.eclipse.forms.IManagedForm;
 import org.argeo.cms.util.CmsUtils;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.eclipse.ui.ColumnDefinition;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.eclipse.ui.parts.LdifUsersTable;
index 6ce51b80384442500df310948e27671dccfe3fe7..ed797c58a34ab1f4cb97ba33d21a0093d40f05af 100644 (file)
@@ -12,11 +12,11 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.providers.CommonNameLP;
 import org.argeo.cms.e4.users.providers.DomainNameLP;
 import org.argeo.cms.e4.users.providers.MailLP;
 import org.argeo.cms.e4.users.providers.UserNameLP;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.eclipse.ui.ColumnDefinition;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.eclipse.ui.parts.LdifUsersTable;
index f7d6706fcb3beed7978abaccb34dd8e476067c99..a35499984673b46afd9f6dec5f22c97207705ec1 100644 (file)
@@ -15,7 +15,7 @@
  */
 package org.argeo.cms.e4.users;
 
-import static org.argeo.cms.util.UserAdminUtils.getProperty;
+import static org.argeo.cms.auth.UserAdminUtils.getProperty;
 import static org.argeo.naming.LdapAttrs.cn;
 import static org.argeo.naming.LdapAttrs.givenName;
 import static org.argeo.naming.LdapAttrs.mail;
@@ -29,6 +29,7 @@ import java.util.List;
 import javax.inject.Inject;
 
 import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.providers.CommonNameLP;
 import org.argeo.cms.e4.users.providers.DomainNameLP;
 import org.argeo.cms.e4.users.providers.RoleIconLP;
@@ -37,7 +38,6 @@ import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
 //import org.argeo.cms.ui.eclipse.forms.FormToolkit;
 import org.argeo.cms.ui.eclipse.forms.IManagedForm;
 import org.argeo.cms.util.CmsUtils;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.eclipse.ui.ColumnDefinition;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.eclipse.ui.parts.LdifUsersTable;
index 561468dc62d0bd65db8fe13cda2fa4a6fe31f5bc..7da3a54d57033aab5d10d6da6945d47f3f279cdc 100644 (file)
@@ -20,9 +20,9 @@ import java.util.List;
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.GroupsView;
 import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.cms.util.UserAdminUtils;
 import org.eclipse.e4.core.di.annotations.CanExecute;
 import org.eclipse.e4.core.di.annotations.Execute;
 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
index b70312bc8abc590dbe8d770bd1267cc79bfe1ea3..cf6ec75f1b64da5c7a42279d087a25866056e813 100644 (file)
@@ -20,9 +20,9 @@ import java.util.List;
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.UserAdminWrapper;
 import org.argeo.cms.e4.users.UsersView;
-import org.argeo.cms.util.UserAdminUtils;
 import org.eclipse.e4.core.di.annotations.CanExecute;
 import org.eclipse.e4.core.di.annotations.Execute;
 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
index f2ddf9eeee15042448796733b3b33605ea225ef0..39e9fdc6a5bc1d26ab1fff663eaf18500380fb72 100644 (file)
@@ -25,9 +25,9 @@ import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
 import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.UiAdminUtils;
 import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.eclipse.ui.EclipseUiUtils;
 import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
 import org.argeo.naming.LdapAttrs;
index 4beacb4816e143b78dabb20718f1e14c2225202f..eb8819425c101a7991eaabe63ceb8d52803c9c53 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.e4.users.providers;
 
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.naming.LdapAttrs;
 import org.osgi.service.useradmin.User;
 
index 48c1d227f765ad1fedb45f8da937f8388d4fe13c..e23729da84782ce5573de91f79387841dfeecee8 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.e4.users.providers;
 
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.osgi.service.useradmin.User;
 
 /** The human friendly domain name for the corresponding user. */
index 4224474f744d5cd0d6d45ebd40965bb77f221608..a312c0dfc7323a3a1f5cd9f856c85960020d3093 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.e4.users.providers;
 
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.naming.LdapAttrs;
 import org.osgi.service.useradmin.User;
 
index 23ee4c19ec360876ed9c958a6cc36a2547f0b805..859081e945cb06998c21d6650e99d77606f363bf 100644 (file)
@@ -1,7 +1,7 @@
 package org.argeo.cms.e4.users.providers;
 
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.cms.e4.users.SecurityAdminImages;
-import org.argeo.cms.util.UserAdminUtils;
 import org.argeo.naming.LdapAttrs;
 import org.argeo.node.NodeConstants;
 import org.argeo.node.NodeInstance;
index 2b90c955730afa4902ab6e0a22420399b4617b64..e33b1531db6344301b8922c4fd1c9fb99c345785 100644 (file)
@@ -4,7 +4,7 @@ import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 
 import org.argeo.cms.CmsException;
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.viewers.ColumnLabelProvider;
 import org.eclipse.swt.SWT;
index 3ad1c5307216de3dc2c6ac6a6c6d08bea624d90f..7be078ac56f73cf155396a7de81661b28798e0a4 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.cms.e4.users.providers;
 
 import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
 
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.naming.LdapAttrs;
 import org.argeo.node.NodeConstants;
 import org.eclipse.jface.viewers.Viewer;
index 2344590bd9aade70db849438ce4073eb9544d553..2d494ef90d3e02026c1c184bc4ddc0568c3ab4b8 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.ui.useradmin;
 
-import org.argeo.cms.util.UserAdminUtils;
+import org.argeo.cms.auth.UserAdminUtils;
 import org.argeo.node.NodeConstants;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.viewers.ColumnLabelProvider;
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/UserAdminUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/util/UserAdminUtils.java
deleted file mode 100644 (file)
index 65f99c5..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-package org.argeo.cms.util;
-
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.node.NodeConstants;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Centralise common patterns to manage users with a {@link UserAdmin} */
-public class UserAdminUtils {
-
-       // CURRENTUSER HELPERS
-       /** Checks if current user is the same as the passed one */
-       public static boolean isCurrentUser(User user) {
-               String userUsername = getProperty(user, LdapAttrs.DN);
-               LdapName userLdapName = getLdapName(userUsername);
-               LdapName selfUserName = getCurrentUserLdapName();
-               return userLdapName.equals(selfUserName);
-       }
-
-       /** Retrieves the current logged-in {@link User} */
-       public static User getCurrentUser(UserAdmin userAdmin) {
-               return (User) userAdmin.getRole(CurrentUser.getUsername());
-       }
-
-       /** Retrieves the current logged-in user {@link LdapName} */
-       public final static LdapName getCurrentUserLdapName() {
-               String name = CurrentUser.getUsername();
-               return getLdapName(name);
-       }
-
-       /** Retrieves the current logged-in user mail */
-       public static String getCurrentUserMail(UserAdmin userAdmin) {
-               String username = CurrentUser.getUsername();
-               return getUserMail(userAdmin, username);
-       }
-
-       /** Retrieves the current logged-in user common name */
-       public final static String getCommonName(User user) {
-               return getProperty(user, LdapAttrs.cn.name());
-       }
-
-       // OTHER USERS HELPERS
-       /**
-        * Retrieves the local id of a user or group, that is respectively the uid or cn
-        * of the passed dn with no {@link UserAdmin}
-        */
-       public static String getUserLocalId(String dn) {
-               LdapName ldapName = getLdapName(dn);
-               Rdn last = ldapName.getRdn(ldapName.size() - 1);
-               if (last.getType().toLowerCase().equals(LdapAttrs.uid.name())
-                               || last.getType().toLowerCase().equals(LdapAttrs.cn.name()))
-                       return (String) last.getValue();
-               else
-                       throw new CmsException("Cannot retrieve user local id, non valid dn: " + dn);
-       }
-
-       /**
-        * Returns the local username if no user with this dn is found or if the found
-        * user has no defined display name
-        */
-       public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
-               Role user = userAdmin.getRole(dn);
-               String dName;
-               if (user == null)
-                       dName = getUserLocalId(dn);
-               else {
-                       dName = getProperty(user, LdapAttrs.displayName.name());
-                       if (EclipseUiUtils.isEmpty(dName))
-                               dName = getProperty(user, LdapAttrs.cn.name());
-                       if (EclipseUiUtils.isEmpty(dName))
-                               dName = getUserLocalId(dn);
-               }
-               return dName;
-       }
-
-       /**
-        * Returns null if no user with this dn is found or if the found user has no
-        * defined mail
-        */
-       public static String getUserMail(UserAdmin userAdmin, String dn) {
-               Role user = userAdmin.getRole(dn);
-               if (user == null)
-                       return null;
-               else
-                       return getProperty(user, LdapAttrs.mail.name());
-       }
-
-       // LDAP NAMES HELPERS
-       /**
-        * Easily retrieves one of the {@link Role}'s property or an empty String if the
-        * requested property is not defined
-        */
-       public final static String getProperty(Role role, String key) {
-               Object obj = role.getProperties().get(key);
-               if (obj != null)
-                       return (String) obj;
-               else
-                       return "";
-       }
-
-       public final static String getProperty(Role role, Enum<?> key) {
-               Object obj = role.getProperties().get(key.name());
-               if (obj != null)
-                       return (String) obj;
-               else
-                       return "";
-       }
-
-       @SuppressWarnings("unchecked")
-       public final static void setProperty(Role role, String key, String value) {
-               role.getProperties().put(key, value);
-       }
-
-       public final static void setProperty(Role role, Enum<?> key, String value) {
-               setProperty(role, key.name(), value);
-       }
-
-       /**
-        * Simply retrieves a LDAP name from a {@link LdapAttrs.DN} with no exception
-        */
-       private static LdapName getLdapName(String dn) {
-               try {
-                       return new LdapName(dn);
-               } catch (InvalidNameException e) {
-                       throw new CmsException("Cannot parse LDAP name " + dn, e);
-               }
-       }
-
-       /** Simply retrieves a display name of the relevant domain */
-       public final static String getDomainName(User user) {
-               String dn = user.getName();
-               if (dn.endsWith(NodeConstants.ROLES_BASEDN))
-                       return "System roles";
-               if (dn.endsWith(NodeConstants.TOKENS_BASEDN))
-                       return "Tokens";
-               try {
-                       // FIXME deal with non-DC
-                       LdapName name = new LdapName(dn);
-                       List<Rdn> rdns = name.getRdns();
-                       String dname = null;
-                       int i = 0;
-                       loop: while (i < rdns.size()) {
-                               Rdn currrRdn = rdns.get(i);
-                               if (!LdapAttrs.dc.name().equals(currrRdn.getType()))
-                                       break loop;
-                               else {
-                                       String currVal = (String) currrRdn.getValue();
-                                       dname = dname == null ? currVal : currVal + "." + dname;
-                               }
-                               i++;
-                       }
-                       return dname;
-               } catch (InvalidNameException e) {
-                       throw new CmsException("Unable to get domain name for " + dn, e);
-               }
-       }
-
-       // VARIOUS HELPERS
-       public final static String buildDefaultCn(String firstName, String lastName) {
-               return (firstName.trim() + " " + lastName.trim() + " ").trim();
-       }
-}
index 2e18c90d0d6bbe308140405605c05e6203f41667..af3f7ef0d68d6aa5edf8f0239fa4ee8cb9f695ed 100644 (file)
                        <arguments>
                        </arguments>
                </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
        </buildSpec>
        <natures>
                <nature>org.eclipse.pde.PluginNature</nature>
diff --git a/org.argeo.cms/OSGI-INF/cmsUserManager.xml b/org.argeo.cms/OSGI-INF/cmsUserManager.xml
new file mode 100644 (file)
index 0000000..c7e3826
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="User Admin Service">
+   <implementation class="org.argeo.cms.internal.auth.CmsUserManagerImpl"/>
+   <service>
+      <provide interface="org.argeo.cms.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="javax.transaction.UserTransaction" name="UserTransaction" policy="static"/>
+</scr:component>
\ No newline at end of file
index ab54cd60ef3c789e6521be96ebe6199996e4484e..e89451e3a71e7ecba855cde895c7e91941fb83ff 100644 (file)
@@ -10,4 +10,6 @@ org.apache.commons.httpclient.cookie;resolution:=optional,\
 org.osgi.*;version=0.0.0,\
 *
 
+Service-Component: OSGI-INF/cmsUserManager.xml
+
 Provide-Capability: cms.datamodel;name=argeo;cnd=/org/argeo/cms/argeo.cnd;abstract=true
index e8ee671166e8a242555c6caf8b504cdd21b626aa..03b14d4341eea3dfddc1156cf1e8b221e2641c23 100644 (file)
@@ -3,8 +3,9 @@ bin.includes = META-INF/,\
                .,\
                OSGI-INF/,\
                bin/,\
-               OSGI-INF/org.argeo.cms.internal.kernel.KernelInitOld.xml
-source.. = src/
+               OSGI-INF/cmsUserManager.xml
+source.. = src/,\
+           ext/test/
 additional.bundles = org.apache.jackrabbit.data,\
                      org.argeo.jcr,\
                      org.junit
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
new file mode 100644 (file)
index 0000000..39d4be6
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.cms;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.security.auth.Subject;
+import javax.transaction.UserTransaction;
+
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Provide method interfaces to manage user concepts without accessing directly
+ * the userAdmin.
+ */
+public interface CmsUserManager {
+
+       // CurrentUser
+       /** Returns the e-mail of the current logged in user */
+       public String getMyMail();
+
+       // Other users
+       /** Returns a {@link User} given a username */
+       public User getUser(String username);
+
+       /** Can be a group or a user */
+       public String getUserDisplayName(String dn);
+
+       /** Can be a group or a user */
+       public String getUserMail(String dn);
+
+       /** Lists all roles of the given user */
+       public String[] getUserRoles(String dn);
+
+       /** Checks if the passed user belongs to the passed role */
+       public boolean isUserInRole(String userDn, String roleDn);
+
+       // Search
+       /** Returns a filtered list of roles */
+       public Role[] getRoles(String filter) throws InvalidSyntaxException;
+
+       /** Recursively lists users in a given group. */
+       public 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);
+
+       /* MISCELLANEOUS */
+       /** Returns the dn of a role given its local ID */
+       public String buildDefaultDN(String localId, int type);
+
+       /** Exposes the main default domain name for this instance */
+       public 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);
+
+       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 expireAuthToken(String token);
+
+       void expireAuthTokens(Subject subject);
+
+       User createUserFromPerson(Node person);
+
+       @Deprecated
+       public UserAdmin getUserAdmin();
+
+       @Deprecated
+       public UserTransaction getUserTransaction();
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java
new file mode 100644 (file)
index 0000000..326b0f4
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.cms.auth;
+
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.cms.CmsException;
+import org.argeo.naming.LdapAttrs;
+import org.argeo.node.NodeConstants;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Centralise common patterns to manage users with a {@link UserAdmin} */
+public class UserAdminUtils {
+
+       // CURRENTUSER HELPERS
+       /** Checks if current user is the same as the passed one */
+       public static boolean isCurrentUser(User user) {
+               String userUsername = getProperty(user, LdapAttrs.DN);
+               LdapName userLdapName = getLdapName(userUsername);
+               LdapName selfUserName = getCurrentUserLdapName();
+               return userLdapName.equals(selfUserName);
+       }
+
+       /** Retrieves the current logged-in {@link User} */
+       public static User getCurrentUser(UserAdmin userAdmin) {
+               return (User) userAdmin.getRole(CurrentUser.getUsername());
+       }
+
+       /** Retrieves the current logged-in user {@link LdapName} */
+       public final static LdapName getCurrentUserLdapName() {
+               String name = CurrentUser.getUsername();
+               return getLdapName(name);
+       }
+
+       /** Retrieves the current logged-in user mail */
+       public static String getCurrentUserMail(UserAdmin userAdmin) {
+               String username = CurrentUser.getUsername();
+               return getUserMail(userAdmin, username);
+       }
+
+       /** Retrieves the current logged-in user common name */
+       public final static String getCommonName(User user) {
+               return getProperty(user, LdapAttrs.cn.name());
+       }
+
+       // OTHER USERS HELPERS
+       /**
+        * Retrieves the local id of a user or group, that is respectively the uid or cn
+        * of the passed dn with no {@link UserAdmin}
+        */
+       public static String getUserLocalId(String dn) {
+               LdapName ldapName = getLdapName(dn);
+               Rdn last = ldapName.getRdn(ldapName.size() - 1);
+               if (last.getType().toLowerCase().equals(LdapAttrs.uid.name())
+                               || last.getType().toLowerCase().equals(LdapAttrs.cn.name()))
+                       return (String) last.getValue();
+               else
+                       throw new CmsException("Cannot retrieve user local id, non valid dn: " + dn);
+       }
+
+       /**
+        * Returns the local username if no user with this dn is found or if the found
+        * user has no defined display name
+        */
+       public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
+               Role user = userAdmin.getRole(dn);
+               String dName;
+               if (user == null)
+                       dName = getUserLocalId(dn);
+               else {
+                       dName = getProperty(user, LdapAttrs.displayName.name());
+                       if (isEmpty(dName))
+                               dName = getProperty(user, LdapAttrs.cn.name());
+                       if (isEmpty(dName))
+                               dName = getUserLocalId(dn);
+               }
+               return dName;
+       }
+
+       /**
+        * Returns null if no user with this dn is found or if the found user has no
+        * defined mail
+        */
+       public static String getUserMail(UserAdmin userAdmin, String dn) {
+               Role user = userAdmin.getRole(dn);
+               if (user == null)
+                       return null;
+               else
+                       return getProperty(user, LdapAttrs.mail.name());
+       }
+
+       // LDAP NAMES HELPERS
+       /**
+        * Easily retrieves one of the {@link Role}'s property or an empty String if the
+        * requested property is not defined
+        */
+       public final static String getProperty(Role role, String key) {
+               Object obj = role.getProperties().get(key);
+               if (obj != null)
+                       return (String) obj;
+               else
+                       return "";
+       }
+
+       public final static String getProperty(Role role, Enum<?> key) {
+               Object obj = role.getProperties().get(key.name());
+               if (obj != null)
+                       return (String) obj;
+               else
+                       return "";
+       }
+
+       public final static void setProperty(Role role, String key, String value) {
+               role.getProperties().put(key, value);
+       }
+
+       public final static void setProperty(Role role, Enum<?> key, String value) {
+               setProperty(role, key.name(), value);
+       }
+
+       /**
+        * Simply retrieves a LDAP name from a {@link LdapAttrs.DN} with no exception
+        */
+       private static LdapName getLdapName(String dn) {
+               try {
+                       return new LdapName(dn);
+               } catch (InvalidNameException e) {
+                       throw new CmsException("Cannot parse LDAP name " + dn, e);
+               }
+       }
+
+       /** Simply retrieves a display name of the relevant domain */
+       public final static String getDomainName(User user) {
+               String dn = user.getName();
+               if (dn.endsWith(NodeConstants.ROLES_BASEDN))
+                       return "System roles";
+               if (dn.endsWith(NodeConstants.TOKENS_BASEDN))
+                       return "Tokens";
+               try {
+                       // FIXME deal with non-DC
+                       LdapName name = new LdapName(dn);
+                       List<Rdn> rdns = name.getRdns();
+                       String dname = null;
+                       int i = 0;
+                       loop: while (i < rdns.size()) {
+                               Rdn currrRdn = rdns.get(i);
+                               if (!LdapAttrs.dc.name().equals(currrRdn.getType()))
+                                       break loop;
+                               else {
+                                       String currVal = (String) currrRdn.getValue();
+                                       dname = dname == null ? currVal : currVal + "." + dname;
+                               }
+                               i++;
+                       }
+                       return dname;
+               } catch (InvalidNameException e) {
+                       throw new CmsException("Unable to get domain name for " + dn, e);
+               }
+       }
+
+       // VARIOUS HELPERS
+       public final static String buildDefaultCn(String firstName, String lastName) {
+               return (firstName.trim() + " " + lastName.trim() + " ").trim();
+       }
+
+       /** Simply checks if a string is null or empty */
+       private static boolean isEmpty(String stringToTest) {
+               return stringToTest == null || "".equals(stringToTest.trim());
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..109a0d4
--- /dev/null
@@ -0,0 +1,459 @@
+package org.argeo.cms.internal.auth;
+
+import static org.argeo.naming.LdapAttrs.cn;
+import static org.argeo.naming.LdapAttrs.description;
+import static org.argeo.naming.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.Set;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.transaction.Status;
+import javax.transaction.UserTransaction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.naming.LdapAttrs;
+import org.argeo.naming.NamingUtils;
+import org.argeo.naming.SharedSecret;
+import org.argeo.node.NodeConstants;
+import org.argeo.osgi.useradmin.TokenUtils;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+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 Log log = LogFactory.getLog(CmsUserManagerImpl.class);
+
+       private UserAdmin userAdmin;
+       @Deprecated
+       private ServiceReference<UserAdmin> userAdminServiceReference;
+       private Map<String, String> serviceProperties;
+       private UserTransaction userTransaction;
+
+       @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 */
+       public User getUser(String dn) {
+               return (User) 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;
+       }
+
+       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)
+                       throw new IllegalArgumentException("Group " + groupDn + " not found");
+               Set<User> users = new HashSet<User>();
+               addUsers(users, group, filter);
+               return users;
+       }
+
+       /** Recursively add users to list */
+       private void addUsers(Set<User> users, Group group, String filter) {
+               Role[] roles = group.getMembers();
+               for (Role role : roles) {
+                       if (role.getType() == Role.GROUP) {
+                               addUsers(users, (Group) role, filter);
+                       } else if (role.getType() == Role.USER) {
+                               if (match(role, filter))
+                                       users.add((User) role);
+                       } else {
+                               // ignore
+                       }
+               }
+       }
+
+       public List<User> 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<User> users = new ArrayList<User>();
+               for (Role role : roles) {
+                       if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
+                                       && (includeSystemRoles || !role.getName().toLowerCase().endsWith(NodeConstants.ROLES_BASEDN))) {
+                               if (match(role, filter))
+                                       users.add((User) 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 User getUserFromLocalId(String localId) {
+               User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
+               if (user == null)
+                       user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
+               return user;
+       }
+
+       @Override
+       public String buildDefaultDN(String localId, int type) {
+               return buildDistinguishedName(localId, getDefaultDomainName(), type);
+       }
+
+       @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>();
+               String[] propertyKeys = userAdminServiceReference != null ? userAdminServiceReference.getPropertyKeys()
+                               : 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 String buildDistinguishedName(String localId, String baseDn, int type) {
+               Map<String, String> dns = getKnownBaseDns(true);
+               Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
+               String dn = null;
+               if (Role.GROUP == type)
+                       dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
+               else if (Role.USER == type)
+                       dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.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.getStatus() != Status.STATUS_NO_TRANSACTION)
+                                       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 + "," + NodeConstants.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.getStatus() != Status.STATUS_NO_TRANSACTION)
+                                       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, NodeConstants.TOKENS_BASEDN);
+               for (String token : tokens)
+                       expireAuthToken(token);
+       }
+
+       @Override
+       public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.getRole(userDn);
+                       String tokenDn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
+                       Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
+                       for (String role : roles) {
+                               Role r = userAdmin.getRole(role);
+                               if (r != null)
+                                       tokenGroup.addMember(r);
+                               else {
+                                       if (!role.equals(NodeConstants.ROLE_USER)) {
+                                               throw new IllegalStateException(
+                                                               "Cannot add role " + role + " to token " + token + " for " + userDn);
+                                       }
+                               }
+                       }
+                       tokenGroup.getProperties().put(owner.name(), user.getName());
+                       if (hours != null) {
+                               String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now().plusHours(hours));
+                               tokenGroup.getProperties().put(description.name(), ldapDate);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add token", e1);
+               }
+       }
+
+       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, Map<String, String> serviceProperties) {
+               this.userAdmin = userAdmin;
+               this.serviceProperties = serviceProperties;
+       }
+
+       public void setUserTransaction(UserTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+}