]> git.argeo.org Git - lgpl/argeo-commons.git/blob - CmsUserManagerImpl.java
5753decf9ed2c565d5f011795371e99620b9154d
[lgpl/argeo-commons.git] / CmsUserManagerImpl.java
1 package org.argeo.cms.internal.auth;
2
3 import static org.argeo.naming.LdapAttrs.cn;
4 import static org.argeo.naming.LdapAttrs.description;
5 import static org.argeo.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.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.naming.LdapAttrs;
33 import org.argeo.naming.NamingUtils;
34 import org.argeo.naming.SharedSecret;
35 import org.argeo.osgi.transaction.WorkTransaction;
36 import org.argeo.osgi.useradmin.TokenUtils;
37 import org.argeo.osgi.useradmin.UserAdminConf;
38 import org.argeo.osgi.useradmin.UserDirectory;
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 Log log = LogFactory.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, String>> 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(NodeConstants.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.getBaseDn().toString();
234
235 if (onlyWritable && readOnly)
236 continue;
237 if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
238 continue;
239 if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
240 continue;
241 dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
242
243 }
244 return dns;
245 }
246
247 public String buildDistinguishedName(String localId, String baseDn, int type) {
248 Map<String, String> dns = getKnownBaseDns(true);
249 Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
250 String dn = null;
251 if (Role.GROUP == type)
252 dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
253 else if (Role.USER == type)
254 dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
255 else
256 throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
257 return dn;
258 }
259
260 @Override
261 public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
262 String name = CurrentUser.getUsername();
263 LdapName dn;
264 try {
265 dn = new LdapName(name);
266 } catch (InvalidNameException e) {
267 throw new IllegalArgumentException("Invalid user dn " + name, e);
268 }
269 User user = (User) userAdmin.getRole(dn.toString());
270 if (!user.hasCredential(null, oldPassword))
271 throw new IllegalArgumentException("Invalid password");
272 if (Arrays.equals(newPassword, new char[0]))
273 throw new IllegalArgumentException("New password empty");
274 try {
275 userTransaction.begin();
276 user.getCredentials().put(null, newPassword);
277 userTransaction.commit();
278 } catch (Exception e) {
279 try {
280 userTransaction.rollback();
281 } catch (Exception e1) {
282 log.error("Could not roll back", e1);
283 }
284 if (e instanceof RuntimeException)
285 throw (RuntimeException) e;
286 else
287 throw new RuntimeException("Cannot change password", e);
288 }
289 }
290
291 public void resetPassword(String username, char[] newPassword) {
292 LdapName dn;
293 try {
294 dn = new LdapName(username);
295 } catch (InvalidNameException e) {
296 throw new IllegalArgumentException("Invalid user dn " + username, e);
297 }
298 User user = (User) userAdmin.getRole(dn.toString());
299 if (Arrays.equals(newPassword, new char[0]))
300 throw new IllegalArgumentException("New password empty");
301 try {
302 userTransaction.begin();
303 user.getCredentials().put(null, newPassword);
304 userTransaction.commit();
305 } catch (Exception e) {
306 try {
307 userTransaction.rollback();
308 } catch (Exception e1) {
309 log.error("Could not roll back", e1);
310 }
311 if (e instanceof RuntimeException)
312 throw (RuntimeException) e;
313 else
314 throw new RuntimeException("Cannot change password", e);
315 }
316 }
317
318 public String addSharedSecret(String email, int hours) {
319 User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
320 try {
321 userTransaction.begin();
322 String uuid = UUID.randomUUID().toString();
323 SharedSecret sharedSecret = new SharedSecret(hours, uuid);
324 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
325 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
326 userTransaction.commit();
327 return tokenStr;
328 } catch (Exception e) {
329 try {
330 userTransaction.rollback();
331 } catch (Exception e1) {
332 log.error("Could not roll back", e1);
333 }
334 if (e instanceof RuntimeException)
335 throw (RuntimeException) e;
336 else
337 throw new RuntimeException("Cannot change password", e);
338 }
339 }
340
341 @Deprecated
342 public String addSharedSecret(String username, String authInfo, String authToken) {
343 try {
344 userTransaction.begin();
345 User user = (User) userAdmin.getRole(username);
346 SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
347 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
348 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
349 userTransaction.commit();
350 return tokenStr;
351 } catch (Exception e1) {
352 try {
353 if (!userTransaction.isNoTransactionStatus())
354 userTransaction.rollback();
355 } catch (Exception e2) {
356 if (log.isTraceEnabled())
357 log.trace("Cannot rollback transaction", e2);
358 }
359 throw new RuntimeException("Cannot add shared secret", e1);
360 }
361 }
362
363 @Override
364 public void expireAuthToken(String token) {
365 try {
366 userTransaction.begin();
367 String dn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
368 Group tokenGroup = (Group) userAdmin.getRole(dn);
369 String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
370 tokenGroup.getProperties().put(description.name(), ldapDate);
371 userTransaction.commit();
372 if (log.isDebugEnabled())
373 log.debug("Token " + token + " expired.");
374 } catch (Exception e1) {
375 try {
376 if (!userTransaction.isNoTransactionStatus())
377 userTransaction.rollback();
378 } catch (Exception e2) {
379 if (log.isTraceEnabled())
380 log.trace("Cannot rollback transaction", e2);
381 }
382 throw new RuntimeException("Cannot expire token", e1);
383 }
384 }
385
386 @Override
387 public void expireAuthTokens(Subject subject) {
388 Set<String> tokens = TokenUtils.tokensUsed(subject, NodeConstants.TOKENS_BASEDN);
389 for (String token : tokens)
390 expireAuthToken(token);
391 }
392
393 @Override
394 public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
395 addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
396 }
397
398 @Override
399 public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
400 try {
401 userTransaction.begin();
402 User user = (User) userAdmin.getRole(userDn);
403 String tokenDn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
404 Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
405 if (roles != null)
406 for (String role : roles) {
407 Role r = userAdmin.getRole(role);
408 if (r != null)
409 tokenGroup.addMember(r);
410 else {
411 if (!role.equals(NodeConstants.ROLE_USER)) {
412 throw new IllegalStateException(
413 "Cannot add role " + role + " to token " + token + " for " + userDn);
414 }
415 }
416 }
417 tokenGroup.getProperties().put(owner.name(), user.getName());
418 if (expiryDate != null) {
419 String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
420 tokenGroup.getProperties().put(description.name(), ldapDate);
421 }
422 userTransaction.commit();
423 } catch (Exception e1) {
424 try {
425 if (!userTransaction.isNoTransactionStatus())
426 userTransaction.rollback();
427 } catch (Exception e2) {
428 if (log.isTraceEnabled())
429 log.trace("Cannot rollback transaction", e2);
430 }
431 throw new RuntimeException("Cannot add token", e1);
432 }
433 }
434
435 // public User createUserFromPerson(Node person) {
436 // String email = JcrUtils.get(person, LdapAttrs.mail.property());
437 // String dn = buildDefaultDN(email, Role.USER);
438 // User user;
439 // try {
440 // userTransaction.begin();
441 // user = (User) userAdmin.createRole(dn, Role.USER);
442 // Dictionary<String, Object> userProperties = user.getProperties();
443 // String name = JcrUtils.get(person, LdapAttrs.displayName.property());
444 // userProperties.put(LdapAttrs.cn.name(), name);
445 // userProperties.put(LdapAttrs.displayName.name(), name);
446 // String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
447 // String surname = JcrUtils.get(person, LdapAttrs.sn.property());
448 // userProperties.put(LdapAttrs.givenName.name(), givenName);
449 // userProperties.put(LdapAttrs.sn.name(), surname);
450 // userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
451 // userTransaction.commit();
452 // } catch (Exception e) {
453 // try {
454 // userTransaction.rollback();
455 // } catch (Exception e1) {
456 // log.error("Could not roll back", e1);
457 // }
458 // if (e instanceof RuntimeException)
459 // throw (RuntimeException) e;
460 // else
461 // throw new RuntimeException("Cannot create user", e);
462 // }
463 // return user;
464 // }
465
466 public UserAdmin getUserAdmin() {
467 return userAdmin;
468 }
469
470 // public UserTransaction getUserTransaction() {
471 // return userTransaction;
472 // }
473
474 /* DEPENDENCY INJECTION */
475 public void setUserAdmin(UserAdmin userAdmin) {
476 this.userAdmin = userAdmin;
477 // this.serviceProperties = serviceProperties;
478 }
479
480 public void setUserTransaction(WorkTransaction userTransaction) {
481 this.userTransaction = userTransaction;
482 }
483
484 public void addUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
485 userDirectories.put(userDirectory, new Hashtable<>(properties));
486 }
487
488 public void removeUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
489 userDirectories.remove(userDirectory);
490 }
491
492 }