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
.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
;
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
;
36 public abstract class AbstractUserDirectory
implements UserAdmin
{
37 private final static Log log
= LogFactory
38 .getLog(AbstractUserDirectory
.class);
39 private boolean isReadOnly
;
42 private UserAdmin externalRoles
;
43 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] {
44 "uid", "mail", "cn" });
46 private String memberAttributeId
= "member";
47 private List
<String
> credentialAttributeIds
= Arrays
48 .asList(new String
[] { "userpassword" });
50 // private TransactionSynchronizationRegistry syncRegistry;
51 // private Object editingTransactionKey = null;
53 private TransactionManager transactionManager
;
54 private ThreadLocal
<WorkingCopy
> workingCopy
= new ThreadLocal
<AbstractUserDirectory
.WorkingCopy
>();
55 private Xid editingTransactionXid
= null;
57 public AbstractUserDirectory() {
60 public AbstractUserDirectory(URI uri
, boolean isReadOnly
) {
62 this.isReadOnly
= isReadOnly
;
65 /** Returns the {@link Group}s this user is a direct member of. */
66 protected abstract List
<?
extends DirectoryGroup
> getDirectGroups(User user
);
68 protected abstract Boolean
daoHasRole(LdapName dn
);
70 protected abstract DirectoryUser
daoGetRole(LdapName key
);
72 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
74 protected abstract void doGetUser(String key
, String value
,
75 List
<DirectoryUser
> collectedUsers
);
81 public void destroy() {
86 if (editingTransactionXid
== null)
88 return workingCopy
.get() != null;
89 // Object currentTrKey = syncRegistry.getTransactionKey();
90 // if (currentTrKey == null)
92 // return editingTransactionKey.equals(currentTrKey);
95 protected WorkingCopy
getWorkingCopy() {
96 WorkingCopy wc
= workingCopy
.get();
100 workingCopy
.set(null);
107 Transaction transaction
;
109 transaction
= transactionManager
.getTransaction();
110 } catch (SystemException e
) {
111 throw new UserDirectoryException("Cannot get transaction", e
);
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();
119 transaction
.enlistResource(wc
);
120 editingTransactionXid
= wc
.getXid();
122 } catch (Exception e
) {
123 throw new UserDirectoryException("Cannot enlist " + wc
, e
);
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
);
136 List
<Role
> getAllRoles(User user
) {
137 List
<Role
> allRoles
= new ArrayList
<Role
>();
139 collectRoles(user
, allRoles
);
142 collectAnonymousRoles(allRoles
);
146 private void collectRoles(User user
, List
<Role
> allRoles
) {
147 for (Group group
: getDirectGroups(user
)) {
148 // TODO check for loops
150 collectRoles(group
, allRoles
);
154 private void collectAnonymousRoles(List
<Role
> allRoles
) {
155 // TODO gather anonymous roles
160 public Role
getRole(String name
) {
161 LdapName key
= toDn(name
);
162 WorkingCopy wc
= getWorkingCopy();
163 DirectoryUser user
= daoGetRole(key
);
165 if (user
== null && wc
.getNewUsers().containsKey(key
))
166 user
= wc
.getNewUsers().get(key
);
167 else if (wc
.getDeletedUsers().containsKey(key
))
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
);
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
))
185 for (DirectoryUser user
: wc
.getNewUsers().values()) {
186 if (f
== null || f
.match(user
.getProperties()))
189 // no need to check modified users,
190 // since doGetRoles was already based on the modified attributes
192 return res
.toArray(new Role
[res
.size()]);
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());
201 doGetUser(key
, value
, collectedUsers
);
204 DirectoryUser user
= null;
206 user
= (DirectoryUser
) getRole(value
);
208 collectedUsers
.add(user
);
209 } catch (Exception e
) {
213 for (String attr
: getIndexedUserProperties())
214 doGetUser(attr
, value
, collectedUsers
);
216 if (collectedUsers
.size() == 1)
217 return collectedUsers
.get(0);
222 public Authorization
getAuthorization(User user
) {
223 return new LdifAuthorization((DirectoryUser
) user
,
224 getAllRoles((DirectoryUser
) user
));
228 public Role
createRole(String name
, int type
) {
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
);
244 wc
.getModifiedUsers().put(dn
, attrs
);
245 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
246 wc
.getNewUsers().put(dn
, newRole
);
248 return getRole(name
);
251 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
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
);
267 throw new UserDirectoryException("Unsupported type " + type
);
272 public boolean removeRole(String name
) {
274 WorkingCopy wc
= getWorkingCopy();
275 LdapName dn
= toDn(name
);
276 if (!daoHasRole(dn
) && !wc
.getNewUsers().containsKey(dn
))
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());
289 protected void prepare(WorkingCopy wc
) {
293 protected void commit(WorkingCopy wc
) {
297 protected void rollback(WorkingCopy wc
) {
302 protected LdapName
toDn(String name
) {
304 return new LdapName(name
);
305 } catch (InvalidNameException e
) {
306 throw new UserDirectoryException("Badly formatted name", e
);
312 String
getMemberAttributeId() {
313 return memberAttributeId
;
316 List
<String
> getCredentialAttributeIds() {
317 return credentialAttributeIds
;
320 protected URI
getUri() {
324 protected void setUri(URI uri
) {
328 protected List
<String
> getIndexedUserProperties() {
329 return indexedUserProperties
;
332 protected void setIndexedUserProperties(List
<String
> indexedUserProperties
) {
333 this.indexedUserProperties
= indexedUserProperties
;
336 protected void setReadOnly(boolean isReadOnly
) {
337 this.isReadOnly
= isReadOnly
;
340 public boolean isReadOnly() {
344 UserAdmin
getExternalRoles() {
345 return externalRoles
;
348 public void setExternalRoles(UserAdmin externalRoles
) {
349 this.externalRoles
= externalRoles
;
352 public void setSyncRegistry(TransactionSynchronizationRegistry syncRegistry
) {
353 // this.syncRegistry = syncRegistry;
356 public void setTransactionManager(TransactionManager transactionManager
) {
357 this.transactionManager
= transactionManager
;
363 protected class WorkingCopy
implements XAResource
{
365 private int transactionTimeout
= 0;
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
>();
372 public void start(Xid xid
, int flags
) throws XAException
{
373 if (editingTransactionXid
!= null)
374 throw new UserDirectoryException("Transaction "
375 + editingTransactionXid
+ " already editing");
380 public void end(Xid xid
, int flags
) throws XAException
{
386 modifiedUsers
.clear();
387 modifiedUsers
= null;
388 deletedUsers
.clear();
393 editingTransactionXid
= null;
397 public int prepare(Xid xid
) throws XAException
{
399 if (noModifications())
402 AbstractUserDirectory
.this.prepare(this);
403 } catch (Exception e
) {
404 log
.error("Cannot prepare " + xid
, e
);
405 throw new XAException(XAException
.XA_RBOTHER
);
411 public void commit(Xid xid
, boolean onePhase
) throws XAException
{
413 if (noModifications())
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
);
426 public void rollback(Xid xid
) throws XAException
{
429 AbstractUserDirectory
.this.rollback(this);
430 } catch (Exception e
) {
431 log
.error("Cannot rollback " + xid
, e
);
432 throw new XAException(XAException
.XA_HEURMIX
);
437 public void forget(Xid xid
) throws XAException
{
438 throw new UnsupportedOperationException();
442 public boolean isSameRM(XAResource xares
) throws XAException
{
443 return xares
== this;
447 public Xid
[] recover(int flag
) throws XAException
{
448 throw new UnsupportedOperationException();
452 public int getTransactionTimeout() throws XAException
{
453 return transactionTimeout
;
457 public boolean setTransactionTimeout(int seconds
) throws XAException
{
458 transactionTimeout
= seconds
;
462 private Xid
getXid() {
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
);
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;
481 public boolean noModifications() {
482 return newUsers
.size() == 0 && modifiedUsers
.size() == 0
483 && deletedUsers
.size() == 0;
486 public Attributes
getAttributes(LdapName dn
) {
487 if (modifiedUsers
.containsKey(dn
))
488 return modifiedUsers
.get(dn
);
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());
499 public Map
<LdapName
, DirectoryUser
> getNewUsers() {
503 public Map
<LdapName
, DirectoryUser
> getDeletedUsers() {
507 public Map
<LdapName
, Attributes
> getModifiedUsers() {
508 return modifiedUsers
;