From: Mathieu Baudier Date: Mon, 2 Mar 2020 08:22:35 +0000 (+0100) Subject: Introduce CmsUserManager (from Argeo Connect's UserAdminService) X-Git-Tag: argeo-commons-2.1.88~16 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=b7c2f2cc2f6f74762031567e9e636ff277ebc7c7 Introduce CmsUserManager (from Argeo Connect's UserAdminService) --- diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java index 518be8ce5..3c9347a71 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java index b6108600e..a2c920aa8 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java index 6ce51b803..ed797c58a 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java index f7d6706fc..a35499984 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java index 561468dc6..7da3a54d5 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java index b70312bc8..cf6ec75f1 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java index f2ddf9eee..39e9fdc6a 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java index 4beacb481..eb8819425 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java index 48c1d227f..e23729da8 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java @@ -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. */ diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java index 4224474f7..a312c0dfc 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java index 23ee4c19e..859081e94 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java index 2b90c9557..e33b1531d 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java @@ -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; diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java index 3ad1c5307..7be078ac5 100644 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java +++ b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java @@ -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; diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java index 2344590bd..2d494ef90 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java @@ -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 index 65f99c5e1..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/util/UserAdminUtils.java +++ /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 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(); - } -} diff --git a/org.argeo.cms/.project b/org.argeo.cms/.project index 2e18c90d0..af3f7ef0d 100644 --- a/org.argeo.cms/.project +++ b/org.argeo.cms/.project @@ -20,6 +20,11 @@ + + org.eclipse.pde.ds.core.builder + + + org.eclipse.pde.PluginNature diff --git a/org.argeo.cms/OSGI-INF/cmsUserManager.xml b/org.argeo.cms/OSGI-INF/cmsUserManager.xml new file mode 100644 index 000000000..c7e3826fe --- /dev/null +++ b/org.argeo.cms/OSGI-INF/cmsUserManager.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/org.argeo.cms/bnd.bnd b/org.argeo.cms/bnd.bnd index ab54cd60e..e89451e3a 100644 --- a/org.argeo.cms/bnd.bnd +++ b/org.argeo.cms/bnd.bnd @@ -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 diff --git a/org.argeo.cms/build.properties b/org.argeo.cms/build.properties index e8ee67116..03b14d434 100644 --- a/org.argeo.cms/build.properties +++ b/org.argeo.cms/build.properties @@ -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 index 000000000..39d4be622 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java @@ -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 listUsersInGroup(String groupDn, String filter); + + /** Search among groups including system roles and users if needed */ + public List 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 index 000000000..326b0f4da --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java @@ -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 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 index 000000000..109a0d406 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java @@ -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: + *
    + *
  • Retrieve my user and corresponding information (main info, + * groups...)
  • + *
  • List all local groups (not the system roles)
  • + *
  • If sufficient rights: retrieve a given user and its information
  • + *
+ */ +public class CmsUserManagerImpl implements CmsUserManager { + private final static Log log = LogFactory.getLog(CmsUserManagerImpl.class); + + private UserAdmin userAdmin; + @Deprecated + private ServiceReference userAdminServiceReference; + private Map 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 listUsersInGroup(String groupDn, String filter) { + Group group = (Group) userAdmin.getRole(groupDn); + if (group == null) + throw new IllegalArgumentException("Group " + groupDn + " not found"); + Set users = new HashSet(); + addUsers(users, group, filter); + return users; + } + + /** Recursively add users to list */ + private void addUsers(Set 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 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 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(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 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 getKnownBaseDns(boolean onlyWritable) { + Map dns = new HashMap(); + String[] propertyKeys = userAdminServiceReference != null ? userAdminServiceReference.getPropertyKeys() + : serviceProperties.keySet().toArray(new String[serviceProperties.size()]); + for (String uri : propertyKeys) { + if (!uri.startsWith("/")) + continue; + Dictionary 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 dns = getKnownBaseDns(true); + Dictionary 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 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 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 serviceProperties) { + this.userAdmin = userAdmin; + this.serviceProperties = serviceProperties; + } + + public void setUserTransaction(UserTransaction userTransaction) { + this.userTransaction = userTransaction; + } +}