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
.Dictionary
;
12 import java
.util
.HashMap
;
13 import java
.util
.HashSet
;
14 import java
.util
.List
;
17 import java
.util
.UUID
;
19 import javax
.jcr
.Node
;
20 import javax
.naming
.InvalidNameException
;
21 import javax
.naming
.ldap
.LdapName
;
22 import javax
.security
.auth
.Subject
;
23 import javax
.transaction
.Status
;
24 import javax
.transaction
.UserTransaction
;
26 import org
.apache
.commons
.logging
.Log
;
27 import org
.apache
.commons
.logging
.LogFactory
;
28 import org
.argeo
.api
.NodeConstants
;
29 import org
.argeo
.cms
.CmsUserManager
;
30 import org
.argeo
.cms
.auth
.CurrentUser
;
31 import org
.argeo
.cms
.auth
.UserAdminUtils
;
32 import org
.argeo
.jcr
.JcrUtils
;
33 import org
.argeo
.naming
.LdapAttrs
;
34 import org
.argeo
.naming
.NamingUtils
;
35 import org
.argeo
.naming
.SharedSecret
;
36 import org
.argeo
.osgi
.useradmin
.TokenUtils
;
37 import org
.argeo
.osgi
.useradmin
.UserAdminConf
;
38 import org
.osgi
.framework
.InvalidSyntaxException
;
39 import org
.osgi
.service
.useradmin
.Authorization
;
40 import org
.osgi
.service
.useradmin
.Group
;
41 import org
.osgi
.service
.useradmin
.Role
;
42 import org
.osgi
.service
.useradmin
.User
;
43 import org
.osgi
.service
.useradmin
.UserAdmin
;
46 * Canonical implementation of the people {@link CmsUserManager}. Wraps
47 * interaction with users and groups.
49 * In a *READ-ONLY* mode. We want to be able to:
51 * <li>Retrieve my user and corresponding information (main info,
53 * <li>List all local groups (not the system roles)</li>
54 * <li>If sufficient rights: retrieve a given user and its information</li>
57 public class CmsUserManagerImpl
implements CmsUserManager
{
58 private final static Log log
= LogFactory
.getLog(CmsUserManagerImpl
.class);
60 private UserAdmin userAdmin
;
61 private Map
<String
, String
> serviceProperties
;
62 private UserTransaction userTransaction
;
65 public String
getMyMail() {
66 return getUserMail(CurrentUser
.getUsername());
70 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
71 return userAdmin
.getRoles(filter
);
74 // ALL USER: WARNING access to this will be later reduced
76 /** Retrieve a user given his dn */
77 public User
getUser(String dn
) {
78 return (User
) getUserAdmin().getRole(dn
);
81 /** Can be a group or a user */
82 public String
getUserDisplayName(String dn
) {
83 // FIXME: during initialisation phase, the system logs "admin" as user
84 // name rather than the corresponding dn
85 if ("admin".equals(dn
))
86 return "System Administrator";
88 return UserAdminUtils
.getUserDisplayName(getUserAdmin(), dn
);
92 public String
getUserMail(String dn
) {
93 return UserAdminUtils
.getUserMail(getUserAdmin(), dn
);
96 /** Lists all roles of the given user */
98 public String
[] getUserRoles(String dn
) {
99 Authorization currAuth
= getUserAdmin().getAuthorization(getUser(dn
));
100 return currAuth
.getRoles();
104 public boolean isUserInRole(String userDn
, String roleDn
) {
105 String
[] roles
= getUserRoles(userDn
);
106 for (String role
: roles
) {
107 if (role
.equalsIgnoreCase(roleDn
))
113 private final String
[] knownProps
= { LdapAttrs
.cn
.name(), LdapAttrs
.sn
.name(), LdapAttrs
.givenName
.name(),
114 LdapAttrs
.uid
.name() };
116 public Set
<User
> listUsersInGroup(String groupDn
, String filter
) {
117 Group group
= (Group
) userAdmin
.getRole(groupDn
);
119 throw new IllegalArgumentException("Group " + groupDn
+ " not found");
120 Set
<User
> users
= new HashSet
<User
>();
121 addUsers(users
, group
, filter
);
125 /** Recursively add users to list */
126 private void addUsers(Set
<User
> users
, Group group
, String filter
) {
127 Role
[] roles
= group
.getMembers();
128 for (Role role
: roles
) {
129 if (role
.getType() == Role
.GROUP
) {
130 addUsers(users
, (Group
) role
, filter
);
131 } else if (role
.getType() == Role
.USER
) {
132 if (match(role
, filter
))
133 users
.add((User
) role
);
140 public List
<User
> listGroups(String filter
, boolean includeUsers
, boolean includeSystemRoles
) {
143 roles
= getUserAdmin().getRoles(filter
);
144 } catch (InvalidSyntaxException e
) {
145 throw new IllegalArgumentException("Unable to get roles with filter: " + filter
, e
);
148 List
<User
> users
= new ArrayList
<User
>();
149 for (Role role
: roles
) {
150 if ((includeUsers
&& role
.getType() == Role
.USER
|| role
.getType() == Role
.GROUP
) && !users
.contains(role
)
151 && (includeSystemRoles
|| !role
.getName().toLowerCase().endsWith(NodeConstants
.ROLES_BASEDN
))) {
152 if (match(role
, filter
))
153 users
.add((User
) role
);
159 private boolean match(Role role
, String filter
) {
160 boolean doFilter
= filter
!= null && !"".equals(filter
);
162 for (String prop
: knownProps
) {
163 Object currProp
= null;
165 currProp
= role
.getProperties().get(prop
);
166 } catch (Exception e
) {
169 if (currProp
!= null) {
170 String currPropStr
= ((String
) currProp
).toLowerCase();
171 if (currPropStr
.contains(filter
.toLowerCase())) {
182 public User
getUserFromLocalId(String localId
) {
183 User user
= getUserAdmin().getUser(LdapAttrs
.uid
.name(), localId
);
185 user
= getUserAdmin().getUser(LdapAttrs
.cn
.name(), localId
);
190 public String
buildDefaultDN(String localId
, int type
) {
191 return buildDistinguishedName(localId
, getDefaultDomainName(), type
);
195 public String
getDefaultDomainName() {
196 Map
<String
, String
> dns
= getKnownBaseDns(true);
198 return dns
.keySet().iterator().next();
200 throw new IllegalStateException("Current context contains " + dns
.size() + " base dns: "
201 + dns
.keySet().toString() + ". Unable to chose a default one.");
204 public Map
<String
, String
> getKnownBaseDns(boolean onlyWritable
) {
205 Map
<String
, String
> dns
= new HashMap
<String
, String
>();
206 String
[] propertyKeys
= serviceProperties
.keySet().toArray(new String
[serviceProperties
.size()]);
207 for (String uri
: propertyKeys
) {
208 if (!uri
.startsWith("/"))
210 Dictionary
<String
, ?
> props
= UserAdminConf
.uriAsProperties(uri
);
211 String readOnly
= UserAdminConf
.readOnly
.getValue(props
);
212 String baseDn
= UserAdminConf
.baseDn
.getValue(props
);
214 if (onlyWritable
&& "true".equals(readOnly
))
216 if (baseDn
.equalsIgnoreCase(NodeConstants
.ROLES_BASEDN
))
218 if (baseDn
.equalsIgnoreCase(NodeConstants
.TOKENS_BASEDN
))
220 dns
.put(baseDn
, uri
);
225 public String
buildDistinguishedName(String localId
, String baseDn
, int type
) {
226 Map
<String
, String
> dns
= getKnownBaseDns(true);
227 Dictionary
<String
, ?
> props
= UserAdminConf
.uriAsProperties(dns
.get(baseDn
));
229 if (Role
.GROUP
== type
)
230 dn
= LdapAttrs
.cn
.name() + "=" + localId
+ "," + UserAdminConf
.groupBase
.getValue(props
) + "," + baseDn
;
231 else if (Role
.USER
== type
)
232 dn
= LdapAttrs
.uid
.name() + "=" + localId
+ "," + UserAdminConf
.userBase
.getValue(props
) + "," + baseDn
;
234 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId
);
239 public void changeOwnPassword(char[] oldPassword
, char[] newPassword
) {
240 String name
= CurrentUser
.getUsername();
243 dn
= new LdapName(name
);
244 } catch (InvalidNameException e
) {
245 throw new IllegalArgumentException("Invalid user dn " + name
, e
);
247 User user
= (User
) userAdmin
.getRole(dn
.toString());
248 if (!user
.hasCredential(null, oldPassword
))
249 throw new IllegalArgumentException("Invalid password");
250 if (Arrays
.equals(newPassword
, new char[0]))
251 throw new IllegalArgumentException("New password empty");
253 userTransaction
.begin();
254 user
.getCredentials().put(null, newPassword
);
255 userTransaction
.commit();
256 } catch (Exception e
) {
258 userTransaction
.rollback();
259 } catch (Exception e1
) {
260 log
.error("Could not roll back", e1
);
262 if (e
instanceof RuntimeException
)
263 throw (RuntimeException
) e
;
265 throw new RuntimeException("Cannot change password", e
);
269 public void resetPassword(String username
, char[] newPassword
) {
272 dn
= new LdapName(username
);
273 } catch (InvalidNameException e
) {
274 throw new IllegalArgumentException("Invalid user dn " + username
, e
);
276 User user
= (User
) userAdmin
.getRole(dn
.toString());
277 if (Arrays
.equals(newPassword
, new char[0]))
278 throw new IllegalArgumentException("New password empty");
280 userTransaction
.begin();
281 user
.getCredentials().put(null, newPassword
);
282 userTransaction
.commit();
283 } catch (Exception e
) {
285 userTransaction
.rollback();
286 } catch (Exception e1
) {
287 log
.error("Could not roll back", e1
);
289 if (e
instanceof RuntimeException
)
290 throw (RuntimeException
) e
;
292 throw new RuntimeException("Cannot change password", e
);
296 public String
addSharedSecret(String email
, int hours
) {
297 User user
= (User
) userAdmin
.getUser(LdapAttrs
.mail
.name(), email
);
299 userTransaction
.begin();
300 String uuid
= UUID
.randomUUID().toString();
301 SharedSecret sharedSecret
= new SharedSecret(hours
, uuid
);
302 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
303 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
304 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
);
320 public String
addSharedSecret(String username
, String authInfo
, String authToken
) {
322 userTransaction
.begin();
323 User user
= (User
) userAdmin
.getRole(username
);
324 SharedSecret sharedSecret
= new SharedSecret(authInfo
, authToken
);
325 user
.getCredentials().put(SharedSecret
.X_SHARED_SECRET
, sharedSecret
.toAuthPassword());
326 String tokenStr
= sharedSecret
.getAuthInfo() + '$' + sharedSecret
.getAuthValue();
327 userTransaction
.commit();
329 } catch (Exception e1
) {
331 if (userTransaction
.getStatus() != Status
.STATUS_NO_TRANSACTION
)
332 userTransaction
.rollback();
333 } catch (Exception e2
) {
334 if (log
.isTraceEnabled())
335 log
.trace("Cannot rollback transaction", e2
);
337 throw new RuntimeException("Cannot add shared secret", e1
);
342 public void expireAuthToken(String token
) {
344 userTransaction
.begin();
345 String dn
= cn
+ "=" + token
+ "," + NodeConstants
.TOKENS_BASEDN
;
346 Group tokenGroup
= (Group
) userAdmin
.getRole(dn
);
347 String ldapDate
= NamingUtils
.instantToLdapDate(ZonedDateTime
.now(ZoneOffset
.UTC
));
348 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
349 userTransaction
.commit();
350 if (log
.isDebugEnabled())
351 log
.debug("Token " + token
+ " expired.");
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 expire token", e1
);
365 public void expireAuthTokens(Subject subject
) {
366 Set
<String
> tokens
= TokenUtils
.tokensUsed(subject
, NodeConstants
.TOKENS_BASEDN
);
367 for (String token
: tokens
)
368 expireAuthToken(token
);
372 public void addAuthToken(String userDn
, String token
, Integer hours
, String
... roles
) {
373 addAuthToken(userDn
, token
, ZonedDateTime
.now().plusHours(hours
), roles
);
377 public void addAuthToken(String userDn
, String token
, ZonedDateTime expiryDate
, String
... roles
) {
379 userTransaction
.begin();
380 User user
= (User
) userAdmin
.getRole(userDn
);
381 String tokenDn
= cn
+ "=" + token
+ "," + NodeConstants
.TOKENS_BASEDN
;
382 Group tokenGroup
= (Group
) userAdmin
.createRole(tokenDn
, Role
.GROUP
);
384 for (String role
: roles
) {
385 Role r
= userAdmin
.getRole(role
);
387 tokenGroup
.addMember(r
);
389 if (!role
.equals(NodeConstants
.ROLE_USER
)) {
390 throw new IllegalStateException(
391 "Cannot add role " + role
+ " to token " + token
+ " for " + userDn
);
395 tokenGroup
.getProperties().put(owner
.name(), user
.getName());
396 if (expiryDate
!= null) {
397 String ldapDate
= NamingUtils
.instantToLdapDate(expiryDate
);
398 tokenGroup
.getProperties().put(description
.name(), ldapDate
);
400 userTransaction
.commit();
401 } catch (Exception e1
) {
403 if (userTransaction
.getStatus() != Status
.STATUS_NO_TRANSACTION
)
404 userTransaction
.rollback();
405 } catch (Exception e2
) {
406 if (log
.isTraceEnabled())
407 log
.trace("Cannot rollback transaction", e2
);
409 throw new RuntimeException("Cannot add token", e1
);
413 public User
createUserFromPerson(Node person
) {
414 String email
= JcrUtils
.get(person
, LdapAttrs
.mail
.property());
415 String dn
= buildDefaultDN(email
, Role
.USER
);
418 userTransaction
.begin();
419 user
= (User
) userAdmin
.createRole(dn
, Role
.USER
);
420 Dictionary
<String
, Object
> userProperties
= user
.getProperties();
421 String name
= JcrUtils
.get(person
, LdapAttrs
.displayName
.property());
422 userProperties
.put(LdapAttrs
.cn
.name(), name
);
423 userProperties
.put(LdapAttrs
.displayName
.name(), name
);
424 String givenName
= JcrUtils
.get(person
, LdapAttrs
.givenName
.property());
425 String surname
= JcrUtils
.get(person
, LdapAttrs
.sn
.property());
426 userProperties
.put(LdapAttrs
.givenName
.name(), givenName
);
427 userProperties
.put(LdapAttrs
.sn
.name(), surname
);
428 userProperties
.put(LdapAttrs
.mail
.name(), email
.toLowerCase());
429 userTransaction
.commit();
430 } catch (Exception e
) {
432 userTransaction
.rollback();
433 } catch (Exception e1
) {
434 log
.error("Could not roll back", e1
);
436 if (e
instanceof RuntimeException
)
437 throw (RuntimeException
) e
;
439 throw new RuntimeException("Cannot create user", e
);
444 public UserAdmin
getUserAdmin() {
448 public UserTransaction
getUserTransaction() {
449 return userTransaction
;
452 /* DEPENDENCY INJECTION */
453 public void setUserAdmin(UserAdmin userAdmin
, Map
<String
, String
> serviceProperties
) {
454 this.userAdmin
= userAdmin
;
455 this.serviceProperties
= serviceProperties
;
458 public void setUserTransaction(UserTransaction userTransaction
) {
459 this.userTransaction
= userTransaction
;