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