1 package org.argeo.connect.core;
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.cms.auth.CurrentUser;
29 import org.argeo.cms.util.UserAdminUtils;
30 import org.argeo.connect.ConnectException;
31 import org.argeo.connect.UserAdminService;
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.node.NodeConstants;
37 import org.argeo.osgi.useradmin.TokenUtils;
38 import org.argeo.osgi.useradmin.UserAdminConf;
39 import org.argeo.people.PeopleNames;
40 import org.osgi.framework.InvalidSyntaxException;
41 import org.osgi.framework.ServiceReference;
42 import org.osgi.service.useradmin.Authorization;
43 import org.osgi.service.useradmin.Group;
44 import org.osgi.service.useradmin.Role;
45 import org.osgi.service.useradmin.User;
46 import org.osgi.service.useradmin.UserAdmin;
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public class UserAdminServiceImpl implements UserAdminService {
61 private final static Log log = LogFactory.getLog(UserAdminServiceImpl.class);
62
63 private UserAdmin userAdmin;
64 @Deprecated
65 private ServiceReference<UserAdmin> userAdminServiceReference;
66 private Map<String, String> serviceProperties;
67 private UserTransaction userTransaction;
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
80
81
82 public User getUser(String dn) {
83 return (User) getUserAdmin().getRole(dn);
84 }
85
86
87 public String getUserDisplayName(String dn) {
88
89
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
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 ConnectException("Group " + groupDn + " not found");
125 Set<User> users = new HashSet<User>();
126 addUsers(users, group, filter);
127 return users;
128 }
129
130
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
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(null);
149 } catch (InvalidSyntaxException e) {
150 throw new ConnectException("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 ConnectException("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 = userAdminServiceReference != null ? userAdminServiceReference.getPropertyKeys()
212 : serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
213 for (String uri : propertyKeys) {
214 if (!uri.startsWith("/"))
215 continue;
216 Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
217 String readOnly = UserAdminConf.readOnly.getValue(props);
218 String baseDn = UserAdminConf.baseDn.getValue(props);
219
220 if (onlyWritable && "true".equals(readOnly))
221 continue;
222 if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
223 continue;
224 if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
225 continue;
226 dns.put(baseDn, uri);
227 }
228 return dns;
229 }
230
231 public String buildDistinguishedName(String localId, String baseDn, int type) {
232 Map<String, String> dns = getKnownBaseDns(true);
233 Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
234 String dn = null;
235 if (Role.GROUP == type)
236 dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
237 else if (Role.USER == type)
238 dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
239 else
240 throw new ConnectException("Unknown role type. " + "Cannot deduce dn for " + localId);
241 return dn;
242 }
243
244 @Override
245 public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
246 String name = CurrentUser.getUsername();
247 LdapName dn;
248 try {
249 dn = new LdapName(name);
250 } catch (InvalidNameException e) {
251 throw new ConnectException("Invalid user dn " + name, e);
252 }
253 User user = (User) userAdmin.getRole(dn.toString());
254 if (!user.hasCredential(null, oldPassword))
255 throw new ConnectException("Invalid password");
256 if (Arrays.equals(newPassword, new char[0]))
257 throw new ConnectException("New password empty");
258 try {
259 userTransaction.begin();
260 user.getCredentials().put(null, newPassword);
261 userTransaction.commit();
262 } catch (Exception e) {
263 try {
264 userTransaction.rollback();
265 } catch (Exception e1) {
266 log.error("Could not roll back", e1);
267 }
268 if (e instanceof RuntimeException)
269 throw (RuntimeException) e;
270 else
271 throw new ConnectException("Cannot change password", e);
272 }
273 }
274
275 public void resetPassword(String username, char[] newPassword) {
276 LdapName dn;
277 try {
278 dn = new LdapName(username);
279 } catch (InvalidNameException e) {
280 throw new ConnectException("Invalid user dn " + username, e);
281 }
282 User user = (User) userAdmin.getRole(dn.toString());
283 if (Arrays.equals(newPassword, new char[0]))
284 throw new ConnectException("New password empty");
285 try {
286 userTransaction.begin();
287 user.getCredentials().put(null, newPassword);
288 userTransaction.commit();
289 } catch (Exception e) {
290 try {
291 userTransaction.rollback();
292 } catch (Exception e1) {
293 log.error("Could not roll back", e1);
294 }
295 if (e instanceof RuntimeException)
296 throw (RuntimeException) e;
297 else
298 throw new ConnectException("Cannot change password", e);
299 }
300 }
301
302 public String addSharedSecret(String email, int hours) {
303 User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
304 try {
305 userTransaction.begin();
306 String uuid = UUID.randomUUID().toString();
307 SharedSecret sharedSecret = new SharedSecret(hours, uuid);
308 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
309 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
310 userTransaction.commit();
311 return tokenStr;
312 } catch (Exception e) {
313 try {
314 userTransaction.rollback();
315 } catch (Exception e1) {
316 log.error("Could not roll back", e1);
317 }
318 if (e instanceof RuntimeException)
319 throw (RuntimeException) e;
320 else
321 throw new ConnectException("Cannot change password", e);
322 }
323 }
324
325 @Override
326 public String addSharedSecret(String username, String authInfo, String authToken) {
327 try {
328 userTransaction.begin();
329 User user = (User) userAdmin.getRole(username);
330 SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
331 user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
332 String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
333 userTransaction.commit();
334 return tokenStr;
335 } catch (Exception e1) {
336 try {
337 if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
338 userTransaction.rollback();
339 } catch (Exception e2) {
340 if (log.isTraceEnabled())
341 log.trace("Cannot rollback transaction", e2);
342 }
343 throw new ConnectException("Cannot add sheared secret", e1);
344 }
345 }
346
347 @Override
348 public void expireAuthToken(String token) {
349 try {
350 userTransaction.begin();
351 String dn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
352 Group tokenGroup = (Group) userAdmin.getRole(dn);
353 String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
354 tokenGroup.getProperties().put(description.name(), ldapDate);
355 userTransaction.commit();
356 if (log.isDebugEnabled())
357 log.debug("Token " + token + " expired.");
358 } catch (Exception e1) {
359 try {
360 if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
361 userTransaction.rollback();
362 } catch (Exception e2) {
363 if (log.isTraceEnabled())
364 log.trace("Cannot rollback transaction", e2);
365 }
366 throw new ConnectException("Cannot expire token", e1);
367 }
368 }
369
370 @Override
371 public void expireAuthTokens(Subject subject) {
372 Set<String> tokens = TokenUtils.tokensUsed(subject, NodeConstants.TOKENS_BASEDN);
373 for (String token : tokens)
374 expireAuthToken(token);
375 }
376
377 @Override
378 public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
379 try {
380 userTransaction.begin();
381 User user = (User) userAdmin.getRole(userDn);
382 String tokenDn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
383 Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
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 ConnectException("Cannot add role " + role + " to token " + token + " for " + userDn);
391 }
392 }
393 }
394 tokenGroup.getProperties().put(owner.name(), user.getName());
395 if (hours != null) {
396 String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now().plusHours(hours));
397 tokenGroup.getProperties().put(description.name(), ldapDate);
398 }
399 userTransaction.commit();
400 } catch (Exception e1) {
401 try {
402 if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
403 userTransaction.rollback();
404 } catch (Exception e2) {
405 if (log.isTraceEnabled())
406 log.trace("Cannot rollback transaction", e2);
407 }
408 throw new ConnectException("Cannot add token", e1);
409 }
410 }
411
412 public User createUserFromPerson(Node person) {
413 String email = JcrUtils.get(person, PeopleNames.PEOPLE_PRIMARY_EMAIL);
414 String dn = buildDefaultDN(email, Role.USER);
415 User user;
416 try {
417 userTransaction.begin();
418 user = (User) userAdmin.createRole(dn, Role.USER);
419 Dictionary<String, Object> userProperties = user.getProperties();
420 String name = JcrUtils.get(person, PeopleNames.PEOPLE_DISPLAY_NAME);
421 userProperties.put(LdapAttrs.cn.name(), name);
422 userProperties.put(LdapAttrs.displayName.name(), name);
423 String givenName = JcrUtils.get(person, PeopleNames.PEOPLE_FIRST_NAME);
424 String surname = JcrUtils.get(person, PeopleNames.PEOPLE_LAST_NAME);
425 userProperties.put(LdapAttrs.givenName.name(), givenName);
426 userProperties.put(LdapAttrs.sn.name(), surname);
427 userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
428 userTransaction.commit();
429 } catch (Exception e) {
430 try {
431 userTransaction.rollback();
432 } catch (Exception e1) {
433 log.error("Could not roll back", e1);
434 }
435 if (e instanceof RuntimeException)
436 throw (RuntimeException) e;
437 else
438 throw new ConnectException("Cannot create user", e);
439 }
440 return user;
441 }
442
443 public UserAdmin getUserAdmin() {
444 return userAdmin;
445 }
446
447 public UserTransaction getUserTransaction() {
448 return userTransaction;
449 }
450
451
452 public void setUserAdmin(UserAdmin userAdmin, Map<String, String> serviceProperties) {
453 this.userAdmin = userAdmin;
454 this.serviceProperties = serviceProperties;
455 }
456
457 @Deprecated
458 public void setUserAdminOld(UserAdmin userAdmin) {
459 this.userAdmin = userAdmin;
460 }
461
462 public void setUserTransaction(UserTransaction userTransaction) {
463 this.userTransaction = userTransaction;
464 }
465
466 @Deprecated
467 public void setUserAdminServiceReference(ServiceReference<UserAdmin> userAdminServiceReference) {
468 this.userAdminServiceReference = userAdminServiceReference;
469 }
470 }