1 package org
.argeo
.cms
.internal
.auth
;
3 import static org
.argeo
.naming
.LdapAttrs
.cn
;
4 import static org
.argeo
.naming
.LdapAttrs
.description
;
5 import static org
.argeo
.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
.Collections
;
12 import java
.util
.Dictionary
;
13 import java
.util
.HashMap
;
14 import java
.util
.HashSet
;
15 import java
.util
.Hashtable
;
16 import java
.util
.LinkedHashMap
;
17 import java
.util
.List
;
20 import java
.util
.UUID
;
22 import javax
.naming
.InvalidNameException
;
23 import javax
.naming
.ldap
.LdapName
;
24 import javax
.security
.auth
.Subject
;
25 import javax
.transaction
.Status
;
26 import javax
.transaction
.UserTransaction
;
28 import org
.apache
.commons
.logging
.Log
;
29 import org
.apache
.commons
.logging
.LogFactory
;
30 import org
.argeo
.api
.NodeConstants
;
31 import org
.argeo
.cms
.CmsUserManager
;
32 import org
.argeo
.cms
.auth
.CurrentUser
;
33 import org
.argeo
.cms
.auth
.UserAdminUtils
;
34 import org
.argeo
.naming
.LdapAttrs
;
35 import org
.argeo
.naming
.NamingUtils
;
36 import org
.argeo
.naming
.SharedSecret
;
37 import org
.argeo
.osgi
.useradmin
.TokenUtils
;
38 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
39 import org
.argeo
.osgi
.useradmin
.UserDirectory
;
40 import org
.osgi
.framework
.InvalidSyntaxException
;
41 import org
.osgi
.service
.useradmin
.Authorization
;
42 import org
.osgi
.service
.useradmin
.Group
;
43 import org
.osgi
.service
.useradmin
.Role
;
44 import org
.osgi
.service
.useradmin
.User
;
45 import org
.osgi
.service
.useradmin
.UserAdmin
;
48 * Canonical implementation of the people {@link CmsUserManager}. Wraps
49 * interaction with users and groups.
51 * In a *READ-ONLY* mode. We want to be able to:
53 * <li>Retrieve my user and corresponding information (main info,
55 * <li>List all local groups (not the system roles)</li>
56 * <li>If sufficient rights: retrieve a given user and its information</li>
59 public class CmsUserManagerImpl
implements CmsUserManager
{
60 private final static Log log
= LogFactory
.getLog(CmsUserManagerImpl
.class);
62 private UserAdmin userAdmin
;
63 // private Map<String, String> serviceProperties;
64 private UserTransaction userTransaction
;
66 private Map
<UserDirectory
, Hashtable
<String
, String
>> userDirectories
= Collections
67 .synchronizedMap(new LinkedHashMap
<>());
70 public String
getMyMail() {
71 return getUserMail(CurrentUser
.getUsername());
75 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
76 return userAdmin
.getRoles(filter
);
79 // ALL USER: WARNING access to this will be later reduced
81 /** Retrieve a user given his dn */
82 public User
getUser(String dn
) {
83 return (User
) getUserAdmin().getRole(dn
);
86 /** Can be a group or a user */
87 public String
getUserDisplayName(String dn
) {
88 // FIXME: during initialisation phase, the system logs "admin" as user
89 // name rather than the corresponding dn
90 if ("admin".equals(dn
))
91 return "System Administrator";
93 return UserAdminUtils
.getUserDisplayName(getUserAdmin(), dn
);
97 public String
getUserMail(String dn
) {
98 return UserAdminUtils
.getUserMail(getUserAdmin(), dn
);
101 /** Lists all roles of the given user */
103 public String
[] getUserRoles(String dn
) {
104 Authorization currAuth
= getUserAdmin().getAuthorization(getUser(dn
));
105 return currAuth
.getRoles();
109 public boolean isUserInRole(String userDn
, String roleDn
) {
110 String
[] roles
= getUserRoles(userDn
);
111 for (String role
: roles
) {
112 if (role
.equalsIgnoreCase(roleDn
))
118 private final String
[] knownProps
= { LdapAttrs
.cn
.name(), LdapAttrs
.sn
.name(), LdapAttrs
.givenName
.name(),
119 LdapAttrs
.uid
.name() };
121 public Set
<User
> listUsersInGroup(String groupDn
, String filter
) {
122 Group group
= (Group
) userAdmin
.getRole(groupDn
);
124 throw new IllegalArgumentException("Group " + groupDn
+ " not found");
125 Set
<User
> users
= new HashSet
<User
>();
126 addUsers(users
, group
, filter
);
130 /** Recursively add users to list */
131 private void addUsers(Set
<User
> users
, Group group
, String filter
) {
132 Role
[] roles
= group
.getMembers();
133 for (Role role
: roles
) {
134 if (role
.getType() == Role
.GROUP
) {
135 addUsers(users
, (Group
) role
, filter
);
136 } else if (role
.getType() == Role
.USER
) {
137 if (match(role
, filter
))
138 users
.add((User
) role
);
145 public List
<User
> listGroups(String filter
, boolean includeUsers
, boolean includeSystemRoles
) {
148 roles
= getUserAdmin().getRoles(filter
);
149 } catch (InvalidSyntaxException e
) {
150 throw new IllegalArgumentException("Unable to get roles with filter: " + filter
, e
);
153 List
<User
> users
= new ArrayList
<User
>();
154 for (Role role
: roles
) {
155 if ((includeUsers
&& role
.getType() == Role
.USER
|| role
.getType() == Role
.GROUP
) && !users
.contains(role
)
156 && (includeSystemRoles
|| !role
.getName().toLowerCase().endsWith(NodeConstants
.ROLES_BASEDN
))) {
157 if (match(role
, filter
))
158 users
.add((User
) role
);
164 private boolean match(Role role
, String filter
) {
165 boolean doFilter
= filter
!= null && !"".equals(filter
);
167 for (String prop
: knownProps
) {
168 Object currProp
= null;
170 currProp
= role
.getProperties().get(prop
);
171 } catch (Exception e
) {
174 if (currProp
!= null) {
175 String currPropStr
= ((String
) currProp
).toLowerCase();
176 if (currPropStr
.contains(filter
.toLowerCase())) {
187 public User
getUserFromLocalId(String localId
) {
188 User user
= getUserAdmin().getUser(LdapAttrs
.uid
.name(), localId
);
190 user
= getUserAdmin().getUser(LdapAttrs
.cn
.name(), localId
);
195 public String
buildDefaultDN(String localId
, int type
) {
196 return buildDistinguishedName(localId
, getDefaultDomainName(), type
);
200 public String
getDefaultDomainName() {
201 Map
<String
, String
> dns
= getKnownBaseDns(true);
203 return dns
.keySet().iterator().next();
205 throw new IllegalStateException("Current context contains " + dns
.size() + " base dns: "
206 + dns
.keySet().toString() + ". Unable to chose a default one.");
209 // public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
210 // Map<String, String> dns = new HashMap<String, String>();
211 // String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
212 // for (String uri : propertyKeys) {
213 // if (!uri.startsWith("/"))
215 // Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
216 // String readOnly = UserAdminConf.readOnly.getValue(props);
217 // String baseDn = UserAdminConf.baseDn.getValue(props);
219 // if (onlyWritable && "true".equals(readOnly))
221 // if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
223 // if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
225 // dns.put(baseDn, uri);
230 public Map
<String
, String
> getKnownBaseDns(boolean onlyWritable
) {
231 Map
<String
, String
> dns
= new HashMap
<String
, String
>();
232 for (UserDirectory userDirectory
: userDirectories
.keySet()) {
233 Boolean readOnly
= userDirectory
.isReadOnly();
234 String baseDn
= userDirectory
.getBaseDn().toString();
236 if (onlyWritable
&& readOnly
)
238 if (baseDn
.equalsIgnoreCase(NodeConstants
.ROLES_BASEDN
))
240 if (baseDn
.equalsIgnoreCase(NodeConstants
.TOKENS_BASEDN
))
242 dns
.put(baseDn
, UserAdminConf
.propertiesAsUri(userDirectories
.get(userDirectory
)).toString());
248 public String
buildDistinguishedName(String localId
, String baseDn
, int type
) {
249 Map
<String
, String
> dns
= getKnownBaseDns(true);
250 Dictionary
<String
, ?
> props
= UserAdminConf
.uriAsProperties(dns
.get(baseDn
));
252 if (Role
.GROUP
== type
)
253 dn
= LdapAttrs
.cn
.name() + "=" + localId
+ "," + UserAdminConf
.groupBase
.getValue(props
) + "," + baseDn
;
254 else if (Role
.USER
== type
)
255 dn
= LdapAttrs
.uid
.name() + "=" + localId
+ "," + UserAdminConf
.userBase
.getValue(props
) + "," + baseDn
;
257 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId
);
262 public void changeOwnPassword(char[] oldPassword
, char[] newPassword
) {
263 String name
= CurrentUser
.getUsername();
266 dn
= new LdapName(name
);
267 } catch (InvalidNameException e
) {
268 throw new IllegalArgumentException("Invalid user dn " + name
, e
);
270 User user
= (User
) userAdmin
.getRole(dn
.toString());
271 if (!user
.hasCredential(null, oldPassword
))
272 throw new IllegalArgumentException("Invalid password");
273 if (Arrays
.equals(newPassword
, new char[0]))
274 throw new IllegalArgumentException("New password empty");
276 userTransaction
.begin();
277 user
.getCredentials().put(null, newPassword
);
278 userTransaction
.commit();
279 } catch (Exception e
) {
281 userTransaction
.rollback();
282 } catch (Exception e1
) {
283 log
.error("Could not roll back", e1
);
285 if (e
instanceof RuntimeException
)
286 throw (RuntimeException
) e
;
288 throw new RuntimeException("Cannot change password", e
);
292 public void resetPassword(String username
, char[] newPassword
) {
295 dn
= new LdapName(username
);
296 } catch (InvalidNameException e
) {
297 throw new IllegalArgumentException("Invalid user dn " + username
, e
);
299 User user
= (User
) userAdmin
.getRole(dn
.toString());
300 if (Arrays
.equals(newPassword
, new char[0]))
301 throw new IllegalArgumentException("New password empty");
303 userTransaction
.begin();
304 user
.getCredentials().put(null, newPassword
);
305 userTransaction
.commit();
306 } catch (Exception e
) {
308 userTransaction
.rollback();
309 } catch (Exception e1
) {
310 log
.error("Could not roll back", e1
);
312 if (e
instanceof RuntimeException
)
313 throw (RuntimeException
) e
;
315 throw new RuntimeException("Cannot change password", e
);
319 public String
addSharedSecret(String email
, int hours
) {
320 User user
= (User
) userAdmin
.getUser(LdapAttrs
.mail
.name(), email
);
322 userTransaction
.begin();
323 String uuid
= UUID
.randomUUID().toString();
324 SharedSecret sharedSecret
= new SharedSecret(hours
, uuid
);
325 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
326 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
327 userTransaction
.commit();
329 } catch (Exception e
) {
331 userTransaction
.rollback();
332 } catch (Exception e1
) {
333 log
.error("Could not roll back", e1
);
335 if (e
instanceof RuntimeException
)
336 throw (RuntimeException
) e
;
338 throw new RuntimeException("Cannot change password", e
);
343 public String
addSharedSecret(String username
, String authInfo
, String authToken
) {
345 userTransaction
.begin();
346 User user
= (User
) userAdmin
.getRole(username
);
347 SharedSecret sharedSecret
= new SharedSecret(authInfo
, authToken
);
348 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
349 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
350 userTransaction
.commit();
352 } catch (Exception e1
) {
354 if (userTransaction
.getStatus() != Status
.STATUS_NO_TRANSACTION
)
355 userTransaction
.rollback();
356 } catch (Exception e2
) {
357 if (log
.isTraceEnabled())
358 log
.trace("Cannot rollback transaction", e2
);
360 throw new RuntimeException("Cannot add shared secret", e1
);
365 public void expireAuthToken(String token
) {
367 userTransaction
.begin();
368 String dn
= cn
+ "=" + token
+ "," + NodeConstants
.TOKENS_BASEDN
;
369 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
370 String ldapDate
= NamingUtils
.instantToLdapDate(ZonedDateTime
.now(ZoneOffset
.UTC
));
371 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
372 userTransaction
.commit();
373 if (log
.isDebugEnabled())
374 log
.debug("Token " + token
+ " expired.");
375 } catch (Exception e1
) {
377 if (userTransaction
.getStatus() != Status
.STATUS_NO_TRANSACTION
)
378 userTransaction
.rollback();
379 } catch (Exception e2
) {
380 if (log
.isTraceEnabled())
381 log
.trace("Cannot rollback transaction", e2
);
383 throw new RuntimeException("Cannot expire token", e1
);
388 public void expireAuthTokens(Subject subject
) {
389 Set
<String
> tokens
= TokenUtils
.tokensUsed(subject
, NodeConstants
.TOKENS_BASEDN
);
390 for (String token
: tokens
)
391 expireAuthToken(token
);
395 public void addAuthToken(String userDn
, String token
, Integer hours
, String
... roles
) {
396 addAuthToken(userDn
, token
, ZonedDateTime
.now().plusHours(hours
), roles
);
400 public void addAuthToken(String userDn
, String token
, ZonedDateTime expiryDate
, String
... roles
) {
402 userTransaction
.begin();
403 User user
= (User
) userAdmin
.getRole(userDn
);
404 String tokenDn
= cn
+ "=" + token
+ "," + NodeConstants
.TOKENS_BASEDN
;
405 Group tokenGroup
= (Group
) userAdmin
.createRole(tokenDn
, Role
.GROUP
);
407 for (String role
: roles
) {
408 Role r
= userAdmin
.getRole(role
);
410 tokenGroup
.addMember(r
);
412 if (!role
.equals(NodeConstants
.ROLE_USER
)) {
413 throw new IllegalStateException(
414 "Cannot add role " + role
+ " to token " + token
+ " for " + userDn
);
418 tokenGroup
.getProperties().put(owner
.name(), user
.getName());
419 if (expiryDate
!= null) {
420 String ldapDate
= NamingUtils
.instantToLdapDate(expiryDate
);
421 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
423 userTransaction
.commit();
424 } catch (Exception e1
) {
426 if (userTransaction
.getStatus() != Status
.STATUS_NO_TRANSACTION
)
427 userTransaction
.rollback();
428 } catch (Exception e2
) {
429 if (log
.isTraceEnabled())
430 log
.trace("Cannot rollback transaction", e2
);
432 throw new RuntimeException("Cannot add token", e1
);
436 // public User createUserFromPerson(Node person) {
437 // String email = JcrUtils.get(person, LdapAttrs.mail.property());
438 // String dn = buildDefaultDN(email, Role.USER);
441 // userTransaction.begin();
442 // user = (User) userAdmin.createRole(dn, Role.USER);
443 // Dictionary<String, Object> userProperties = user.getProperties();
444 // String name = JcrUtils.get(person, LdapAttrs.displayName.property());
445 // userProperties.put(LdapAttrs.cn.name(), name);
446 // userProperties.put(LdapAttrs.displayName.name(), name);
447 // String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
448 // String surname = JcrUtils.get(person, LdapAttrs.sn.property());
449 // userProperties.put(LdapAttrs.givenName.name(), givenName);
450 // userProperties.put(LdapAttrs.sn.name(), surname);
451 // userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
452 // userTransaction.commit();
453 // } catch (Exception e) {
455 // userTransaction.rollback();
456 // } catch (Exception e1) {
457 // log.error("Could not roll back", e1);
459 // if (e instanceof RuntimeException)
460 // throw (RuntimeException) e;
462 // throw new RuntimeException("Cannot create user", e);
467 public UserAdmin
getUserAdmin() {
471 public UserTransaction
getUserTransaction() {
472 return userTransaction
;
475 /* DEPENDENCY INJECTION */
476 public void setUserAdmin(UserAdmin userAdmin
) {
477 this.userAdmin
= userAdmin
;
478 // this.serviceProperties = serviceProperties;
481 public void setUserTransaction(UserTransaction userTransaction
) {
482 this.userTransaction
= userTransaction
;
485 public void addUserDirectory(UserDirectory userDirectory
, Map
<String
, String
> properties
) {
486 userDirectories
.put(userDirectory
, new Hashtable
<>(properties
));
489 public void removeUserDirectory(UserDirectory userDirectory
, Map
<String
, String
> properties
) {
490 userDirectories
.remove(userDirectory
);