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