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