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