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