]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
d034e2233e90edfd4a55159dd39645042570f8ae
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / AbstractUserDirectory.java
1 package org.argeo.osgi.useradmin;
2
3 import java.net.URI;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Map;
10
11 import javax.naming.InvalidNameException;
12 import javax.naming.directory.Attributes;
13 import javax.naming.directory.BasicAttribute;
14 import javax.naming.directory.BasicAttributes;
15 import javax.naming.ldap.LdapName;
16 import javax.naming.ldap.Rdn;
17 import javax.transaction.SystemException;
18 import javax.transaction.Transaction;
19 import javax.transaction.TransactionManager;
20 import javax.transaction.TransactionSynchronizationRegistry;
21 import javax.transaction.xa.XAException;
22 import javax.transaction.xa.XAResource;
23 import javax.transaction.xa.Xid;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.osgi.framework.Filter;
28 import org.osgi.framework.FrameworkUtil;
29 import org.osgi.framework.InvalidSyntaxException;
30 import org.osgi.service.useradmin.Authorization;
31 import org.osgi.service.useradmin.Group;
32 import org.osgi.service.useradmin.Role;
33 import org.osgi.service.useradmin.User;
34 import org.osgi.service.useradmin.UserAdmin;
35
36 public abstract class AbstractUserDirectory implements UserAdmin {
37 private final static Log log = LogFactory
38 .getLog(AbstractUserDirectory.class);
39 private boolean isReadOnly;
40 private URI uri;
41
42 private UserAdmin externalRoles;
43 private List<String> indexedUserProperties = Arrays.asList(new String[] {
44 "uid", "mail", "cn" });
45
46 private String memberAttributeId = "member";
47 private List<String> credentialAttributeIds = Arrays
48 .asList(new String[] { "userpassword" });
49
50 // private TransactionSynchronizationRegistry syncRegistry;
51 // private Object editingTransactionKey = null;
52
53 private TransactionManager transactionManager;
54 private ThreadLocal<WorkingCopy> workingCopy = new ThreadLocal<AbstractUserDirectory.WorkingCopy>();
55 private Xid editingTransactionXid = null;
56
57 public AbstractUserDirectory() {
58 }
59
60 public AbstractUserDirectory(URI uri, boolean isReadOnly) {
61 this.uri = uri;
62 this.isReadOnly = isReadOnly;
63 }
64
65 /** Returns the {@link Group}s this user is a direct member of. */
66 protected abstract List<? extends DirectoryGroup> getDirectGroups(User user);
67
68 protected abstract Boolean daoHasRole(LdapName dn);
69
70 protected abstract DirectoryUser daoGetRole(LdapName key);
71
72 protected abstract List<DirectoryUser> doGetRoles(Filter f);
73
74 protected abstract void doGetUser(String key, String value,
75 List<DirectoryUser> collectedUsers);
76
77 public void init() {
78
79 }
80
81 public void destroy() {
82
83 }
84
85 boolean isEditing() {
86 if (editingTransactionXid == null)
87 return false;
88 return workingCopy.get() != null;
89 // Object currentTrKey = syncRegistry.getTransactionKey();
90 // if (currentTrKey == null)
91 // return false;
92 // return editingTransactionKey.equals(currentTrKey);
93 }
94
95 protected WorkingCopy getWorkingCopy() {
96 WorkingCopy wc = workingCopy.get();
97 if (wc == null)
98 return null;
99 if (wc.xid == null) {
100 workingCopy.set(null);
101 return null;
102 }
103 return wc;
104 }
105
106 void checkEdit() {
107 Transaction transaction;
108 try {
109 transaction = transactionManager.getTransaction();
110 } catch (SystemException e) {
111 throw new UserDirectoryException("Cannot get transaction", e);
112 }
113 if (transaction == null)
114 throw new UserDirectoryException(
115 "A transaction needs to be active in order to edit");
116 if (editingTransactionXid == null) {
117 WorkingCopy wc = new WorkingCopy();
118 try {
119 transaction.enlistResource(wc);
120 editingTransactionXid = wc.getXid();
121 workingCopy.set(wc);
122 } catch (Exception e) {
123 throw new UserDirectoryException("Cannot enlist " + wc, e);
124 }
125 } else {
126 if (workingCopy.get() == null)
127 throw new UserDirectoryException("Transaction "
128 + editingTransactionXid + " already editing");
129 else if (!editingTransactionXid.equals(workingCopy.get().getXid()))
130 throw new UserDirectoryException("Working copy Xid "
131 + workingCopy.get().getXid() + " inconsistent with"
132 + editingTransactionXid);
133 }
134 }
135
136 List<Role> getAllRoles(User user) {
137 List<Role> allRoles = new ArrayList<Role>();
138 if (user != null) {
139 collectRoles(user, allRoles);
140 allRoles.add(user);
141 } else
142 collectAnonymousRoles(allRoles);
143 return allRoles;
144 }
145
146 private void collectRoles(User user, List<Role> allRoles) {
147 for (Group group : getDirectGroups(user)) {
148 // TODO check for loops
149 allRoles.add(group);
150 collectRoles(group, allRoles);
151 }
152 }
153
154 private void collectAnonymousRoles(List<Role> allRoles) {
155 // TODO gather anonymous roles
156 }
157
158 // USER ADMIN
159 @Override
160 public Role getRole(String name) {
161 LdapName key = toDn(name);
162 WorkingCopy wc = getWorkingCopy();
163 DirectoryUser user = daoGetRole(key);
164 if (wc != null) {
165 if (user == null && wc.getNewUsers().containsKey(key))
166 user = wc.getNewUsers().get(key);
167 else if (wc.getDeletedUsers().containsKey(key))
168 user = null;
169 }
170 return user;
171 }
172
173 @Override
174 public Role[] getRoles(String filter) throws InvalidSyntaxException {
175 WorkingCopy wc = getWorkingCopy();
176 Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
177 List<DirectoryUser> res = doGetRoles(f);
178 if (wc != null) {
179 for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
180 DirectoryUser user = it.next();
181 LdapName dn = user.getDn();
182 if (wc.getDeletedUsers().containsKey(dn))
183 it.remove();
184 }
185 for (DirectoryUser user : wc.getNewUsers().values()) {
186 if (f == null || f.match(user.getProperties()))
187 res.add(user);
188 }
189 // no need to check modified users,
190 // since doGetRoles was already based on the modified attributes
191 }
192 return res.toArray(new Role[res.size()]);
193 }
194
195 @Override
196 public User getUser(String key, String value) {
197 // TODO check value null or empty
198 List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>(
199 getIndexedUserProperties().size());
200 if (key != null) {
201 doGetUser(key, value, collectedUsers);
202 } else {
203 // try dn
204 DirectoryUser user = null;
205 try {
206 user = (DirectoryUser) getRole(value);
207 if (user != null)
208 collectedUsers.add(user);
209 } catch (Exception e) {
210 // silent
211 }
212 // try all indexes
213 for (String attr : getIndexedUserProperties())
214 doGetUser(attr, value, collectedUsers);
215 }
216 if (collectedUsers.size() == 1)
217 return collectedUsers.get(0);
218 return null;
219 }
220
221 @Override
222 public Authorization getAuthorization(User user) {
223 return new LdifAuthorization((DirectoryUser) user,
224 getAllRoles((DirectoryUser) user));
225 }
226
227 @Override
228 public Role createRole(String name, int type) {
229 checkEdit();
230 WorkingCopy wc = getWorkingCopy();
231 LdapName dn = toDn(name);
232 if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn))
233 || wc.getNewUsers().containsKey(dn))
234 throw new UserDirectoryException("Already a role " + name);
235 BasicAttributes attrs = new BasicAttributes();
236 attrs.put("dn", dn.toString());
237 Rdn nameRdn = dn.getRdn(dn.size() - 1);
238 // TODO deal with multiple attr RDN
239 attrs.put(nameRdn.getType(), nameRdn.getValue());
240 if (wc.getDeletedUsers().containsKey(dn)) {
241 wc.getDeletedUsers().remove(dn);
242 wc.getModifiedUsers().put(dn, attrs);
243 } else {
244 wc.getModifiedUsers().put(dn, attrs);
245 DirectoryUser newRole = newRole(dn, type, attrs);
246 wc.getNewUsers().put(dn, newRole);
247 }
248 return getRole(name);
249 }
250
251 protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
252 LdifUser newRole;
253 BasicAttribute objectClass = new BasicAttribute("objectClass");
254 if (type == Role.USER) {
255 objectClass.add("inetOrgPerson");
256 objectClass.add("organizationalPerson");
257 objectClass.add("person");
258 objectClass.add("top");
259 attrs.put(objectClass);
260 newRole = new LdifUser(this, dn, attrs);
261 } else if (type == Role.GROUP) {
262 objectClass.add("groupOfNames");
263 objectClass.add("top");
264 attrs.put(objectClass);
265 newRole = new LdifGroup(this, dn, attrs);
266 } else
267 throw new UserDirectoryException("Unsupported type " + type);
268 return newRole;
269 }
270
271 @Override
272 public boolean removeRole(String name) {
273 checkEdit();
274 WorkingCopy wc = getWorkingCopy();
275 LdapName dn = toDn(name);
276 if (!daoHasRole(dn) && !wc.getNewUsers().containsKey(dn))
277 return false;
278 DirectoryUser user = (DirectoryUser) getRole(name);
279 wc.getDeletedUsers().put(dn, user);
280 // FIXME clarify directgroups
281 for (DirectoryGroup group : getDirectGroups(user)) {
282 group.getAttributes().get(getMemberAttributeId())
283 .remove(dn.toString());
284 }
285 return true;
286 }
287
288 // TRANSACTION
289 protected void prepare(WorkingCopy wc) {
290
291 }
292
293 protected void commit(WorkingCopy wc) {
294
295 }
296
297 protected void rollback(WorkingCopy wc) {
298
299 }
300
301 // UTILITIES
302 protected LdapName toDn(String name) {
303 try {
304 return new LdapName(name);
305 } catch (InvalidNameException e) {
306 throw new UserDirectoryException("Badly formatted name", e);
307 }
308 }
309
310 // GETTERS
311
312 String getMemberAttributeId() {
313 return memberAttributeId;
314 }
315
316 List<String> getCredentialAttributeIds() {
317 return credentialAttributeIds;
318 }
319
320 protected URI getUri() {
321 return uri;
322 }
323
324 protected void setUri(URI uri) {
325 this.uri = uri;
326 }
327
328 protected List<String> getIndexedUserProperties() {
329 return indexedUserProperties;
330 }
331
332 protected void setIndexedUserProperties(List<String> indexedUserProperties) {
333 this.indexedUserProperties = indexedUserProperties;
334 }
335
336 protected void setReadOnly(boolean isReadOnly) {
337 this.isReadOnly = isReadOnly;
338 }
339
340 public boolean isReadOnly() {
341 return isReadOnly;
342 }
343
344 UserAdmin getExternalRoles() {
345 return externalRoles;
346 }
347
348 public void setExternalRoles(UserAdmin externalRoles) {
349 this.externalRoles = externalRoles;
350 }
351
352 public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry) {
353 // this.syncRegistry = syncRegistry;
354 }
355
356 public void setTransactionManager(TransactionManager transactionManager) {
357 this.transactionManager = transactionManager;
358 }
359
360 //
361 // XA RESOURCE
362 //
363 protected class WorkingCopy implements XAResource {
364 private Xid xid;
365 private int transactionTimeout = 0;
366
367 private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
368 private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
369 private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
370
371 @Override
372 public void start(Xid xid, int flags) throws XAException {
373 if (editingTransactionXid != null)
374 throw new UserDirectoryException("Transaction "
375 + editingTransactionXid + " already editing");
376 this.xid = xid;
377 }
378
379 @Override
380 public void end(Xid xid, int flags) throws XAException {
381 checkXid(xid);
382
383 // clean collections
384 newUsers.clear();
385 newUsers = null;
386 modifiedUsers.clear();
387 modifiedUsers = null;
388 deletedUsers.clear();
389 deletedUsers = null;
390
391 // clean IDs
392 this.xid = null;
393 editingTransactionXid = null;
394 }
395
396 @Override
397 public int prepare(Xid xid) throws XAException {
398 checkXid(xid);
399 if (noModifications())
400 return XA_RDONLY;
401 try {
402 AbstractUserDirectory.this.prepare(this);
403 } catch (Exception e) {
404 log.error("Cannot prepare " + xid, e);
405 throw new XAException(XAException.XA_RBOTHER);
406 }
407 return XA_OK;
408 }
409
410 @Override
411 public void commit(Xid xid, boolean onePhase) throws XAException {
412 checkXid(xid);
413 if (noModifications())
414 return;
415 try {
416 if (onePhase)
417 AbstractUserDirectory.this.prepare(this);
418 AbstractUserDirectory.this.commit(this);
419 } catch (Exception e) {
420 log.error("Cannot commit " + xid, e);
421 throw new XAException(XAException.XA_RBOTHER);
422 }
423 }
424
425 @Override
426 public void rollback(Xid xid) throws XAException {
427 checkXid(xid);
428 try {
429 AbstractUserDirectory.this.rollback(this);
430 } catch (Exception e) {
431 log.error("Cannot rollback " + xid, e);
432 throw new XAException(XAException.XA_HEURMIX);
433 }
434 }
435
436 @Override
437 public void forget(Xid xid) throws XAException {
438 throw new UnsupportedOperationException();
439 }
440
441 @Override
442 public boolean isSameRM(XAResource xares) throws XAException {
443 return xares == this;
444 }
445
446 @Override
447 public Xid[] recover(int flag) throws XAException {
448 throw new UnsupportedOperationException();
449 }
450
451 @Override
452 public int getTransactionTimeout() throws XAException {
453 return transactionTimeout;
454 }
455
456 @Override
457 public boolean setTransactionTimeout(int seconds) throws XAException {
458 transactionTimeout = seconds;
459 return true;
460 }
461
462 private Xid getXid() {
463 return xid;
464 }
465
466 private void checkXid(Xid xid) throws XAException {
467 if (this.xid == null)
468 throw new XAException(XAException.XAER_OUTSIDE);
469 if (!this.xid.equals(xid))
470 throw new XAException(XAException.XAER_NOTA);
471 }
472
473 @Override
474 protected void finalize() throws Throwable {
475 if (editingTransactionXid != null)
476 log.warn("Editing transaction still referenced but no working copy "
477 + editingTransactionXid);
478 editingTransactionXid = null;
479 }
480
481 public boolean noModifications() {
482 return newUsers.size() == 0 && modifiedUsers.size() == 0
483 && deletedUsers.size() == 0;
484 }
485
486 public Attributes getAttributes(LdapName dn) {
487 if (modifiedUsers.containsKey(dn))
488 return modifiedUsers.get(dn);
489 return null;
490 }
491
492 public void startEditing(DirectoryUser user) {
493 LdapName dn = user.getDn();
494 if (modifiedUsers.containsKey(dn))
495 throw new UserDirectoryException("Already editing " + dn);
496 modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
497 }
498
499 public Map<LdapName, DirectoryUser> getNewUsers() {
500 return newUsers;
501 }
502
503 public Map<LdapName, DirectoryUser> getDeletedUsers() {
504 return deletedUsers;
505 }
506
507 public Map<LdapName, Attributes> getModifiedUsers() {
508 return modifiedUsers;
509 }
510
511 }
512 }