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