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