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