View Javadoc
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 }