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