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