]> git.argeo.org Git - lgpl/argeo-commons.git/blob - useradmin/AbstractUserDirectory.java
Prepare next development cycle
[lgpl/argeo-commons.git] / useradmin / AbstractUserDirectory.java
1 package org.argeo.osgi.useradmin;
2
3 import static org.argeo.naming.LdapAttrs.objectClass;
4 import static org.argeo.naming.LdapObjs.extensibleObject;
5 import static org.argeo.naming.LdapObjs.inetOrgPerson;
6 import static org.argeo.naming.LdapObjs.organizationalPerson;
7 import static org.argeo.naming.LdapObjs.person;
8 import static org.argeo.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 import javax.transaction.SystemException;
31 import javax.transaction.Transaction;
32 import javax.transaction.TransactionManager;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.argeo.naming.LdapAttrs;
37 import org.argeo.naming.LdapObjs;
38 import org.osgi.framework.Filter;
39 import org.osgi.framework.FrameworkUtil;
40 import org.osgi.framework.InvalidSyntaxException;
41 import org.osgi.service.useradmin.Authorization;
42 import org.osgi.service.useradmin.Role;
43 import org.osgi.service.useradmin.User;
44 import org.osgi.service.useradmin.UserAdmin;
45
46 /** Base class for a {@link UserDirectory}. */
47 public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
48 static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
49 static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
50
51 private final static Log log = LogFactory.getLog(AbstractUserDirectory.class);
52
53 private final Hashtable<String, Object> properties;
54 private final LdapName baseDn, userBaseDn, groupBaseDn;
55 private final String userObjectClass, userBase, groupObjectClass, groupBase;
56
57 private final boolean readOnly;
58 private final URI uri;
59
60 private UserAdmin externalRoles;
61 // private List<String> indexedUserProperties = Arrays
62 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
63 // LdapAttrs.cn.name() });
64
65 private String memberAttributeId = "member";
66 private List<String> credentialAttributeIds = Arrays
67 .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
68
69 // JTA
70 private TransactionManager transactionManager;
71 private WcXaResource xaResource = new WcXaResource(this);
72
73 public AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props) {
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;
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 try {
89 uri = new URI(uriStr);
90 } catch (URISyntaxException e) {
91 throw new UserDirectoryException("Badly formatted URI " + uriStr, e);
92 }
93 }
94
95 userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
96 userBase = UserAdminConf.userBase.getValue(properties);
97 groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
98 groupBase = UserAdminConf.groupBase.getValue(properties);
99 try {
100 baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
101 userBaseDn = new LdapName(userBase + "," + baseDn);
102 groupBaseDn = new LdapName(groupBase + "," + baseDn);
103 } catch (InvalidNameException e) {
104 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
105 }
106 String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
107 if (readOnlyStr == null) {
108 readOnly = readOnlyDefault(uri);
109 properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
110 } else
111 readOnly = new Boolean(readOnlyStr);
112 }
113
114 /** Returns the groups this user is a direct member of. */
115 protected abstract List<LdapName> getDirectGroups(LdapName dn);
116
117 protected abstract Boolean daoHasRole(LdapName dn);
118
119 protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
120
121 protected abstract List<DirectoryUser> doGetRoles(Filter f);
122
123 protected abstract AbstractUserDirectory scope(User user);
124
125 public void init() {
126
127 }
128
129 public void destroy() {
130
131 }
132
133 protected boolean isEditing() {
134 return xaResource.wc() != null;
135 }
136
137 protected UserDirectoryWorkingCopy getWorkingCopy() {
138 UserDirectoryWorkingCopy wc = xaResource.wc();
139 if (wc == null)
140 return null;
141 return wc;
142 }
143
144 protected void checkEdit() {
145 Transaction transaction;
146 try {
147 transaction = transactionManager.getTransaction();
148 } catch (SystemException e) {
149 throw new UserDirectoryException("Cannot get transaction", e);
150 }
151 if (transaction == null)
152 throw new UserDirectoryException("A transaction needs to be active in order to edit");
153 if (xaResource.wc() == null) {
154 try {
155 transaction.enlistResource(xaResource);
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 (memberOf != null) {
178 try {
179 NamingEnumeration<?> values = memberOf.getAll();
180 while (values.hasMore()) {
181 Object value = values.next();
182 LdapName groupDn = new LdapName(value.toString());
183 DirectoryUser group = doGetRole(groupDn);
184 allRoles.add(group);
185 if (log.isDebugEnabled())
186 log.debug("Add memberOf " + groupDn);
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 allRoles.add(group);
196 if (log.isDebugEnabled())
197 log.debug("Add direct group " + groupDn);
198 collectRoles(group, allRoles);
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 @SuppressWarnings("unchecked")
231 @Override
232 public Role[] getRoles(String filter) throws InvalidSyntaxException {
233 UserDirectoryWorkingCopy wc = getWorkingCopy();
234 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
235 List<DirectoryUser> res = doGetRoles(f);
236 if (wc != null) {
237 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
238 DirectoryUser user = it.next();
239 LdapName dn = user.getDn();
240 if (wc.getDeletedUsers().containsKey(dn))
241 it.remove();
242 }
243 for (DirectoryUser user : wc.getNewUsers().values()) {
244 if (f == null || f.match(user.getProperties()))
245 res.add(user);
246 }
247 // no need to check modified users,
248 // since doGetRoles was already based on the modified attributes
249 }
250 return res.toArray(new Role[res.size()]);
251 }
252
253 @Override
254 public User getUser(String key, String value) {
255 // TODO check value null or empty
256 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
257 if (key != null) {
258 doGetUser(key, value, collectedUsers);
259 } else {
260 throw new UserDirectoryException("Key cannot be null");
261 // // try dn
262 // DirectoryUser user = null;
263 // try {
264 // user = (DirectoryUser) getRole(value);
265 // if (user != null)
266 // collectedUsers.add(user);
267 // } catch (Exception e) {
268 // // silent
269 // }
270 // // try all indexes
271 // for (String attr : getIndexedUserProperties())
272 // doGetUser(attr, value, collectedUsers);
273 }
274 if (collectedUsers.size() == 1)
275 return collectedUsers.get(0);
276 else if (collectedUsers.size() > 1)
277 log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" : "") + value);
278 return null;
279 }
280
281 protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
282 try {
283 Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
284 List<DirectoryUser> users = doGetRoles(f);
285 collectedUsers.addAll(users);
286 } catch (InvalidSyntaxException e) {
287 throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
288 }
289 }
290
291 @Override
292 public Authorization getAuthorization(User user) {
293 if (user == null || user instanceof DirectoryUser) {
294 return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
295 } else {
296 // bind
297 AbstractUserDirectory scopedUserAdmin = scope(user);
298 try {
299 DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
300 LdifAuthorization authorization = new LdifAuthorization(directoryUser,
301 scopedUserAdmin.getAllRoles(directoryUser));
302 return authorization;
303 } finally {
304 scopedUserAdmin.destroy();
305 }
306 }
307 }
308
309 @Override
310 public Role createRole(String name, int type) {
311 checkEdit();
312 UserDirectoryWorkingCopy wc = getWorkingCopy();
313 LdapName dn = toDn(name);
314 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
315 throw new UserDirectoryException("Already a role " + name);
316 BasicAttributes attrs = new BasicAttributes(true);
317 // attrs.put(LdifName.dn.name(), dn.toString());
318 Rdn nameRdn = dn.getRdn(dn.size() - 1);
319 // TODO deal with multiple attr RDN
320 attrs.put(nameRdn.getType(), nameRdn.getValue());
321 if (wc.getDeletedUsers().containsKey(dn)) {
322 wc.getDeletedUsers().remove(dn);
323 wc.getModifiedUsers().put(dn, attrs);
324 return getRole(name);
325 } else {
326 wc.getModifiedUsers().put(dn, attrs);
327 DirectoryUser newRole = newRole(dn, type, attrs);
328 wc.getNewUsers().put(dn, newRole);
329 return newRole;
330 }
331 }
332
333 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
334 LdifUser newRole;
335 BasicAttribute objClass = new BasicAttribute(objectClass.name());
336 if (type == Role.USER) {
337 String userObjClass = newUserObjectClass(dn);
338 objClass.add(userObjClass);
339 if (inetOrgPerson.name().equals(userObjClass)) {
340 objClass.add(organizationalPerson.name());
341 objClass.add(person.name());
342 } else if (organizationalPerson.name().equals(userObjClass)) {
343 objClass.add(person.name());
344 }
345 objClass.add(top.name());
346 objClass.add(extensibleObject.name());
347 attrs.put(objClass);
348 newRole = new LdifUser(this, dn, attrs);
349 } else if (type == Role.GROUP) {
350 String groupObjClass = getGroupObjectClass();
351 objClass.add(groupObjClass);
352 // objClass.add(LdifName.extensibleObject.name());
353 objClass.add(top.name());
354 attrs.put(objClass);
355 newRole = new LdifGroup(this, dn, attrs);
356 } else
357 throw new UserDirectoryException("Unsupported type " + type);
358 return newRole;
359 }
360
361 @Override
362 public boolean removeRole(String name) {
363 checkEdit();
364 UserDirectoryWorkingCopy wc = getWorkingCopy();
365 LdapName dn = toDn(name);
366 boolean actuallyDeleted;
367 if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
368 DirectoryUser user = (DirectoryUser) getRole(name);
369 wc.getDeletedUsers().put(dn, user);
370 actuallyDeleted = true;
371 } else {// just removing from groups (e.g. system roles)
372 actuallyDeleted = false;
373 }
374 for (LdapName groupDn : getDirectGroups(dn)) {
375 DirectoryUser group = doGetRole(groupDn);
376 group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
377 }
378 return actuallyDeleted;
379 }
380
381 // TRANSACTION
382 protected void prepare(UserDirectoryWorkingCopy wc) {
383
384 }
385
386 protected void commit(UserDirectoryWorkingCopy wc) {
387
388 }
389
390 protected void rollback(UserDirectoryWorkingCopy wc) {
391
392 }
393
394 // UTILITIES
395 protected LdapName toDn(String name) {
396 try {
397 return new LdapName(name);
398 } catch (InvalidNameException e) {
399 throw new UserDirectoryException("Badly formatted name", e);
400 }
401 }
402
403 // GETTERS
404 protected String getMemberAttributeId() {
405 return memberAttributeId;
406 }
407
408 protected List<String> getCredentialAttributeIds() {
409 return credentialAttributeIds;
410 }
411
412 protected URI getUri() {
413 return uri;
414 }
415
416 // protected List<String> getIndexedUserProperties() {
417 // return indexedUserProperties;
418 // }
419 //
420 // protected void setIndexedUserProperties(List<String>
421 // indexedUserProperties) {
422 // this.indexedUserProperties = indexedUserProperties;
423 // }
424
425 private static boolean readOnlyDefault(URI uri) {
426 if (uri == null)
427 return true;
428 if (uri.getScheme() == null)
429 return false;// assume relative file to be writable
430 if (uri.getScheme().equals("file")) {
431 File file = new File(uri);
432 if (file.exists())
433 return !file.canWrite();
434 else
435 return !file.getParentFile().canWrite();
436 }
437 return true;
438 }
439
440 public boolean isReadOnly() {
441 return readOnly;
442 }
443
444 protected UserAdmin getExternalRoles() {
445 return externalRoles;
446 }
447
448 protected int roleType(LdapName dn) {
449 if (dn.startsWith(groupBaseDn))
450 return Role.GROUP;
451 else if (dn.startsWith(userBaseDn))
452 return Role.USER;
453 else
454 return Role.GROUP;
455 }
456
457 /** dn can be null, in that case a default should be returned. */
458 public String getUserObjectClass() {
459 return userObjectClass;
460 }
461
462 public String getUserBase() {
463 return userBase;
464 }
465
466 protected String newUserObjectClass(LdapName dn) {
467 return getUserObjectClass();
468 }
469
470 public String getGroupObjectClass() {
471 return groupObjectClass;
472 }
473
474 public String getGroupBase() {
475 return groupBase;
476 }
477
478 public LdapName getBaseDn() {
479 return (LdapName) baseDn.clone();
480 }
481
482 public Dictionary<String, Object> getProperties() {
483 return properties;
484 }
485
486 public Dictionary<String, Object> cloneProperties() {
487 return new Hashtable<>(properties);
488 }
489
490 public void setExternalRoles(UserAdmin externalRoles) {
491 this.externalRoles = externalRoles;
492 }
493
494 public void setTransactionManager(TransactionManager transactionManager) {
495 this.transactionManager = transactionManager;
496 }
497
498 public WcXaResource getXaResource() {
499 return xaResource;
500 }
501
502 }