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
.Enumeration
;
16 import java
.util
.HashMap
;
17 import java
.util
.Hashtable
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
22 import javax
.naming
.InvalidNameException
;
23 import javax
.naming
.directory
.Attributes
;
24 import javax
.naming
.directory
.BasicAttribute
;
25 import javax
.naming
.directory
.BasicAttributes
;
26 import javax
.naming
.ldap
.LdapName
;
27 import javax
.naming
.ldap
.Rdn
;
28 import javax
.transaction
.SystemException
;
29 import javax
.transaction
.Transaction
;
30 import javax
.transaction
.TransactionManager
;
31 import javax
.transaction
.xa
.XAException
;
32 import javax
.transaction
.xa
.XAResource
;
33 import javax
.transaction
.xa
.Xid
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
37 import org
.osgi
.framework
.Filter
;
38 import org
.osgi
.framework
.FrameworkUtil
;
39 import org
.osgi
.framework
.InvalidSyntaxException
;
40 import org
.osgi
.service
.useradmin
.Authorization
;
41 import org
.osgi
.service
.useradmin
.Role
;
42 import org
.osgi
.service
.useradmin
.User
;
43 import org
.osgi
.service
.useradmin
.UserAdmin
;
45 /** Base class for a {@link UserDirectory}. */
46 abstract class AbstractUserDirectory
implements UserAdmin
, UserDirectory
{
47 private final static Log log
= LogFactory
48 .getLog(AbstractUserDirectory
.class);
50 private final Hashtable
<String
, Object
> properties
;
51 private final String baseDn
;
52 private final String userObjectClass
;
53 private final String groupObjectClass
;
55 private final boolean readOnly
;
56 private final URI uri
;
58 private UserAdmin externalRoles
;
59 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] {
60 LdifName
.uid
.name(), LdifName
.mail
.name(), LdifName
.cn
.name() });
62 private String memberAttributeId
= "member";
63 private List
<String
> credentialAttributeIds
= Arrays
64 .asList(new String
[] { LdifName
.userpassword
.name() });
66 private TransactionManager transactionManager
;
67 private ThreadLocal
<WorkingCopy
> workingCopy
= new ThreadLocal
<AbstractUserDirectory
.WorkingCopy
>();
68 private Xid editingTransactionXid
= null;
70 AbstractUserDirectory(Dictionary
<String
, ?
> props
) {
71 properties
= new Hashtable
<String
, Object
>();
72 for (Enumeration
<String
> keys
= props
.keys(); keys
.hasMoreElements();) {
73 String key
= keys
.nextElement();
74 properties
.put(key
, props
.get(key
));
77 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
82 uri
= new URI(uriStr
);
83 } catch (URISyntaxException e
) {
84 throw new UserDirectoryException("Badly formatted URI "
88 baseDn
= UserAdminConf
.baseDn
.getValue(properties
).toString();
89 String readOnlyStr
= UserAdminConf
.readOnly
.getValue(properties
);
90 if (readOnlyStr
== null) {
91 readOnly
= readOnlyDefault(uri
);
92 properties
.put(UserAdminConf
.readOnly
.property(),
93 Boolean
.toString(readOnly
));
95 readOnly
= new Boolean(readOnlyStr
);
97 userObjectClass
= UserAdminConf
.userObjectClass
.getValue(properties
);
98 groupObjectClass
= UserAdminConf
.groupObjectClass
.getValue(properties
);
101 /** Returns the groups this user is a direct member of. */
102 protected abstract List
<LdapName
> getDirectGroups(LdapName dn
);
104 protected abstract Boolean
daoHasRole(LdapName dn
);
106 protected abstract DirectoryUser
daoGetRole(LdapName key
);
108 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
114 public void destroy() {
118 boolean isEditing() {
119 if (editingTransactionXid
== null)
121 return workingCopy
.get() != null;
124 protected WorkingCopy
getWorkingCopy() {
125 WorkingCopy wc
= workingCopy
.get();
128 if (wc
.xid
== null) {
129 workingCopy
.set(null);
136 Transaction transaction
;
138 transaction
= transactionManager
.getTransaction();
139 } catch (SystemException e
) {
140 throw new UserDirectoryException("Cannot get transaction", e
);
142 if (transaction
== null)
143 throw new UserDirectoryException(
144 "A transaction needs to be active in order to edit");
145 if (editingTransactionXid
== null) {
146 WorkingCopy wc
= new WorkingCopy();
148 transaction
.enlistResource(wc
);
149 editingTransactionXid
= wc
.getXid();
151 } catch (Exception e
) {
152 throw new UserDirectoryException("Cannot enlist " + wc
, e
);
155 if (workingCopy
.get() == null)
156 throw new UserDirectoryException("Transaction "
157 + editingTransactionXid
+ " already editing");
158 else if (!editingTransactionXid
.equals(workingCopy
.get().getXid()))
159 throw new UserDirectoryException("Working copy Xid "
160 + workingCopy
.get().getXid() + " inconsistent with"
161 + editingTransactionXid
);
165 List
<Role
> getAllRoles(DirectoryUser user
) {
166 List
<Role
> allRoles
= new ArrayList
<Role
>();
168 collectRoles(user
, allRoles
);
171 collectAnonymousRoles(allRoles
);
175 private void collectRoles(DirectoryUser user
, List
<Role
> allRoles
) {
176 for (LdapName groupDn
: getDirectGroups(user
.getDn())) {
177 // TODO check for loops
178 DirectoryUser group
= doGetRole(groupDn
);
180 collectRoles(group
, allRoles
);
184 private void collectAnonymousRoles(List
<Role
> allRoles
) {
185 // TODO gather anonymous roles
190 public Role
getRole(String name
) {
191 return doGetRole(toDn(name
));
194 protected DirectoryUser
doGetRole(LdapName dn
) {
195 WorkingCopy wc
= getWorkingCopy();
196 DirectoryUser user
= daoGetRole(dn
);
198 if (user
== null && wc
.getNewUsers().containsKey(dn
))
199 user
= wc
.getNewUsers().get(dn
);
200 else if (wc
.getDeletedUsers().containsKey(dn
))
206 @SuppressWarnings("unchecked")
208 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
209 WorkingCopy wc
= getWorkingCopy();
210 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
211 List
<DirectoryUser
> res
= doGetRoles(f
);
213 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
214 DirectoryUser user
= it
.next();
215 LdapName dn
= user
.getDn();
216 if (wc
.getDeletedUsers().containsKey(dn
))
219 for (DirectoryUser user
: wc
.getNewUsers().values()) {
220 if (f
== null || f
.match(user
.getProperties()))
223 // no need to check modified users,
224 // since doGetRoles was already based on the modified attributes
226 return res
.toArray(new Role
[res
.size()]);
230 public User
getUser(String key
, String value
) {
231 // TODO check value null or empty
232 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>(
233 getIndexedUserProperties().size());
235 doGetUser(key
, value
, collectedUsers
);
238 DirectoryUser user
= null;
240 user
= (DirectoryUser
) getRole(value
);
242 collectedUsers
.add(user
);
243 } catch (Exception e
) {
247 for (String attr
: getIndexedUserProperties())
248 doGetUser(attr
, value
, collectedUsers
);
250 if (collectedUsers
.size() == 1)
251 return collectedUsers
.get(0);
255 protected void doGetUser(String key
, String value
,
256 List
<DirectoryUser
> collectedUsers
) {
258 Filter f
= FrameworkUtil
.createFilter("(&(" + objectClass
+ "="
259 + getUserObjectClass() + ")(" + key
+ "=" + value
+ "))");
260 List
<DirectoryUser
> users
= doGetRoles(f
);
261 collectedUsers
.addAll(users
);
262 } catch (InvalidSyntaxException e
) {
263 throw new UserDirectoryException("Cannot get user with " + key
269 public Authorization
getAuthorization(User user
) {
270 return new LdifAuthorization((DirectoryUser
) user
,
271 getAllRoles((DirectoryUser
) user
));
275 public Role
createRole(String name
, int type
) {
277 WorkingCopy wc
= getWorkingCopy();
278 LdapName dn
= toDn(name
);
279 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
))
280 || wc
.getNewUsers().containsKey(dn
))
281 throw new UserDirectoryException("Already a role " + name
);
282 BasicAttributes attrs
= new BasicAttributes();
283 attrs
.put("dn", dn
.toString());
284 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
285 // TODO deal with multiple attr RDN
286 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
287 if (wc
.getDeletedUsers().containsKey(dn
)) {
288 wc
.getDeletedUsers().remove(dn
);
289 wc
.getModifiedUsers().put(dn
, attrs
);
291 wc
.getModifiedUsers().put(dn
, attrs
);
292 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
293 wc
.getNewUsers().put(dn
, newRole
);
295 return getRole(name
);
298 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
300 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
301 if (type
== Role
.USER
) {
302 String userObjClass
= getUserObjectClass();
303 objClass
.add(userObjClass
);
304 if (inetOrgPerson
.name().equals(userObjClass
)) {
305 objClass
.add(organizationalPerson
.name());
306 objClass
.add(person
.name());
307 } else if (organizationalPerson
.name().equals(userObjClass
)) {
308 objClass
.add(person
.name());
312 newRole
= new LdifUser(this, dn
, attrs
);
313 } else if (type
== Role
.GROUP
) {
314 objClass
.add(getGroupObjectClass());
317 newRole
= new LdifGroup(this, dn
, attrs
);
319 throw new UserDirectoryException("Unsupported type " + type
);
324 public boolean removeRole(String name
) {
326 WorkingCopy wc
= getWorkingCopy();
327 LdapName dn
= toDn(name
);
328 boolean actuallyDeleted
;
329 if (daoHasRole(dn
) || wc
.getNewUsers().containsKey(dn
)) {
330 DirectoryUser user
= (DirectoryUser
) getRole(name
);
331 wc
.getDeletedUsers().put(dn
, user
);
332 actuallyDeleted
= true;
333 } else {// just removing from groups (e.g. system roles)
334 actuallyDeleted
= false;
336 for (LdapName groupDn
: getDirectGroups(dn
)) {
337 DirectoryUser group
= doGetRole(groupDn
);
338 group
.getAttributes().get(getMemberAttributeId())
339 .remove(dn
.toString());
341 return actuallyDeleted
;
345 protected void prepare(WorkingCopy wc
) {
349 protected void commit(WorkingCopy wc
) {
353 protected void rollback(WorkingCopy wc
) {
358 protected LdapName
toDn(String name
) {
360 return new LdapName(name
);
361 } catch (InvalidNameException e
) {
362 throw new UserDirectoryException("Badly formatted name", e
);
368 String
getMemberAttributeId() {
369 return memberAttributeId
;
372 List
<String
> getCredentialAttributeIds() {
373 return credentialAttributeIds
;
376 protected URI
getUri() {
380 protected List
<String
> getIndexedUserProperties() {
381 return indexedUserProperties
;
384 protected void setIndexedUserProperties(List
<String
> indexedUserProperties
) {
385 this.indexedUserProperties
= indexedUserProperties
;
388 private static boolean readOnlyDefault(URI uri
) {
391 if (uri
.getScheme().equals("file")) {
392 File file
= new File(uri
);
394 return !file
.canWrite();
396 return !file
.getParentFile().canWrite();
401 public boolean isReadOnly() {
405 UserAdmin
getExternalRoles() {
406 return externalRoles
;
409 public String
getBaseDn() {
413 protected String
getUserObjectClass() {
414 return userObjectClass
;
417 protected String
getGroupObjectClass() {
418 return groupObjectClass
;
421 public Dictionary
<String
, ?
> getProperties() {
425 public void setExternalRoles(UserAdmin externalRoles
) {
426 this.externalRoles
= externalRoles
;
429 public void setTransactionManager(TransactionManager transactionManager
) {
430 this.transactionManager
= transactionManager
;
436 protected class WorkingCopy
implements XAResource
{
438 private int transactionTimeout
= 0;
440 private Map
<LdapName
, DirectoryUser
> newUsers
= new HashMap
<LdapName
, DirectoryUser
>();
441 private Map
<LdapName
, Attributes
> modifiedUsers
= new HashMap
<LdapName
, Attributes
>();
442 private Map
<LdapName
, DirectoryUser
> deletedUsers
= new HashMap
<LdapName
, DirectoryUser
>();
445 public void start(Xid xid
, int flags
) throws XAException
{
446 if (editingTransactionXid
!= null)
447 throw new UserDirectoryException("Transaction "
448 + editingTransactionXid
+ " already editing");
453 public void end(Xid xid
, int flags
) throws XAException
{
459 modifiedUsers
.clear();
460 modifiedUsers
= null;
461 deletedUsers
.clear();
466 editingTransactionXid
= null;
470 public int prepare(Xid xid
) throws XAException
{
472 if (noModifications())
475 AbstractUserDirectory
.this.prepare(this);
476 } catch (Exception e
) {
477 log
.error("Cannot prepare " + xid
, e
);
478 throw new XAException(XAException
.XA_RBOTHER
);
484 public void commit(Xid xid
, boolean onePhase
) throws XAException
{
486 if (noModifications())
490 AbstractUserDirectory
.this.prepare(this);
491 AbstractUserDirectory
.this.commit(this);
492 } catch (Exception e
) {
493 log
.error("Cannot commit " + xid
, e
);
494 throw new XAException(XAException
.XA_RBOTHER
);
499 public void rollback(Xid xid
) throws XAException
{
502 AbstractUserDirectory
.this.rollback(this);
503 } catch (Exception e
) {
504 log
.error("Cannot rollback " + xid
, e
);
505 throw new XAException(XAException
.XA_HEURMIX
);
510 public void forget(Xid xid
) throws XAException
{
511 throw new UnsupportedOperationException();
515 public boolean isSameRM(XAResource xares
) throws XAException
{
516 return xares
== this;
520 public Xid
[] recover(int flag
) throws XAException
{
521 throw new UnsupportedOperationException();
525 public int getTransactionTimeout() throws XAException
{
526 return transactionTimeout
;
530 public boolean setTransactionTimeout(int seconds
) throws XAException
{
531 transactionTimeout
= seconds
;
535 private Xid
getXid() {
539 private void checkXid(Xid xid
) throws XAException
{
540 if (this.xid
== null)
541 throw new XAException(XAException
.XAER_OUTSIDE
);
542 if (!this.xid
.equals(xid
))
543 throw new XAException(XAException
.XAER_NOTA
);
547 protected void finalize() throws Throwable
{
548 if (editingTransactionXid
!= null)
549 log
.warn("Editing transaction still referenced but no working copy "
550 + editingTransactionXid
);
551 editingTransactionXid
= null;
554 public boolean noModifications() {
555 return newUsers
.size() == 0 && modifiedUsers
.size() == 0
556 && deletedUsers
.size() == 0;
559 public Attributes
getAttributes(LdapName dn
) {
560 if (modifiedUsers
.containsKey(dn
))
561 return modifiedUsers
.get(dn
);
565 public void startEditing(DirectoryUser user
) {
566 LdapName dn
= user
.getDn();
567 if (modifiedUsers
.containsKey(dn
))
568 throw new UserDirectoryException("Already editing " + dn
);
569 modifiedUsers
.put(dn
, (Attributes
) user
.getAttributes().clone());
572 public Map
<LdapName
, DirectoryUser
> getNewUsers() {
576 public Map
<LdapName
, DirectoryUser
> getDeletedUsers() {
580 public Map
<LdapName
, Attributes
> getModifiedUsers() {
581 return modifiedUsers
;