1 package org
.argeo
.osgi
.useradmin
;
4 import java
.util
.ArrayList
;
5 import java
.util
.Arrays
;
6 import java
.util
.HashMap
;
7 import java
.util
.Iterator
;
11 import javax
.naming
.InvalidNameException
;
12 import javax
.naming
.directory
.Attributes
;
13 import javax
.naming
.directory
.BasicAttributes
;
14 import javax
.naming
.ldap
.LdapName
;
15 import javax
.naming
.ldap
.Rdn
;
16 import javax
.transaction
.SystemException
;
17 import javax
.transaction
.Transaction
;
18 import javax
.transaction
.TransactionManager
;
19 import javax
.transaction
.TransactionSynchronizationRegistry
;
20 import javax
.transaction
.xa
.XAException
;
21 import javax
.transaction
.xa
.XAResource
;
22 import javax
.transaction
.xa
.Xid
;
24 import org
.apache
.commons
.logging
.Log
;
25 import org
.apache
.commons
.logging
.LogFactory
;
26 import org
.osgi
.framework
.Filter
;
27 import org
.osgi
.framework
.FrameworkUtil
;
28 import org
.osgi
.framework
.InvalidSyntaxException
;
29 import org
.osgi
.service
.useradmin
.Authorization
;
30 import org
.osgi
.service
.useradmin
.Group
;
31 import org
.osgi
.service
.useradmin
.Role
;
32 import org
.osgi
.service
.useradmin
.User
;
33 import org
.osgi
.service
.useradmin
.UserAdmin
;
35 public abstract class AbstractUserDirectory
implements UserAdmin
{
36 private final static Log log
= LogFactory
37 .getLog(AbstractUserDirectory
.class);
38 private boolean isReadOnly
;
41 private UserAdmin externalRoles
;
42 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] {
43 "uid", "mail", "cn" });
45 private String memberAttributeId
= "member";
46 private List
<String
> credentialAttributeIds
= Arrays
47 .asList(new String
[] { "userpassword" });
49 // private TransactionSynchronizationRegistry syncRegistry;
50 // private Object editingTransactionKey = null;
52 private TransactionManager transactionManager
;
53 private ThreadLocal
<WorkingCopy
> workingCopy
= new ThreadLocal
<AbstractUserDirectory
.WorkingCopy
>();
54 private Xid editingTransactionXid
= null;
56 public AbstractUserDirectory() {
59 public AbstractUserDirectory(URI uri
, boolean isReadOnly
) {
61 this.isReadOnly
= isReadOnly
;
64 /** Returns the {@link Group}s this user is a direct member of. */
65 protected abstract List
<?
extends DirectoryGroup
> getDirectGroups(User user
);
67 protected abstract Boolean
daoHasRole(LdapName dn
);
69 protected abstract DirectoryUser
daoGetRole(LdapName key
);
71 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
73 protected abstract void doGetUser(String key
, String value
,
74 List
<DirectoryUser
> collectedUsers
);
80 public void destroy() {
85 if (editingTransactionXid
== null)
87 return workingCopy
.get() != null;
88 // Object currentTrKey = syncRegistry.getTransactionKey();
89 // if (currentTrKey == null)
91 // return editingTransactionKey.equals(currentTrKey);
94 protected WorkingCopy
getWorkingCopy() {
95 WorkingCopy wc
= workingCopy
.get();
99 workingCopy
.set(null);
106 Transaction transaction
;
108 transaction
= transactionManager
.getTransaction();
109 } catch (SystemException e
) {
110 throw new UserDirectoryException("Cannot get transaction", e
);
112 if (transaction
== null)
113 throw new UserDirectoryException(
114 "A transaction needs to be active in order to edit");
115 if (editingTransactionXid
== null) {
116 WorkingCopy wc
= new WorkingCopy();
118 transaction
.enlistResource(wc
);
119 editingTransactionXid
= wc
.getXid();
121 } catch (Exception e
) {
122 throw new UserDirectoryException("Cannot enlist " + wc
, e
);
125 if (workingCopy
.get() == null)
126 throw new UserDirectoryException("Transaction "
127 + editingTransactionXid
+ " already editing");
128 else if (!editingTransactionXid
.equals(workingCopy
.get().getXid()))
129 throw new UserDirectoryException("Working copy Xid "
130 + workingCopy
.get().getXid() + " inconsistent with"
131 + editingTransactionXid
);
135 List
<Role
> getAllRoles(User user
) {
136 List
<Role
> allRoles
= new ArrayList
<Role
>();
138 collectRoles(user
, allRoles
);
141 collectAnonymousRoles(allRoles
);
145 private void collectRoles(User user
, List
<Role
> allRoles
) {
146 for (Group group
: getDirectGroups(user
)) {
147 // TODO check for loops
149 collectRoles(group
, allRoles
);
153 private void collectAnonymousRoles(List
<Role
> allRoles
) {
154 // TODO gather anonymous roles
159 public Role
getRole(String name
) {
160 LdapName key
= toDn(name
);
161 WorkingCopy wc
= getWorkingCopy();
162 DirectoryUser user
= daoGetRole(key
);
164 if (user
== null && wc
.getNewUsers().containsKey(key
))
165 user
= wc
.getNewUsers().get(key
);
166 else if (wc
.getDeletedUsers().containsKey(key
))
173 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
174 WorkingCopy wc
= getWorkingCopy();
175 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
176 List
<DirectoryUser
> res
= doGetRoles(f
);
178 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
179 DirectoryUser user
= it
.next();
180 LdapName dn
= user
.getDn();
181 if (wc
.getDeletedUsers().containsKey(dn
))
184 for (DirectoryUser user
: wc
.getNewUsers().values()) {
185 if (f
== null || f
.match(user
.getProperties()))
188 // no need to check modified users,
189 // since doGetRoles was already based on the modified attributes
191 return res
.toArray(new Role
[res
.size()]);
195 public User
getUser(String key
, String value
) {
196 // TODO check value null or empty
197 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>(
198 getIndexedUserProperties().size());
200 doGetUser(key
, value
, collectedUsers
);
203 DirectoryUser user
= null;
205 user
= (DirectoryUser
) getRole(value
);
207 collectedUsers
.add(user
);
208 } catch (Exception e
) {
212 for (String attr
: getIndexedUserProperties())
213 doGetUser(attr
, value
, collectedUsers
);
215 if (collectedUsers
.size() == 1)
216 return collectedUsers
.get(0);
221 public Authorization
getAuthorization(User user
) {
222 return new LdifAuthorization((DirectoryUser
) user
,
223 getAllRoles((DirectoryUser
) user
));
227 public Role
createRole(String name
, int type
) {
229 WorkingCopy wc
= getWorkingCopy();
230 LdapName dn
= toDn(name
);
231 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
))
232 || wc
.getNewUsers().containsKey(dn
))
233 throw new UserDirectoryException("Already a role " + name
);
234 BasicAttributes attrs
= new BasicAttributes();
235 attrs
.put("dn", dn
.toString());
236 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
237 // TODO deal with multiple attr RDN
238 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
239 if (wc
.getDeletedUsers().containsKey(dn
)) {
240 wc
.getDeletedUsers().remove(dn
);
241 wc
.getModifiedUsers().put(dn
, attrs
);
243 wc
.getModifiedUsers().put(dn
, attrs
);
244 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
245 wc
.getNewUsers().put(dn
, newRole
);
247 return getRole(name
);
250 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
252 if (type
== Role
.USER
) {
253 newRole
= new LdifUser(this, dn
, attrs
);
254 // users.put(dn, newRole);
255 } else if (type
== Role
.GROUP
) {
256 newRole
= new LdifGroup(this, dn
, attrs
);
257 // groups.put(dn, (LdifGroup) newRole);
259 throw new UserDirectoryException("Unsupported type " + type
);
264 public boolean removeRole(String name
) {
266 WorkingCopy wc
= getWorkingCopy();
267 LdapName dn
= toDn(name
);
268 if (!daoHasRole(dn
) && !wc
.getNewUsers().containsKey(dn
))
270 DirectoryUser user
= (DirectoryUser
) getRole(name
);
271 wc
.getDeletedUsers().put(dn
, user
);
272 // FIXME clarify directgroups
273 for (DirectoryGroup group
: getDirectGroups(user
)) {
274 group
.getAttributes().get(getMemberAttributeId())
275 .remove(dn
.toString());
281 protected void prepare(WorkingCopy wc
) {
285 protected void commit(WorkingCopy wc
) {
289 protected void rollback(WorkingCopy wc
) {
294 protected LdapName
toDn(String name
) {
296 return new LdapName(name
);
297 } catch (InvalidNameException e
) {
298 throw new UserDirectoryException("Badly formatted name", e
);
304 String
getMemberAttributeId() {
305 return memberAttributeId
;
308 List
<String
> getCredentialAttributeIds() {
309 return credentialAttributeIds
;
312 protected URI
getUri() {
316 protected void setUri(URI uri
) {
320 protected List
<String
> getIndexedUserProperties() {
321 return indexedUserProperties
;
324 protected void setIndexedUserProperties(List
<String
> indexedUserProperties
) {
325 this.indexedUserProperties
= indexedUserProperties
;
328 protected void setReadOnly(boolean isReadOnly
) {
329 this.isReadOnly
= isReadOnly
;
332 public boolean isReadOnly() {
336 UserAdmin
getExternalRoles() {
337 return externalRoles
;
340 public void setExternalRoles(UserAdmin externalRoles
) {
341 this.externalRoles
= externalRoles
;
344 public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry
) {
345 // this.syncRegistry = syncRegistry;
348 public void setTransactionManager(TransactionManager transactionManager
) {
349 this.transactionManager
= transactionManager
;
355 protected class WorkingCopy
implements XAResource
{
357 private int transactionTimeout
= 0;
359 private Map
<LdapName
, DirectoryUser
> newUsers
= new HashMap
<LdapName
, DirectoryUser
>();
360 private Map
<LdapName
, Attributes
> modifiedUsers
= new HashMap
<LdapName
, Attributes
>();
361 private Map
<LdapName
, DirectoryUser
> deletedUsers
= new HashMap
<LdapName
, DirectoryUser
>();
364 public void start(Xid xid
, int flags
) throws XAException
{
365 if (editingTransactionXid
!= null)
366 throw new UserDirectoryException("Transaction "
367 + editingTransactionXid
+ " already editing");
372 public void end(Xid xid
, int flags
) throws XAException
{
378 modifiedUsers
.clear();
379 modifiedUsers
= null;
380 deletedUsers
.clear();
385 editingTransactionXid
= null;
389 public int prepare(Xid xid
) throws XAException
{
391 if (noModifications())
394 AbstractUserDirectory
.this.prepare(this);
395 } catch (Exception e
) {
396 log
.error("Cannot prepare " + xid
, e
);
397 throw new XAException(XAException
.XA_RBOTHER
);
403 public void commit(Xid xid
, boolean onePhase
) throws XAException
{
405 if (noModifications())
409 AbstractUserDirectory
.this.prepare(this);
410 AbstractUserDirectory
.this.commit(this);
411 } catch (Exception e
) {
412 log
.error("Cannot commit " + xid
, e
);
413 throw new XAException(XAException
.XA_RBOTHER
);
418 public void rollback(Xid xid
) throws XAException
{
421 AbstractUserDirectory
.this.rollback(this);
422 } catch (Exception e
) {
423 log
.error("Cannot rollback " + xid
, e
);
424 throw new XAException(XAException
.XA_HEURMIX
);
429 public void forget(Xid xid
) throws XAException
{
430 throw new UnsupportedOperationException();
434 public boolean isSameRM(XAResource xares
) throws XAException
{
435 return xares
== this;
439 public Xid
[] recover(int flag
) throws XAException
{
440 throw new UnsupportedOperationException();
444 public int getTransactionTimeout() throws XAException
{
445 return transactionTimeout
;
449 public boolean setTransactionTimeout(int seconds
) throws XAException
{
450 transactionTimeout
= seconds
;
454 private Xid
getXid() {
458 private void checkXid(Xid xid
) throws XAException
{
459 if (this.xid
== null)
460 throw new XAException(XAException
.XAER_OUTSIDE
);
461 if (!this.xid
.equals(xid
))
462 throw new XAException(XAException
.XAER_NOTA
);
466 protected void finalize() throws Throwable
{
467 if (editingTransactionXid
!= null)
468 log
.warn("Editing transaction still referenced but no working copy "
469 + editingTransactionXid
);
470 editingTransactionXid
= null;
473 public boolean noModifications() {
474 return newUsers
.size() == 0 && modifiedUsers
.size() == 0
475 && deletedUsers
.size() == 0;
478 public Attributes
getAttributes(LdapName dn
) {
479 if (modifiedUsers
.containsKey(dn
))
480 return modifiedUsers
.get(dn
);
484 public void startEditing(DirectoryUser user
) {
485 LdapName dn
= user
.getDn();
486 if (modifiedUsers
.containsKey(dn
))
487 throw new UserDirectoryException("Already editing " + dn
);
488 modifiedUsers
.put(dn
, (Attributes
) user
.getAttributes().clone());
491 public Map
<LdapName
, DirectoryUser
> getNewUsers() {
495 public Map
<LdapName
, DirectoryUser
> getDeletedUsers() {
499 public Map
<LdapName
, Attributes
> getModifiedUsers() {
500 return modifiedUsers
;