1 package org
.argeo
.cms
.internal
.auth
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.cn
;
4 import static org
.argeo
.util
.naming
.LdapAttrs
.description
;
5 import static org
.argeo
.util
.naming
.LdapAttrs
.owner
;
7 import java
.time
.ZoneOffset
;
8 import java
.time
.ZonedDateTime
;
9 import java
.util
.ArrayList
;
10 import java
.util
.Arrays
;
11 import java
.util
.Dictionary
;
12 import java
.util
.HashMap
;
13 import java
.util
.HashSet
;
14 import java
.util
.List
;
16 import java
.util
.NavigableMap
;
18 import java
.util
.TreeMap
;
19 import java
.util
.TreeSet
;
20 import java
.util
.UUID
;
22 import javax
.naming
.InvalidNameException
;
23 import javax
.naming
.ldap
.LdapName
;
24 import javax
.security
.auth
.Subject
;
26 import org
.argeo
.api
.cms
.CmsConstants
;
27 import org
.argeo
.api
.cms
.CmsLog
;
28 import org
.argeo
.cms
.CmsUserManager
;
29 import org
.argeo
.cms
.auth
.CurrentUser
;
30 import org
.argeo
.cms
.auth
.UserAdminUtils
;
31 import org
.argeo
.osgi
.useradmin
.AggregatingUserAdmin
;
32 import org
.argeo
.osgi
.useradmin
.TokenUtils
;
33 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
34 import org
.argeo
.util
.directory
.DirectoryConf
;
35 import org
.argeo
.util
.directory
.ldap
.SharedSecret
;
36 import org
.argeo
.util
.naming
.LdapAttrs
;
37 import org
.argeo
.util
.naming
.NamingUtils
;
38 import org
.argeo
.util
.transaction
.WorkTransaction
;
39 import org
.osgi
.framework
.InvalidSyntaxException
;
40 import org
.osgi
.service
.useradmin
.Authorization
;
41 import org
.osgi
.service
.useradmin
.Group
;
42 import org
.osgi
.service
.useradmin
.Role
;
43 import org
.osgi
.service
.useradmin
.User
;
44 import org
.osgi
.service
.useradmin
.UserAdmin
;
47 * Canonical implementation of the people {@link CmsUserManager}. Wraps
48 * interaction with users and groups.
50 * In a *READ-ONLY* mode. We want to be able to:
52 * <li>Retrieve my user and corresponding information (main info,
54 * <li>List all local groups (not the system roles)</li>
55 * <li>If sufficient rights: retrieve a given user and its information</li>
58 public class CmsUserManagerImpl
implements CmsUserManager
{
59 private final static CmsLog log
= CmsLog
.getLog(CmsUserManagerImpl
.class);
61 private UserAdmin userAdmin
;
62 // private Map<String, String> serviceProperties;
63 private WorkTransaction userTransaction
;
65 // private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
66 // .synchronizedMap(new LinkedHashMap<>());
68 private Set
<UserDirectory
> userDirectories
= new HashSet
<>();
71 log
.debug(() -> "CMS user manager available");
79 public String
getMyMail() {
80 return getUserMail(CurrentUser
.getUsername());
84 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
85 return userAdmin
.getRoles(filter
);
88 // ALL USER: WARNING access to this will be later reduced
90 /** Retrieve a user given his dn */
91 public User
getUser(String dn
) {
92 return (User
) getUserAdmin().getRole(dn
);
95 /** Can be a group or a user */
96 public String
getUserDisplayName(String dn
) {
97 // FIXME: during initialisation phase, the system logs "admin" as user
98 // name rather than the corresponding dn
99 if ("admin".equals(dn
))
100 return "System Administrator";
102 return UserAdminUtils
.getUserDisplayName(getUserAdmin(), dn
);
106 public String
getUserMail(String dn
) {
107 return UserAdminUtils
.getUserMail(getUserAdmin(), dn
);
110 /** Lists all roles of the given user */
112 public String
[] getUserRoles(String dn
) {
113 Authorization currAuth
= getUserAdmin().getAuthorization(getUser(dn
));
114 return currAuth
.getRoles();
118 public boolean isUserInRole(String userDn
, String roleDn
) {
119 String
[] roles
= getUserRoles(userDn
);
120 for (String role
: roles
) {
121 if (role
.equalsIgnoreCase(roleDn
))
127 private final String
[] knownProps
= { LdapAttrs
.cn
.name(), LdapAttrs
.sn
.name(), LdapAttrs
.givenName
.name(),
128 LdapAttrs
.uid
.name() };
130 public Set
<User
> listUsersInGroup(String groupDn
, String filter
) {
131 Group group
= (Group
) userAdmin
.getRole(groupDn
);
133 throw new IllegalArgumentException("Group " + groupDn
+ " not found");
134 Set
<User
> users
= new HashSet
<User
>();
135 addUsers(users
, group
, filter
);
139 /** Recursively add users to list */
140 private void addUsers(Set
<User
> users
, Group group
, String filter
) {
141 Role
[] roles
= group
.getMembers();
142 for (Role role
: roles
) {
143 if (role
.getType() == Role
.GROUP
) {
144 addUsers(users
, (Group
) role
, filter
);
145 } else if (role
.getType() == Role
.USER
) {
146 if (match(role
, filter
))
147 users
.add((User
) role
);
154 public List
<User
> listGroups(String filter
, boolean includeUsers
, boolean includeSystemRoles
) {
157 roles
= getUserAdmin().getRoles(filter
);
158 } catch (InvalidSyntaxException e
) {
159 throw new IllegalArgumentException("Unable to get roles with filter: " + filter
, e
);
162 List
<User
> users
= new ArrayList
<User
>();
163 for (Role role
: roles
) {
164 if ((includeUsers
&& role
.getType() == Role
.USER
|| role
.getType() == Role
.GROUP
) && !users
.contains(role
)
165 && (includeSystemRoles
|| !role
.getName().toLowerCase().endsWith(CmsConstants
.ROLES_BASEDN
))) {
166 if (match(role
, filter
))
167 users
.add((User
) role
);
173 private boolean match(Role role
, String filter
) {
174 boolean doFilter
= filter
!= null && !"".equals(filter
);
176 for (String prop
: knownProps
) {
177 Object currProp
= null;
179 currProp
= role
.getProperties().get(prop
);
180 } catch (Exception e
) {
183 if (currProp
!= null) {
184 String currPropStr
= ((String
) currProp
).toLowerCase();
185 if (currPropStr
.contains(filter
.toLowerCase())) {
196 public User
getUserFromLocalId(String localId
) {
197 User user
= getUserAdmin().getUser(LdapAttrs
.uid
.name(), localId
);
199 user
= getUserAdmin().getUser(LdapAttrs
.cn
.name(), localId
);
204 public String
buildDefaultDN(String localId
, int type
) {
205 return buildDistinguishedName(localId
, getDefaultDomainName(), type
);
209 public String
getDefaultDomainName() {
210 Map
<String
, String
> dns
= getKnownBaseDns(true);
212 return dns
.keySet().iterator().next();
214 throw new IllegalStateException("Current context contains " + dns
.size() + " base dns: "
215 + dns
.keySet().toString() + ". Unable to chose a default one.");
218 // public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
219 // Map<String, String> dns = new HashMap<String, String>();
220 // String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
221 // for (String uri : propertyKeys) {
222 // if (!uri.startsWith("/"))
224 // Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
225 // String readOnly = UserAdminConf.readOnly.getValue(props);
226 // String baseDn = UserAdminConf.baseDn.getValue(props);
228 // if (onlyWritable && "true".equals(readOnly))
230 // if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
232 // if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
234 // dns.put(baseDn, uri);
239 public Map
<String
, String
> getKnownBaseDns(boolean onlyWritable
) {
240 Map
<String
, String
> dns
= new HashMap
<String
, String
>();
241 for (UserDirectory userDirectory
: userDirectories
) {
242 Boolean readOnly
= userDirectory
.isReadOnly();
243 String baseDn
= userDirectory
.getContext();
245 if (onlyWritable
&& readOnly
)
247 if (baseDn
.equalsIgnoreCase(CmsConstants
.ROLES_BASEDN
))
249 if (baseDn
.equalsIgnoreCase(CmsConstants
.TOKENS_BASEDN
))
251 dns
.put(baseDn
, DirectoryConf
.propertiesAsUri(userDirectory
.getProperties()).toString());
257 public Set
<UserDirectory
> getUserDirectories() {
258 TreeSet
<UserDirectory
> res
= new TreeSet
<>((o1
, o2
) -> o1
.getContext().compareTo(o2
.getContext()));
259 res
.addAll(userDirectories
);
263 public String
buildDistinguishedName(String localId
, String baseDn
, int type
) {
264 Map
<String
, String
> dns
= getKnownBaseDns(true);
265 Dictionary
<String
, ?
> props
= DirectoryConf
.uriAsProperties(dns
.get(baseDn
));
267 if (Role
.GROUP
== type
)
268 dn
= LdapAttrs
.cn
.name() + "=" + localId
+ "," + DirectoryConf
.groupBase
.getValue(props
) + "," + baseDn
;
269 else if (Role
.USER
== type
)
270 dn
= LdapAttrs
.uid
.name() + "=" + localId
+ "," + DirectoryConf
.userBase
.getValue(props
) + "," + baseDn
;
272 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId
);
277 public void changeOwnPassword(char[] oldPassword
, char[] newPassword
) {
278 String name
= CurrentUser
.getUsername();
281 dn
= new LdapName(name
);
282 } catch (InvalidNameException e
) {
283 throw new IllegalArgumentException("Invalid user dn " + name
, e
);
285 User user
= (User
) userAdmin
.getRole(dn
.toString());
286 if (!user
.hasCredential(null, oldPassword
))
287 throw new IllegalArgumentException("Invalid password");
288 if (Arrays
.equals(newPassword
, new char[0]))
289 throw new IllegalArgumentException("New password empty");
291 userTransaction
.begin();
292 user
.getCredentials().put(null, newPassword
);
293 userTransaction
.commit();
294 } catch (Exception e
) {
296 userTransaction
.rollback();
297 } catch (Exception e1
) {
298 log
.error("Could not roll back", e1
);
300 if (e
instanceof RuntimeException
)
301 throw (RuntimeException
) e
;
303 throw new RuntimeException("Cannot change password", e
);
307 public void resetPassword(String username
, char[] newPassword
) {
310 dn
= new LdapName(username
);
311 } catch (InvalidNameException e
) {
312 throw new IllegalArgumentException("Invalid user dn " + username
, e
);
314 User user
= (User
) userAdmin
.getRole(dn
.toString());
315 if (Arrays
.equals(newPassword
, new char[0]))
316 throw new IllegalArgumentException("New password empty");
318 userTransaction
.begin();
319 user
.getCredentials().put(null, newPassword
);
320 userTransaction
.commit();
321 } catch (Exception e
) {
323 userTransaction
.rollback();
324 } catch (Exception e1
) {
325 log
.error("Could not roll back", e1
);
327 if (e
instanceof RuntimeException
)
328 throw (RuntimeException
) e
;
330 throw new RuntimeException("Cannot change password", e
);
334 public String
addSharedSecret(String email
, int hours
) {
335 User user
= (User
) userAdmin
.getUser(LdapAttrs
.mail
.name(), email
);
337 userTransaction
.begin();
338 String uuid
= UUID
.randomUUID().toString();
339 SharedSecret sharedSecret
= new SharedSecret(hours
, uuid
);
340 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
341 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
342 userTransaction
.commit();
344 } catch (Exception e
) {
346 userTransaction
.rollback();
347 } catch (Exception e1
) {
348 log
.error("Could not roll back", e1
);
350 if (e
instanceof RuntimeException
)
351 throw (RuntimeException
) e
;
353 throw new RuntimeException("Cannot change password", e
);
358 public String
addSharedSecret(String username
, String authInfo
, String authToken
) {
360 userTransaction
.begin();
361 User user
= (User
) userAdmin
.getRole(username
);
362 SharedSecret sharedSecret
= new SharedSecret(authInfo
, authToken
);
363 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
364 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
365 userTransaction
.commit();
367 } catch (Exception e1
) {
369 if (!userTransaction
.isNoTransactionStatus())
370 userTransaction
.rollback();
371 } catch (Exception e2
) {
372 if (log
.isTraceEnabled())
373 log
.trace("Cannot rollback transaction", e2
);
375 throw new RuntimeException("Cannot add shared secret", e1
);
380 public void expireAuthToken(String token
) {
382 userTransaction
.begin();
383 String dn
= cn
+ "=" + token
+ "," + CmsConstants
.TOKENS_BASEDN
;
384 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
385 String ldapDate
= NamingUtils
.instantToLdapDate(ZonedDateTime
.now(ZoneOffset
.UTC
));
386 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
387 userTransaction
.commit();
388 if (log
.isDebugEnabled())
389 log
.debug("Token " + token
+ " expired.");
390 } catch (Exception e1
) {
392 if (!userTransaction
.isNoTransactionStatus())
393 userTransaction
.rollback();
394 } catch (Exception e2
) {
395 if (log
.isTraceEnabled())
396 log
.trace("Cannot rollback transaction", e2
);
398 throw new RuntimeException("Cannot expire token", e1
);
403 public void expireAuthTokens(Subject subject
) {
404 Set
<String
> tokens
= TokenUtils
.tokensUsed(subject
, CmsConstants
.TOKENS_BASEDN
);
405 for (String token
: tokens
)
406 expireAuthToken(token
);
410 public void addAuthToken(String userDn
, String token
, Integer hours
, String
... roles
) {
411 addAuthToken(userDn
, token
, ZonedDateTime
.now().plusHours(hours
), roles
);
415 public void addAuthToken(String userDn
, String token
, ZonedDateTime expiryDate
, String
... roles
) {
417 userTransaction
.begin();
418 User user
= (User
) userAdmin
.getRole(userDn
);
419 String tokenDn
= cn
+ "=" + token
+ "," + CmsConstants
.TOKENS_BASEDN
;
420 Group tokenGroup
= (Group
) userAdmin
.createRole(tokenDn
, Role
.GROUP
);
422 for (String role
: roles
) {
423 Role r
= userAdmin
.getRole(role
);
425 tokenGroup
.addMember(r
);
427 if (!role
.equals(CmsConstants
.ROLE_USER
)) {
428 throw new IllegalStateException(
429 "Cannot add role " + role
+ " to token " + token
+ " for " + userDn
);
433 tokenGroup
.getProperties().put(owner
.name(), user
.getName());
434 if (expiryDate
!= null) {
435 String ldapDate
= NamingUtils
.instantToLdapDate(expiryDate
);
436 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
438 userTransaction
.commit();
439 } catch (Exception e1
) {
441 if (!userTransaction
.isNoTransactionStatus())
442 userTransaction
.rollback();
443 } catch (Exception e2
) {
444 if (log
.isTraceEnabled())
445 log
.trace("Cannot rollback transaction", e2
);
447 throw new RuntimeException("Cannot add token", e1
);
452 public UserDirectory
getDirectory(Role user
) {
453 String name
= user
.getName();
454 NavigableMap
<String
, UserDirectory
> possible
= new TreeMap
<>();
455 for (UserDirectory userDirectory
: userDirectories
) {
456 if (name
.endsWith(userDirectory
.getContext())) {
457 possible
.put(userDirectory
.getContext(), userDirectory
);
460 if (possible
.size() == 0)
461 throw new IllegalStateException("No user directory found for user " + name
);
462 return possible
.lastEntry().getValue();
465 // public User createUserFromPerson(Node person) {
466 // String email = JcrUtils.get(person, LdapAttrs.mail.property());
467 // String dn = buildDefaultDN(email, Role.USER);
470 // userTransaction.begin();
471 // user = (User) userAdmin.createRole(dn, Role.USER);
472 // Dictionary<String, Object> userProperties = user.getProperties();
473 // String name = JcrUtils.get(person, LdapAttrs.displayName.property());
474 // userProperties.put(LdapAttrs.cn.name(), name);
475 // userProperties.put(LdapAttrs.displayName.name(), name);
476 // String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
477 // String surname = JcrUtils.get(person, LdapAttrs.sn.property());
478 // userProperties.put(LdapAttrs.givenName.name(), givenName);
479 // userProperties.put(LdapAttrs.sn.name(), surname);
480 // userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
481 // userTransaction.commit();
482 // } catch (Exception e) {
484 // userTransaction.rollback();
485 // } catch (Exception e1) {
486 // log.error("Could not roll back", e1);
488 // if (e instanceof RuntimeException)
489 // throw (RuntimeException) e;
491 // throw new RuntimeException("Cannot create user", e);
496 public UserAdmin
getUserAdmin() {
500 // public UserTransaction getUserTransaction() {
501 // return userTransaction;
504 /* DEPENDENCY INJECTION */
505 public void setUserAdmin(UserAdmin userAdmin
) {
506 this.userAdmin
= userAdmin
;
508 if (userAdmin
instanceof AggregatingUserAdmin
) {
509 userDirectories
= ((AggregatingUserAdmin
) userAdmin
).getUserDirectories();
511 throw new IllegalArgumentException("Only " + AggregatingUserAdmin
.class.getName() + " is supported.");
514 // this.serviceProperties = serviceProperties;
517 public void setUserTransaction(WorkTransaction userTransaction
) {
518 this.userTransaction
= userTransaction
;
521 // public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
522 // userDirectories.put(userDirectory, new Hashtable<>(properties));
525 // public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
526 // userDirectories.remove(userDirectory);