]> git.argeo.org Git - lgpl/argeo-commons.git/blob - AbstractUserDirectory.java
e028e384e1a8a7af1b5656124e0a66e6f50e9c00
[lgpl/argeo-commons.git] / AbstractUserDirectory.java
1 package org.argeo.osgi.useradmin;
2
3 import static org.argeo.util.naming.LdapAttrs.objectClass;
4 import static org.argeo.util.naming.LdapObjs.extensibleObject;
5 import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
6 import static org.argeo.util.naming.LdapObjs.organizationalPerson;
7 import static org.argeo.util.naming.LdapObjs.person;
8 import static org.argeo.util.naming.LdapObjs.top;
9
10 import java.io.File;
11 import java.net.URI;
12 import java.net.URISyntaxException;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Dictionary;
16 import java.util.Enumeration;
17 import java.util.Hashtable;
18 import java.util.Iterator;
19 import java.util.List;
20
21 import javax.naming.InvalidNameException;
22 import javax.naming.NameNotFoundException;
23 import javax.naming.NamingEnumeration;
24 import javax.naming.directory.Attribute;
25 import javax.naming.directory.Attributes;
26 import javax.naming.directory.BasicAttribute;
27 import javax.naming.directory.BasicAttributes;
28 import javax.naming.ldap.LdapName;
29 import javax.naming.ldap.Rdn;
30
31 import org.argeo.osgi.transaction.WorkControl;
32 import org.argeo.util.naming.LdapAttrs;
33 import org.osgi.framework.Filter;
34 import org.osgi.framework.FrameworkUtil;
35 import org.osgi.framework.InvalidSyntaxException;
36 import org.osgi.service.useradmin.Authorization;
37 import org.osgi.service.useradmin.Role;
38 import org.osgi.service.useradmin.User;
39 import org.osgi.service.useradmin.UserAdmin;
40
41 /** Base class for a {@link UserDirectory}. */
42 public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
43 static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
44 static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
45
46 private final Hashtable<String, Object> properties;
47 private final LdapName baseDn, userBaseDn, groupBaseDn;
48 private final String userObjectClass, userBase, groupObjectClass, groupBase;
49
50 private final boolean readOnly;
51 private final boolean disabled;
52 private final String uri;
53
54 private UserAdmin externalRoles;
55 // private List<String> indexedUserProperties = Arrays
56 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
57 // LdapAttrs.cn.name() });
58
59 private final boolean scoped;
60
61 private String memberAttributeId = "member";
62 private List<String> credentialAttributeIds = Arrays
63 .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
64
65 // Transaction
66 // private TransactionManager transactionManager;
67 private WorkControl transactionControl;
68 private WcXaResource xaResource = new WcXaResource(this);
69
70 private String forcedPassword;
71
72 AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
73 this.scoped = scoped;
74 properties = new Hashtable<String, Object>();
75 for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
76 String key = keys.nextElement();
77 properties.put(key, props.get(key));
78 }
79
80 if (uriArg != null) {
81 uri = uriArg.toString();
82 // uri from properties is ignored
83 } else {
84 String uriStr = UserAdminConf.uri.getValue(properties);
85 if (uriStr == null)
86 uri = null;
87 else
88 uri = uriStr;
89 }
90
91 forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
92
93 userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
94 userBase = UserAdminConf.userBase.getValue(properties);
95 groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
96 groupBase = UserAdminConf.groupBase.getValue(properties);
97 try {
98 baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
99 userBaseDn = new LdapName(userBase + "," + baseDn);
100 groupBaseDn = new LdapName(groupBase + "," + baseDn);
101 } catch (InvalidNameException e) {
102 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
103 }
104 String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
105 if (readOnlyStr == null) {
106 readOnly = readOnlyDefault(uri);
107 properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
108 } else
109 readOnly = Boolean.parseBoolean(readOnlyStr);
110 String disabledStr = UserAdminConf.disabled.getValue(properties);
111 if (disabledStr != null)
112 disabled = Boolean.parseBoolean(disabledStr);
113 else
114 disabled = false;
115 }
116
117 /** Returns the groups this user is a direct member of. */
118 protected abstract List<LdapName> getDirectGroups(LdapName dn);
119
120 protected abstract Boolean daoHasRole(LdapName dn);
121
122 protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
123
124 protected abstract List<DirectoryUser> doGetRoles(Filter f);
125
126 protected abstract AbstractUserDirectory scope(User user);
127
128 public void init() {
129
130 }
131
132 public void destroy() {
133
134 }
135
136 protected boolean isEditing() {
137 return xaResource.wc() != null;
138 }
139
140 protected UserDirectoryWorkingCopy getWorkingCopy() {
141 UserDirectoryWorkingCopy wc = xaResource.wc();
142 if (wc == null)
143 return null;
144 return wc;
145 }
146
147 protected void checkEdit() {
148 // Transaction transaction;
149 // try {
150 // transaction = transactionManager.getTransaction();
151 // } catch (SystemException e) {
152 // throw new UserDirectoryException("Cannot get transaction", e);
153 // }
154 // if (transaction == null)
155 // throw new UserDirectoryException("A transaction needs to be active in order to edit");
156 if (xaResource.wc() == null) {
157 try {
158 // transaction.enlistResource(xaResource);
159 transactionControl.getWorkContext().registerXAResource(xaResource, null);
160 } catch (Exception e) {
161 throw new UserDirectoryException("Cannot enlist " + xaResource, e);
162 }
163 } else {
164 }
165 }
166
167 protected List<Role> getAllRoles(DirectoryUser user) {
168 List<Role> allRoles = new ArrayList<Role>();
169 if (user != null) {
170 collectRoles(user, allRoles);
171 allRoles.add(user);
172 } else
173 collectAnonymousRoles(allRoles);
174 return allRoles;
175 }
176
177 private void collectRoles(DirectoryUser user, List<Role> allRoles) {
178 Attributes attrs = user.getAttributes();
179 // TODO centralize attribute name
180 Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
181 // if user belongs to this directory, we only check meberOf
182 if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
183 try {
184 NamingEnumeration<?> values = memberOf.getAll();
185 while (values.hasMore()) {
186 Object value = values.next();
187 LdapName groupDn = new LdapName(value.toString());
188 DirectoryUser group = doGetRole(groupDn);
189 if (group != null)
190 allRoles.add(group);
191 }
192 } catch (Exception e) {
193 throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
194 }
195 } else {
196 for (LdapName groupDn : getDirectGroups(user.getDn())) {
197 // TODO check for loops
198 DirectoryUser group = doGetRole(groupDn);
199 if (group != null) {
200 allRoles.add(group);
201 collectRoles(group, allRoles);
202 }
203 }
204 }
205 }
206
207 private void collectAnonymousRoles(List<Role> allRoles) {
208 // TODO gather anonymous roles
209 }
210
211 // USER ADMIN
212 @Override
213 public Role getRole(String name) {
214 return doGetRole(toDn(name));
215 }
216
217 protected DirectoryUser doGetRole(LdapName dn) {
218 UserDirectoryWorkingCopy wc = getWorkingCopy();
219 DirectoryUser user;
220 try {
221 user = daoGetRole(dn);
222 } catch (NameNotFoundException e) {
223 user = null;
224 }
225 if (wc != null) {
226 if (user == null && wc.getNewUsers().containsKey(dn))
227 user = wc.getNewUsers().get(dn);
228 else if (wc.getDeletedUsers().containsKey(dn))
229 user = null;
230 }
231 return user;
232 }
233
234 @Override
235 public Role[] getRoles(String filter) throws InvalidSyntaxException {
236 UserDirectoryWorkingCopy wc = getWorkingCopy();
237 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
238 List<DirectoryUser> res = doGetRoles(f);
239 if (wc != null) {
240 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
241 DirectoryUser user = it.next();
242 LdapName dn = user.getDn();
243 if (wc.getDeletedUsers().containsKey(dn))
244 it.remove();
245 }
246 for (DirectoryUser user : wc.getNewUsers().values()) {
247 if (f == null || f.match(user.getProperties()))
248 res.add(user);
249 }
250 // no need to check modified users,
251 // since doGetRoles was already based on the modified attributes
252 }
253 return res.toArray(new Role[res.size()]);
254 }
255
256 @Override
257 public User getUser(String key, String value) {
258 // TODO check value null or empty
259 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
260 if (key != null) {
261 doGetUser(key, value, collectedUsers);
262 } else {
263 throw new UserDirectoryException("Key cannot be null");
264 }
265
266 if (collectedUsers.size() == 1) {
267 return collectedUsers.get(0);
268 } else if (collectedUsers.size() > 1) {
269 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
270 // "") + value);
271 }
272 return null;
273 }
274
275 protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
276 try {
277 Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
278 List<DirectoryUser> users = doGetRoles(f);
279 collectedUsers.addAll(users);
280 } catch (InvalidSyntaxException e) {
281 throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
282 }
283 }
284
285 @Override
286 public Authorization getAuthorization(User user) {
287 if (user == null || user instanceof DirectoryUser) {
288 return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
289 } else {
290 // bind
291 AbstractUserDirectory scopedUserAdmin = scope(user);
292 try {
293 DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
294 if (directoryUser == null)
295 throw new UserDirectoryException("No scoped user found for " + user);
296 LdifAuthorization authorization = new LdifAuthorization(directoryUser,
297 scopedUserAdmin.getAllRoles(directoryUser));
298 return authorization;
299 } finally {
300 scopedUserAdmin.destroy();
301 }
302 }
303 }
304
305 @Override
306 public Role createRole(String name, int type) {
307 checkEdit();
308 UserDirectoryWorkingCopy wc = getWorkingCopy();
309 LdapName dn = toDn(name);
310 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
311 throw new UserDirectoryException("Already a role " + name);
312 BasicAttributes attrs = new BasicAttributes(true);
313 // attrs.put(LdifName.dn.name(), dn.toString());
314 Rdn nameRdn = dn.getRdn(dn.size() - 1);
315 // TODO deal with multiple attr RDN
316 attrs.put(nameRdn.getType(), nameRdn.getValue());
317 if (wc.getDeletedUsers().containsKey(dn)) {
318 wc.getDeletedUsers().remove(dn);
319 wc.getModifiedUsers().put(dn, attrs);
320 return getRole(name);
321 } else {
322 wc.getModifiedUsers().put(dn, attrs);
323 DirectoryUser newRole = newRole(dn, type, attrs);
324 wc.getNewUsers().put(dn, newRole);
325 return newRole;
326 }
327 }
328
329 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
330 LdifUser newRole;
331 BasicAttribute objClass = new BasicAttribute(objectClass.name());
332 if (type == Role.USER) {
333 String userObjClass = newUserObjectClass(dn);
334 objClass.add(userObjClass);
335 if (inetOrgPerson.name().equals(userObjClass)) {
336 objClass.add(organizationalPerson.name());
337 objClass.add(person.name());
338 } else if (organizationalPerson.name().equals(userObjClass)) {
339 objClass.add(person.name());
340 }
341 objClass.add(top.name());
342 objClass.add(extensibleObject.name());
343 attrs.put(objClass);
344 newRole = new LdifUser(this, dn, attrs);
345 } else if (type == Role.GROUP) {
346 String groupObjClass = getGroupObjectClass();
347 objClass.add(groupObjClass);
348 // objClass.add(LdifName.extensibleObject.name());
349 objClass.add(top.name());
350 attrs.put(objClass);
351 newRole = new LdifGroup(this, dn, attrs);
352 } else
353 throw new UserDirectoryException("Unsupported type " + type);
354 return newRole;
355 }
356
357 @Override
358 public boolean removeRole(String name) {
359 checkEdit();
360 UserDirectoryWorkingCopy wc = getWorkingCopy();
361 LdapName dn = toDn(name);
362 boolean actuallyDeleted;
363 if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
364 DirectoryUser user = (DirectoryUser) getRole(name);
365 wc.getDeletedUsers().put(dn, user);
366 actuallyDeleted = true;
367 } else {// just removing from groups (e.g. system roles)
368 actuallyDeleted = false;
369 }
370 for (LdapName groupDn : getDirectGroups(dn)) {
371 DirectoryUser group = doGetRole(groupDn);
372 group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
373 }
374 return actuallyDeleted;
375 }
376
377 // TRANSACTION
378 protected void prepare(UserDirectoryWorkingCopy wc) {
379
380 }
381
382 protected void commit(UserDirectoryWorkingCopy wc) {
383
384 }
385
386 protected void rollback(UserDirectoryWorkingCopy wc) {
387
388 }
389
390 // UTILITIES
391 protected LdapName toDn(String name) {
392 try {
393 return new LdapName(name);
394 } catch (InvalidNameException e) {
395 throw new UserDirectoryException("Badly formatted name", e);
396 }
397 }
398
399 // GETTERS
400 protected String getMemberAttributeId() {
401 return memberAttributeId;
402 }
403
404 protected List<String> getCredentialAttributeIds() {
405 return credentialAttributeIds;
406 }
407
408 protected String getUri() {
409 return uri;
410 }
411
412 private static boolean readOnlyDefault(String uriStr) {
413 if (uriStr == null)
414 return true;
415 /// TODO make it more generic
416 URI uri;
417 try {
418 uri = new URI(uriStr.split(" ")[0]);
419 } catch (URISyntaxException e) {
420 throw new IllegalArgumentException(e);
421 }
422 if (uri.getScheme() == null)
423 return false;// assume relative file to be writable
424 if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
425 File file = new File(uri);
426 if (file.exists())
427 return !file.canWrite();
428 else
429 return !file.getParentFile().canWrite();
430 } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
431 if (uri.getAuthority() != null)// assume writable if authenticated
432 return false;
433 } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
434 return true;
435 }
436 return true;// read only by default
437 }
438
439 public boolean isReadOnly() {
440 return readOnly;
441 }
442
443 public boolean isDisabled() {
444 return disabled;
445 }
446
447 protected UserAdmin getExternalRoles() {
448 return externalRoles;
449 }
450
451 protected int roleType(LdapName dn) {
452 if (dn.startsWith(groupBaseDn))
453 return Role.GROUP;
454 else if (dn.startsWith(userBaseDn))
455 return Role.USER;
456 else
457 return Role.GROUP;
458 }
459
460 /** dn can be null, in that case a default should be returned. */
461 public String getUserObjectClass() {
462 return userObjectClass;
463 }
464
465 public String getUserBase() {
466 return userBase;
467 }
468
469 protected String newUserObjectClass(LdapName dn) {
470 return getUserObjectClass();
471 }
472
473 public String getGroupObjectClass() {
474 return groupObjectClass;
475 }
476
477 public String getGroupBase() {
478 return groupBase;
479 }
480
481 public LdapName getBaseDn() {
482 return (LdapName) baseDn.clone();
483 }
484
485 public Dictionary<String, Object> getProperties() {
486 return properties;
487 }
488
489 public Dictionary<String, Object> cloneProperties() {
490 return new Hashtable<>(properties);
491 }
492
493 public void setExternalRoles(UserAdmin externalRoles) {
494 this.externalRoles = externalRoles;
495 }
496
497 // public void setTransactionManager(TransactionManager transactionManager) {
498 // this.transactionManager = transactionManager;
499 // }
500
501 public String getForcedPassword() {
502 return forcedPassword;
503 }
504
505 public void setTransactionControl(WorkControl transactionControl) {
506 this.transactionControl = transactionControl;
507 }
508
509 public WcXaResource getXaResource() {
510 return xaResource;
511 }
512
513 public boolean isScoped() {
514 return scoped;
515 }
516
517 }