]> git.argeo.org Git - lgpl/argeo-commons.git/blob - CmsUserManagerImpl.java
84562ebd16660a860df397778421037581f9ba64
[lgpl/argeo-commons.git] / 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.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;
18 import java.util.Map;
19 import java.util.Set;
20 import java.util.TreeSet;
21 import java.util.UUID;
22
23 import javax.naming.InvalidNameException;
24 import javax.naming.ldap.LdapName;
25 import javax.security.auth.Subject;
26
27 import org.argeo.api.cms.CmsConstants;
28 import org.argeo.api.cms.CmsLog;
29 import org.argeo.cms.CmsUserManager;
30 import org.argeo.cms.auth.CurrentUser;
31 import org.argeo.cms.auth.UserAdminUtils;
32 import org.argeo.osgi.transaction.WorkTransaction;
33 import org.argeo.osgi.useradmin.TokenUtils;
34 import org.argeo.osgi.useradmin.UserAdminConf;
35 import org.argeo.osgi.useradmin.UserDirectory;
36 import org.argeo.util.naming.LdapAttrs;
37 import org.argeo.util.naming.NamingUtils;
38 import org.argeo.util.naming.SharedSecret;
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 @Override
69 public String getMyMail() {
70 return getUserMail(CurrentUser.getUsername());
71 }
72
73 @Override
74 public Role[] getRoles(String filter) throws InvalidSyntaxException {
75 return userAdmin.getRoles(filter);
76 }
77
78 // ALL USER: WARNING access to this will be later reduced
79
80 /** Retrieve a user given his dn */
81 public User getUser(String dn) {
82 return (User) getUserAdmin().getRole(dn);
83 }
84
85 /** Can be a group or a user */
86 public String getUserDisplayName(String dn) {
87 // FIXME: during initialisation phase, the system logs "admin" as user
88 // name rather than the corresponding dn
89 if ("admin".equals(dn))
90 return "System Administrator";
91 else
92 return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
93 }
94
95 @Override
96 public String getUserMail(String dn) {
97 return UserAdminUtils.getUserMail(getUserAdmin(), dn);
98 }
99
100 /** Lists all roles of the given user */
101 @Override
102 public String[] getUserRoles(String dn) {
103 Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
104 return currAuth.getRoles();
105 }
106
107 @Override
108 public boolean isUserInRole(String userDn, String roleDn) {
109 String[] roles = getUserRoles(userDn);
110 for (String role : roles) {
111 if (role.equalsIgnoreCase(roleDn))
112 return true;
113 }
114 return false;
115 }
116
117 private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
118 LdapAttrs.uid.name() };
119
120 public Set<User> listUsersInGroup(String groupDn, String filter) {
121 Group group = (Group) userAdmin.getRole(groupDn);
122 if (group == null)
123 throw new IllegalArgumentException("Group " + groupDn + " not found");
124 Set<User> users = new HashSet<User>();
125 addUsers(users, group, filter);
126 return users;
127 }
128
129 /** Recursively add users to list */
130 private void addUsers(Set<User> users, Group group, String filter) {
131 Role[] roles = group.getMembers();
132 for (Role role : roles) {
133 if (role.getType() == Role.GROUP) {
134 addUsers(users, (Group) role, filter);
135 } else if (role.getType() == Role.USER) {
136 if (match(role, filter))
137 users.add((User) role);
138 } else {
139 // ignore
140 }
141 }
142 }
143
144 public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
145 Role[] roles = null;
146 try {
147 roles = getUserAdmin().getRoles(filter);
148 } catch (InvalidSyntaxException e) {
149 throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
150 }
151
152 List<User> users = new ArrayList<User>();
153 for (Role role : roles) {
154 if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
155 && (includeSystemRoles || !role.getName().toLowerCase().endsWith(CmsConstants.ROLES_BASEDN))) {
156 if (match(role, filter))
157 users.add((User) role);
158 }
159 }
160 return users;
161 }
162
163 private boolean match(Role role, String filter) {
164 boolean doFilter = filter != null && !"".equals(filter);
165 if (doFilter) {
166 for (String prop : knownProps) {
167 Object currProp = null;
168 try {
169 currProp = role.getProperties().get(prop);
170 } catch (Exception e) {
171 throw e;
172 }
173 if (currProp != null) {
174 String currPropStr = ((String) currProp).toLowerCase();
175 if (currPropStr.contains(filter.toLowerCase())) {
176 return true;
177 }
178 }
179 }
180 return false;
181 } else
182 return true;
183 }
184
185 @Override
186 public User getUserFromLocalId(String localId) {
187 User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
188 if (user == null)
189 user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
190 return user;
191 }
192
193 @Override
194 public String buildDefaultDN(String localId, int type) {
195 return buildDistinguishedName(localId, getDefaultDomainName(), type);
196 }
197
198 @Override
199 public String getDefaultDomainName() {
200 Map<String, String> dns = getKnownBaseDns(true);
201 if (dns.size() == 1)
202 return dns.keySet().iterator().next();
203 else
204 throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
205 + dns.keySet().toString() + ". Unable to chose a default one.");
206 }
207
208 // public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
209 // Map<String, String> dns = new HashMap<String, String>();
210 // String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
211 // for (String uri : propertyKeys) {
212 // if (!uri.startsWith("/"))
213 // continue;
214 // Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
215 // String readOnly = UserAdminConf.readOnly.getValue(props);
216 // String baseDn = UserAdminConf.baseDn.getValue(props);
217 //
218 // if (onlyWritable && "true".equals(readOnly))
219 // continue;
220 // if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
221 // continue;
222 // if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
223 // continue;
224 // dns.put(baseDn, uri);
225 // }
226 // return dns;
227 // }
228
229 public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
230 Map<String, String> dns = new HashMap<String, String>();
231 for (UserDirectory userDirectory : userDirectories.keySet()) {
232 Boolean readOnly = userDirectory.isReadOnly();
233 String baseDn = userDirectory.getBasePath();
234
235 if (onlyWritable && readOnly)
236 continue;
237 if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
238 continue;
239 if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
240 continue;
241 dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
242
243 }
244 return dns;
245 }
246
247 public Set<UserDirectory> getUserDirectories() {
248 TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBasePath().compareTo(o2.getBasePath()));
249 res.addAll(userDirectories.keySet());
250 return res;
251 }
252
253 public String buildDistinguishedName(String localId, String baseDn, int type) {
254 Map<String, String> dns = getKnownBaseDns(true);
255 Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
256 String dn = null;
257 if (Role.GROUP == type)
258 dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
259 else if (Role.USER == type)
260 dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
261 else
262 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
263 return dn;
264 }
265
266 @Override
267 public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
268 String name = CurrentUser.getUsername();
269 LdapName dn;
270 try {
271 dn = new LdapName(name);
272 } catch (InvalidNameException e) {
273 throw new IllegalArgumentException("Invalid user dn " + name, e);
274 }
275 User user = (User) userAdmin.getRole(dn.toString());
276 if (!user.hasCredential(null, oldPassword))
277 throw new IllegalArgumentException("Invalid password");
278 if (Arrays.equals(newPassword, new char[0]))
279 throw new IllegalArgumentException("New password empty");
280 try {
281 userTransaction.begin();
282 user.getCredentials().put(null, newPassword);
283 userTransaction.commit();
284 } catch (Exception e) {
285 try {
286 userTransaction.rollback();
287 } catch (Exception e1) {
288 log.error("Could not roll back", e1);
289 }
290 if (e instanceof RuntimeException)
291 throw (RuntimeException) e;
292 else
293 throw new RuntimeException("Cannot change password", e);
294 }
295 }
296
297 public void resetPassword(String username, char[] newPassword) {
298 LdapName dn;
299 try {
300 dn = new LdapName(username);
301 } catch (InvalidNameException e) {
302 throw new IllegalArgumentException("Invalid user dn " + username, e);
303 }
304 User user = (User) userAdmin.getRole(dn.toString());
305 if (Arrays.equals(newPassword, new char[0]))
306 throw new IllegalArgumentException("New password empty");
307 try {
308 userTransaction.begin();
309 user.getCredentials().put(null, newPassword);
310 userTransaction.commit();
311 } catch (Exception e) {
312 try {
313 userTransaction.rollback();
314 } catch (Exception e1) {
315 log.error("Could not roll back", e1);
316 }
317 if (e instanceof RuntimeException)
318 throw (RuntimeException) e;
319 else
320 throw new RuntimeException("Cannot change password", e);
321 }
322 }
323
324 public String addSharedSecret(String email, int hours) {
325 User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
326 try {
327 userTransaction.begin();
328 String uuid = UUID.randomUUID().toString();
329 SharedSecret sharedSecret = new SharedSecret(hours, uuid);
330 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
331 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
332 userTransaction.commit();
333 return tokenStr;
334 } catch (Exception e) {
335 try {
336 userTransaction.rollback();
337 } catch (Exception e1) {
338 log.error("Could not roll back", e1);
339 }
340 if (e instanceof RuntimeException)
341 throw (RuntimeException) e;
342 else
343 throw new RuntimeException("Cannot change password", e);
344 }
345 }
346
347 @Deprecated
348 public String addSharedSecret(String username, String authInfo, String authToken) {
349 try {
350 userTransaction.begin();
351 User user = (User) userAdmin.getRole(username);
352 SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
353 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
354 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
355 userTransaction.commit();
356 return tokenStr;
357 } catch (Exception e1) {
358 try {
359 if (!userTransaction.isNoTransactionStatus())
360 userTransaction.rollback();
361 } catch (Exception e2) {
362 if (log.isTraceEnabled())
363 log.trace("Cannot rollback transaction", e2);
364 }
365 throw new RuntimeException("Cannot add shared secret", e1);
366 }
367 }
368
369 @Override
370 public void expireAuthToken(String token) {
371 try {
372 userTransaction.begin();
373 String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
374 Group tokenGroup = (Group) userAdmin.getRole(dn);
375 String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
376 tokenGroup.getProperties().put(description.name(), ldapDate);
377 userTransaction.commit();
378 if (log.isDebugEnabled())
379 log.debug("Token " + token + " expired.");
380 } catch (Exception e1) {
381 try {
382 if (!userTransaction.isNoTransactionStatus())
383 userTransaction.rollback();
384 } catch (Exception e2) {
385 if (log.isTraceEnabled())
386 log.trace("Cannot rollback transaction", e2);
387 }
388 throw new RuntimeException("Cannot expire token", e1);
389 }
390 }
391
392 @Override
393 public void expireAuthTokens(Subject subject) {
394 Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
395 for (String token : tokens)
396 expireAuthToken(token);
397 }
398
399 @Override
400 public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
401 addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
402 }
403
404 @Override
405 public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
406 try {
407 userTransaction.begin();
408 User user = (User) userAdmin.getRole(userDn);
409 String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
410 Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
411 if (roles != null)
412 for (String role : roles) {
413 Role r = userAdmin.getRole(role);
414 if (r != null)
415 tokenGroup.addMember(r);
416 else {
417 if (!role.equals(CmsConstants.ROLE_USER)) {
418 throw new IllegalStateException(
419 "Cannot add role " + role + " to token " + token + " for " + userDn);
420 }
421 }
422 }
423 tokenGroup.getProperties().put(owner.name(), user.getName());
424 if (expiryDate != null) {
425 String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
426 tokenGroup.getProperties().put(description.name(), ldapDate);
427 }
428 userTransaction.commit();
429 } catch (Exception e1) {
430 try {
431 if (!userTransaction.isNoTransactionStatus())
432 userTransaction.rollback();
433 } catch (Exception e2) {
434 if (log.isTraceEnabled())
435 log.trace("Cannot rollback transaction", e2);
436 }
437 throw new RuntimeException("Cannot add token", e1);
438 }
439 }
440
441 // public User createUserFromPerson(Node person) {
442 // String email = JcrUtils.get(person, LdapAttrs.mail.property());
443 // String dn = buildDefaultDN(email, Role.USER);
444 // User user;
445 // try {
446 // userTransaction.begin();
447 // user = (User) userAdmin.createRole(dn, Role.USER);
448 // Dictionary<String, Object> userProperties = user.getProperties();
449 // String name = JcrUtils.get(person, LdapAttrs.displayName.property());
450 // userProperties.put(LdapAttrs.cn.name(), name);
451 // userProperties.put(LdapAttrs.displayName.name(), name);
452 // String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
453 // String surname = JcrUtils.get(person, LdapAttrs.sn.property());
454 // userProperties.put(LdapAttrs.givenName.name(), givenName);
455 // userProperties.put(LdapAttrs.sn.name(), surname);
456 // userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
457 // userTransaction.commit();
458 // } catch (Exception e) {
459 // try {
460 // userTransaction.rollback();
461 // } catch (Exception e1) {
462 // log.error("Could not roll back", e1);
463 // }
464 // if (e instanceof RuntimeException)
465 // throw (RuntimeException) e;
466 // else
467 // throw new RuntimeException("Cannot create user", e);
468 // }
469 // return user;
470 // }
471
472 public UserAdmin getUserAdmin() {
473 return userAdmin;
474 }
475
476 // public UserTransaction getUserTransaction() {
477 // return userTransaction;
478 // }
479
480 /* DEPENDENCY INJECTION */
481 public void setUserAdmin(UserAdmin userAdmin) {
482 this.userAdmin = userAdmin;
483 // this.serviceProperties = serviceProperties;
484 }
485
486 public void setUserTransaction(WorkTransaction userTransaction) {
487 this.userTransaction = userTransaction;
488 }
489
490 public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
491 userDirectories.put(userDirectory, new Hashtable<>(properties));
492 }
493
494 public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
495 userDirectories.remove(userDirectory);
496 }
497
498 }