]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
Remove static default UUID factory
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / auth / CmsUserManagerImpl.java
1 package org.argeo.cms.internal.auth;
2
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;
6
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;
15 import java.util.Map;
16 import java.util.NavigableMap;
17 import java.util.Set;
18 import java.util.TreeMap;
19 import java.util.TreeSet;
20 import java.util.UUID;
21
22 import javax.naming.InvalidNameException;
23 import javax.naming.ldap.LdapName;
24 import javax.security.auth.Subject;
25
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;
45
46 /**
47 * Canonical implementation of the people {@link CmsUserManager}. Wraps
48 * interaction with users and groups.
49 *
50 * In a *READ-ONLY* mode. We want to be able to:
51 * <ul>
52 * <li>Retrieve my user and corresponding information (main info,
53 * groups...)</li>
54 * <li>List all local groups (not the system roles)</li>
55 * <li>If sufficient rights: retrieve a given user and its information</li>
56 * </ul>
57 */
58 public class CmsUserManagerImpl implements CmsUserManager {
59 private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
60
61 private UserAdmin userAdmin;
62 // private Map<String, String> serviceProperties;
63 private WorkTransaction userTransaction;
64
65 // private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
66 // .synchronizedMap(new LinkedHashMap<>());
67
68 private Set<UserDirectory> userDirectories = new HashSet<>();
69
70 public void start() {
71 log.debug(() -> "CMS user manager available");
72 }
73
74 public void stop() {
75
76 }
77
78 @Override
79 public String getMyMail() {
80 return getUserMail(CurrentUser.getUsername());
81 }
82
83 @Override
84 public Role[] getRoles(String filter) throws InvalidSyntaxException {
85 return userAdmin.getRoles(filter);
86 }
87
88 // ALL USER: WARNING access to this will be later reduced
89
90 /** Retrieve a user given his dn */
91 public User getUser(String dn) {
92 return (User) getUserAdmin().getRole(dn);
93 }
94
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";
101 else
102 return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
103 }
104
105 @Override
106 public String getUserMail(String dn) {
107 return UserAdminUtils.getUserMail(getUserAdmin(), dn);
108 }
109
110 /** Lists all roles of the given user */
111 @Override
112 public String[] getUserRoles(String dn) {
113 Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
114 return currAuth.getRoles();
115 }
116
117 @Override
118 public boolean isUserInRole(String userDn, String roleDn) {
119 String[] roles = getUserRoles(userDn);
120 for (String role : roles) {
121 if (role.equalsIgnoreCase(roleDn))
122 return true;
123 }
124 return false;
125 }
126
127 private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
128 LdapAttrs.uid.name() };
129
130 public Set<User> listUsersInGroup(String groupDn, String filter) {
131 Group group = (Group) userAdmin.getRole(groupDn);
132 if (group == null)
133 throw new IllegalArgumentException("Group " + groupDn + " not found");
134 Set<User> users = new HashSet<User>();
135 addUsers(users, group, filter);
136 return users;
137 }
138
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);
148 } else {
149 // ignore
150 }
151 }
152 }
153
154 public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
155 Role[] roles = null;
156 try {
157 roles = getUserAdmin().getRoles(filter);
158 } catch (InvalidSyntaxException e) {
159 throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
160 }
161
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);
168 }
169 }
170 return users;
171 }
172
173 private boolean match(Role role, String filter) {
174 boolean doFilter = filter != null && !"".equals(filter);
175 if (doFilter) {
176 for (String prop : knownProps) {
177 Object currProp = null;
178 try {
179 currProp = role.getProperties().get(prop);
180 } catch (Exception e) {
181 throw e;
182 }
183 if (currProp != null) {
184 String currPropStr = ((String) currProp).toLowerCase();
185 if (currPropStr.contains(filter.toLowerCase())) {
186 return true;
187 }
188 }
189 }
190 return false;
191 } else
192 return true;
193 }
194
195 @Override
196 public User getUserFromLocalId(String localId) {
197 User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
198 if (user == null)
199 user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
200 return user;
201 }
202
203 @Override
204 public String buildDefaultDN(String localId, int type) {
205 return buildDistinguishedName(localId, getDefaultDomainName(), type);
206 }
207
208 @Override
209 public String getDefaultDomainName() {
210 Map<String, String> dns = getKnownBaseDns(true);
211 if (dns.size() == 1)
212 return dns.keySet().iterator().next();
213 else
214 throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
215 + dns.keySet().toString() + ". Unable to chose a default one.");
216 }
217
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("/"))
223 // continue;
224 // Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
225 // String readOnly = UserAdminConf.readOnly.getValue(props);
226 // String baseDn = UserAdminConf.baseDn.getValue(props);
227 //
228 // if (onlyWritable && "true".equals(readOnly))
229 // continue;
230 // if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
231 // continue;
232 // if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
233 // continue;
234 // dns.put(baseDn, uri);
235 // }
236 // return dns;
237 // }
238
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();
244
245 if (onlyWritable && readOnly)
246 continue;
247 if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
248 continue;
249 if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
250 continue;
251 dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString());
252
253 }
254 return dns;
255 }
256
257 public Set<UserDirectory> getUserDirectories() {
258 TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext()));
259 res.addAll(userDirectories);
260 return res;
261 }
262
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));
266 String dn = null;
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;
271 else
272 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
273 return dn;
274 }
275
276 @Override
277 public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
278 String name = CurrentUser.getUsername();
279 LdapName dn;
280 try {
281 dn = new LdapName(name);
282 } catch (InvalidNameException e) {
283 throw new IllegalArgumentException("Invalid user dn " + name, e);
284 }
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");
290 try {
291 userTransaction.begin();
292 user.getCredentials().put(null, newPassword);
293 userTransaction.commit();
294 } catch (Exception e) {
295 try {
296 userTransaction.rollback();
297 } catch (Exception e1) {
298 log.error("Could not roll back", e1);
299 }
300 if (e instanceof RuntimeException)
301 throw (RuntimeException) e;
302 else
303 throw new RuntimeException("Cannot change password", e);
304 }
305 }
306
307 public void resetPassword(String username, char[] newPassword) {
308 LdapName dn;
309 try {
310 dn = new LdapName(username);
311 } catch (InvalidNameException e) {
312 throw new IllegalArgumentException("Invalid user dn " + username, e);
313 }
314 User user = (User) userAdmin.getRole(dn.toString());
315 if (Arrays.equals(newPassword, new char[0]))
316 throw new IllegalArgumentException("New password empty");
317 try {
318 userTransaction.begin();
319 user.getCredentials().put(null, newPassword);
320 userTransaction.commit();
321 } catch (Exception e) {
322 try {
323 userTransaction.rollback();
324 } catch (Exception e1) {
325 log.error("Could not roll back", e1);
326 }
327 if (e instanceof RuntimeException)
328 throw (RuntimeException) e;
329 else
330 throw new RuntimeException("Cannot change password", e);
331 }
332 }
333
334 public String addSharedSecret(String email, int hours) {
335 User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
336 try {
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();
343 return tokenStr;
344 } catch (Exception e) {
345 try {
346 userTransaction.rollback();
347 } catch (Exception e1) {
348 log.error("Could not roll back", e1);
349 }
350 if (e instanceof RuntimeException)
351 throw (RuntimeException) e;
352 else
353 throw new RuntimeException("Cannot change password", e);
354 }
355 }
356
357 @Deprecated
358 public String addSharedSecret(String username, String authInfo, String authToken) {
359 try {
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();
366 return tokenStr;
367 } catch (Exception e1) {
368 try {
369 if (!userTransaction.isNoTransactionStatus())
370 userTransaction.rollback();
371 } catch (Exception e2) {
372 if (log.isTraceEnabled())
373 log.trace("Cannot rollback transaction", e2);
374 }
375 throw new RuntimeException("Cannot add shared secret", e1);
376 }
377 }
378
379 @Override
380 public void expireAuthToken(String token) {
381 try {
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) {
391 try {
392 if (!userTransaction.isNoTransactionStatus())
393 userTransaction.rollback();
394 } catch (Exception e2) {
395 if (log.isTraceEnabled())
396 log.trace("Cannot rollback transaction", e2);
397 }
398 throw new RuntimeException("Cannot expire token", e1);
399 }
400 }
401
402 @Override
403 public void expireAuthTokens(Subject subject) {
404 Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
405 for (String token : tokens)
406 expireAuthToken(token);
407 }
408
409 @Override
410 public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
411 addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
412 }
413
414 @Override
415 public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
416 try {
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);
421 if (roles != null)
422 for (String role : roles) {
423 Role r = userAdmin.getRole(role);
424 if (r != null)
425 tokenGroup.addMember(r);
426 else {
427 if (!role.equals(CmsConstants.ROLE_USER)) {
428 throw new IllegalStateException(
429 "Cannot add role " + role + " to token " + token + " for " + userDn);
430 }
431 }
432 }
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);
437 }
438 userTransaction.commit();
439 } catch (Exception e1) {
440 try {
441 if (!userTransaction.isNoTransactionStatus())
442 userTransaction.rollback();
443 } catch (Exception e2) {
444 if (log.isTraceEnabled())
445 log.trace("Cannot rollback transaction", e2);
446 }
447 throw new RuntimeException("Cannot add token", e1);
448 }
449 }
450
451 @Override
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);
458 }
459 }
460 if (possible.size() == 0)
461 throw new IllegalStateException("No user directory found for user " + name);
462 return possible.lastEntry().getValue();
463 }
464
465 // public User createUserFromPerson(Node person) {
466 // String email = JcrUtils.get(person, LdapAttrs.mail.property());
467 // String dn = buildDefaultDN(email, Role.USER);
468 // User user;
469 // try {
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) {
483 // try {
484 // userTransaction.rollback();
485 // } catch (Exception e1) {
486 // log.error("Could not roll back", e1);
487 // }
488 // if (e instanceof RuntimeException)
489 // throw (RuntimeException) e;
490 // else
491 // throw new RuntimeException("Cannot create user", e);
492 // }
493 // return user;
494 // }
495
496 public UserAdmin getUserAdmin() {
497 return userAdmin;
498 }
499
500 // public UserTransaction getUserTransaction() {
501 // return userTransaction;
502 // }
503
504 /* DEPENDENCY INJECTION */
505 public void setUserAdmin(UserAdmin userAdmin) {
506 this.userAdmin = userAdmin;
507
508 if (userAdmin instanceof AggregatingUserAdmin) {
509 userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories();
510 } else {
511 throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported.");
512 }
513
514 // this.serviceProperties = serviceProperties;
515 }
516
517 public void setUserTransaction(WorkTransaction userTransaction) {
518 this.userTransaction = userTransaction;
519 }
520
521 // public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
522 // userDirectories.put(userDirectory, new Hashtable<>(properties));
523 // }
524 //
525 // public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
526 // userDirectories.remove(userDirectory);
527 // }
528
529 }