1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.osgi
.useradmin
.LdifName
.inetOrgPerson
;
4 import static org
.argeo
.osgi
.useradmin
.LdifName
.objectClass
;
5 import static org
.argeo
.osgi
.useradmin
.LdifName
.organizationalPerson
;
6 import static org
.argeo
.osgi
.useradmin
.LdifName
.person
;
7 import static org
.argeo
.osgi
.useradmin
.LdifName
.top
;
11 import java
.net
.URISyntaxException
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Arrays
;
14 import java
.util
.Dictionary
;
15 import java
.util
.HashMap
;
16 import java
.util
.Iterator
;
17 import java
.util
.List
;
20 import javax
.naming
.InvalidNameException
;
21 import javax
.naming
.directory
.Attributes
;
22 import javax
.naming
.directory
.BasicAttribute
;
23 import javax
.naming
.directory
.BasicAttributes
;
24 import javax
.naming
.ldap
.LdapName
;
25 import javax
.naming
.ldap
.Rdn
;
26 import javax
.transaction
.SystemException
;
27 import javax
.transaction
.Transaction
;
28 import javax
.transaction
.TransactionManager
;
29 import javax
.transaction
.xa
.XAException
;
30 import javax
.transaction
.xa
.XAResource
;
31 import javax
.transaction
.xa
.Xid
;
33 import org
.apache
.commons
.logging
.Log
;
34 import org
.apache
.commons
.logging
.LogFactory
;
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
.Group
;
40 import org
.osgi
.service
.useradmin
.Role
;
41 import org
.osgi
.service
.useradmin
.User
;
42 import org
.osgi
.service
.useradmin
.UserAdmin
;
44 abstract class AbstractUserDirectory
implements UserAdmin
, UserDirectory
{
45 private final static Log log
= LogFactory
46 .getLog(AbstractUserDirectory
.class);
48 private Dictionary
<String
, ?
> properties
;
49 private String baseDn
= "dc=example,dc=com";
50 private String userObjectClass
;
51 private String groupObjectClass
;
53 private boolean isReadOnly
;
56 private UserAdmin externalRoles
;
57 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] {
58 LdifName
.uid
.name(), LdifName
.mail
.name(), LdifName
.cn
.name() });
60 private String memberAttributeId
= "member";
61 private List
<String
> credentialAttributeIds
= Arrays
62 .asList(new String
[] { LdifName
.userpassword
.name() });
64 // private TransactionSynchronizationRegistry syncRegistry;
65 // private Object editingTransactionKey = null;
67 private TransactionManager transactionManager
;
68 private ThreadLocal
<WorkingCopy
> workingCopy
= new ThreadLocal
<AbstractUserDirectory
.WorkingCopy
>();
69 private Xid editingTransactionXid
= null;
71 public AbstractUserDirectory(Dictionary
<String
, ?
> properties
) {
73 this.properties
= properties
;
75 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
80 uri
= new URI(uriStr
);
81 } catch (URISyntaxException e
) {
82 throw new UserDirectoryException("Badly formatted URI", e
);
85 baseDn
= UserAdminConf
.baseDn
.getValue(properties
).toString();
86 String isReadOnly
= UserAdminConf
.readOnly
.getValue(properties
);
87 if (isReadOnly
== null)
88 this.isReadOnly
= readOnlyDefault(uri
);
90 this.isReadOnly
= new Boolean(isReadOnly
);
92 this.userObjectClass
= UserAdminConf
.userObjectClass
93 .getValue(properties
);
94 this.groupObjectClass
= UserAdminConf
.groupObjectClass
95 .getValue(properties
);
98 // public AbstractUserDirectory(URI uri, boolean isReadOnly) {
100 // this.isReadOnly = isReadOnly;
103 /** Returns the {@link Group}s this user is a direct member of. */
104 protected abstract List
<?
extends DirectoryGroup
> getDirectGroups(User user
);
106 protected abstract Boolean
daoHasRole(LdapName dn
);
108 protected abstract DirectoryUser
daoGetRole(LdapName key
);
110 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
112 protected abstract void doGetUser(String key
, String value
,
113 List
<DirectoryUser
> collectedUsers
);
119 public void destroy() {
123 boolean isEditing() {
124 if (editingTransactionXid
== null)
126 return workingCopy
.get() != null;
127 // Object currentTrKey = syncRegistry.getTransactionKey();
128 // if (currentTrKey == null)
130 // return editingTransactionKey.equals(currentTrKey);
133 protected WorkingCopy
getWorkingCopy() {
134 WorkingCopy wc
= workingCopy
.get();
137 if (wc
.xid
== null) {
138 workingCopy
.set(null);
145 Transaction transaction
;
147 transaction
= transactionManager
.getTransaction();
148 } catch (SystemException e
) {
149 throw new UserDirectoryException("Cannot get transaction", e
);
151 if (transaction
== null)
152 throw new UserDirectoryException(
153 "A transaction needs to be active in order to edit");
154 if (editingTransactionXid
== null) {
155 WorkingCopy wc
= new WorkingCopy();
157 transaction
.enlistResource(wc
);
158 editingTransactionXid
= wc
.getXid();
160 } catch (Exception e
) {
161 throw new UserDirectoryException("Cannot enlist " + wc
, e
);
164 if (workingCopy
.get() == null)
165 throw new UserDirectoryException("Transaction "
166 + editingTransactionXid
+ " already editing");
167 else if (!editingTransactionXid
.equals(workingCopy
.get().getXid()))
168 throw new UserDirectoryException("Working copy Xid "
169 + workingCopy
.get().getXid() + " inconsistent with"
170 + editingTransactionXid
);
174 List
<Role
> getAllRoles(User user
) {
175 List
<Role
> allRoles
= new ArrayList
<Role
>();
177 collectRoles(user
, allRoles
);
180 collectAnonymousRoles(allRoles
);
184 private void collectRoles(User user
, List
<Role
> allRoles
) {
185 for (Group group
: getDirectGroups(user
)) {
186 // TODO check for loops
188 collectRoles(group
, allRoles
);
192 private void collectAnonymousRoles(List
<Role
> allRoles
) {
193 // TODO gather anonymous roles
198 public Role
getRole(String name
) {
199 LdapName key
= toDn(name
);
200 WorkingCopy wc
= getWorkingCopy();
201 DirectoryUser user
= daoGetRole(key
);
203 if (user
== null && wc
.getNewUsers().containsKey(key
))
204 user
= wc
.getNewUsers().get(key
);
205 else if (wc
.getDeletedUsers().containsKey(key
))
211 @SuppressWarnings("unchecked")
213 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
214 WorkingCopy wc
= getWorkingCopy();
215 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
216 List
<DirectoryUser
> res
= doGetRoles(f
);
218 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
219 DirectoryUser user
= it
.next();
220 LdapName dn
= user
.getDn();
221 if (wc
.getDeletedUsers().containsKey(dn
))
224 for (DirectoryUser user
: wc
.getNewUsers().values()) {
225 if (f
== null || f
.match(user
.getProperties()))
228 // no need to check modified users,
229 // since doGetRoles was already based on the modified attributes
231 return res
.toArray(new Role
[res
.size()]);
235 public User
getUser(String key
, String value
) {
236 // TODO check value null or empty
237 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>(
238 getIndexedUserProperties().size());
240 doGetUser(key
, value
, collectedUsers
);
243 DirectoryUser user
= null;
245 user
= (DirectoryUser
) getRole(value
);
247 collectedUsers
.add(user
);
248 } catch (Exception e
) {
252 for (String attr
: getIndexedUserProperties())
253 doGetUser(attr
, value
, collectedUsers
);
255 if (collectedUsers
.size() == 1)
256 return collectedUsers
.get(0);
261 public Authorization
getAuthorization(User user
) {
262 return new LdifAuthorization((DirectoryUser
) user
,
263 getAllRoles((DirectoryUser
) user
));
267 public Role
createRole(String name
, int type
) {
269 WorkingCopy wc
= getWorkingCopy();
270 LdapName dn
= toDn(name
);
271 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
))
272 || wc
.getNewUsers().containsKey(dn
))
273 throw new UserDirectoryException("Already a role " + name
);
274 BasicAttributes attrs
= new BasicAttributes();
275 attrs
.put("dn", dn
.toString());
276 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
277 // TODO deal with multiple attr RDN
278 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
279 if (wc
.getDeletedUsers().containsKey(dn
)) {
280 wc
.getDeletedUsers().remove(dn
);
281 wc
.getModifiedUsers().put(dn
, attrs
);
283 wc
.getModifiedUsers().put(dn
, attrs
);
284 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
285 wc
.getNewUsers().put(dn
, newRole
);
287 return getRole(name
);
290 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
292 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
293 if (type
== Role
.USER
) {
294 String userObjClass
= getUserObjectClass();
295 objClass
.add(userObjClass
);
296 if (inetOrgPerson
.name().equals(userObjClass
)) {
297 objClass
.add(organizationalPerson
.name());
298 objClass
.add(person
.name());
299 } else if (organizationalPerson
.name().equals(userObjClass
)) {
300 objClass
.add(person
.name());
304 newRole
= new LdifUser(this, dn
, attrs
);
305 } else if (type
== Role
.GROUP
) {
306 objClass
.add(getGroupObjectClass());
309 newRole
= new LdifGroup(this, dn
, attrs
);
311 throw new UserDirectoryException("Unsupported type " + type
);
316 public boolean removeRole(String name
) {
318 WorkingCopy wc
= getWorkingCopy();
319 LdapName dn
= toDn(name
);
320 if (!daoHasRole(dn
) && !wc
.getNewUsers().containsKey(dn
))
322 DirectoryUser user
= (DirectoryUser
) getRole(name
);
323 wc
.getDeletedUsers().put(dn
, user
);
324 // FIXME clarify directgroups
325 for (DirectoryGroup group
: getDirectGroups(user
)) {
326 group
.getAttributes().get(getMemberAttributeId())
327 .remove(dn
.toString());
333 protected void prepare(WorkingCopy wc
) {
337 protected void commit(WorkingCopy wc
) {
341 protected void rollback(WorkingCopy wc
) {
346 protected LdapName
toDn(String name
) {
348 return new LdapName(name
);
349 } catch (InvalidNameException e
) {
350 throw new UserDirectoryException("Badly formatted name", e
);
356 String
getMemberAttributeId() {
357 return memberAttributeId
;
360 List
<String
> getCredentialAttributeIds() {
361 return credentialAttributeIds
;
364 protected URI
getUri() {
368 protected void setUri(URI uri
) {
372 protected List
<String
> getIndexedUserProperties() {
373 return indexedUserProperties
;
376 protected void setIndexedUserProperties(List
<String
> indexedUserProperties
) {
377 this.indexedUserProperties
= indexedUserProperties
;
380 protected void setReadOnly(boolean isReadOnly
) {
381 this.isReadOnly
= isReadOnly
;
384 private static boolean readOnlyDefault(URI uri
) {
387 if (uri
.getScheme().equals("file")) {
388 File file
= new File(uri
);
389 return !file
.canWrite();
394 public boolean isReadOnly() {
398 UserAdmin
getExternalRoles() {
399 return externalRoles
;
402 public String
getBaseDn() {
406 protected String
getUserObjectClass() {
407 return userObjectClass
;
410 protected String
getGroupObjectClass() {
411 return groupObjectClass
;
414 public Dictionary
<String
, ?
> getProperties() {
418 public void setExternalRoles(UserAdmin externalRoles
) {
419 this.externalRoles
= externalRoles
;
422 public void setTransactionManager(TransactionManager transactionManager
) {
423 this.transactionManager
= transactionManager
;
429 protected class WorkingCopy
implements XAResource
{
431 private int transactionTimeout
= 0;
433 private Map
<LdapName
, DirectoryUser
> newUsers
= new HashMap
<LdapName
, DirectoryUser
>();
434 private Map
<LdapName
, Attributes
> modifiedUsers
= new HashMap
<LdapName
, Attributes
>();
435 private Map
<LdapName
, DirectoryUser
> deletedUsers
= new HashMap
<LdapName
, DirectoryUser
>();
438 public void start(Xid xid
, int flags
) throws XAException
{
439 if (editingTransactionXid
!= null)
440 throw new UserDirectoryException("Transaction "
441 + editingTransactionXid
+ " already editing");
446 public void end(Xid xid
, int flags
) throws XAException
{
452 modifiedUsers
.clear();
453 modifiedUsers
= null;
454 deletedUsers
.clear();
459 editingTransactionXid
= null;
463 public int prepare(Xid xid
) throws XAException
{
465 if (noModifications())
468 AbstractUserDirectory
.this.prepare(this);
469 } catch (Exception e
) {
470 log
.error("Cannot prepare " + xid
, e
);
471 throw new XAException(XAException
.XA_RBOTHER
);
477 public void commit(Xid xid
, boolean onePhase
) throws XAException
{
479 if (noModifications())
483 AbstractUserDirectory
.this.prepare(this);
484 AbstractUserDirectory
.this.commit(this);
485 } catch (Exception e
) {
486 log
.error("Cannot commit " + xid
, e
);
487 throw new XAException(XAException
.XA_RBOTHER
);
492 public void rollback(Xid xid
) throws XAException
{
495 AbstractUserDirectory
.this.rollback(this);
496 } catch (Exception e
) {
497 log
.error("Cannot rollback " + xid
, e
);
498 throw new XAException(XAException
.XA_HEURMIX
);
503 public void forget(Xid xid
) throws XAException
{
504 throw new UnsupportedOperationException();
508 public boolean isSameRM(XAResource xares
) throws XAException
{
509 return xares
== this;
513 public Xid
[] recover(int flag
) throws XAException
{
514 throw new UnsupportedOperationException();
518 public int getTransactionTimeout() throws XAException
{
519 return transactionTimeout
;
523 public boolean setTransactionTimeout(int seconds
) throws XAException
{
524 transactionTimeout
= seconds
;
528 private Xid
getXid() {
532 private void checkXid(Xid xid
) throws XAException
{
533 if (this.xid
== null)
534 throw new XAException(XAException
.XAER_OUTSIDE
);
535 if (!this.xid
.equals(xid
))
536 throw new XAException(XAException
.XAER_NOTA
);
540 protected void finalize() throws Throwable
{
541 if (editingTransactionXid
!= null)
542 log
.warn("Editing transaction still referenced but no working copy "
543 + editingTransactionXid
);
544 editingTransactionXid
= null;
547 public boolean noModifications() {
548 return newUsers
.size() == 0 && modifiedUsers
.size() == 0
549 && deletedUsers
.size() == 0;
552 public Attributes
getAttributes(LdapName dn
) {
553 if (modifiedUsers
.containsKey(dn
))
554 return modifiedUsers
.get(dn
);
558 public void startEditing(DirectoryUser user
) {
559 LdapName dn
= user
.getDn();
560 if (modifiedUsers
.containsKey(dn
))
561 throw new UserDirectoryException("Already editing " + dn
);
562 modifiedUsers
.put(dn
, (Attributes
) user
.getAttributes().clone());
565 public Map
<LdapName
, DirectoryUser
> getNewUsers() {
569 public Map
<LdapName
, DirectoryUser
> getDeletedUsers() {
573 public Map
<LdapName
, Attributes
> getModifiedUsers() {
574 return modifiedUsers
;