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
.Hashtable
;
17 import java
.util
.Iterator
;
18 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
.Xid
;
31 import org
.apache
.commons
.logging
.Log
;
32 import org
.apache
.commons
.logging
.LogFactory
;
33 import org
.osgi
.framework
.Filter
;
34 import org
.osgi
.framework
.FrameworkUtil
;
35 import org
.osgi
.framework
.InvalidSyntaxException
;
36 import org
.osgi
.service
.useradmin
.Authorization
;
37 import org
.osgi
.service
.useradmin
.Role
;
38 import org
.osgi
.service
.useradmin
.User
;
39 import org
.osgi
.service
.useradmin
.UserAdmin
;
41 /** Base class for a {@link UserDirectory}. */
42 abstract class AbstractUserDirectory
implements UserAdmin
, UserDirectory
{
43 private final static Log log
= LogFactory
44 .getLog(AbstractUserDirectory
.class);
46 private final Hashtable
<String
, Object
> properties
;
47 private final String baseDn
;
48 private final String userObjectClass
;
49 private final String groupObjectClass
;
51 private final boolean readOnly
;
52 private final URI uri
;
54 private UserAdmin externalRoles
;
55 private List
<String
> indexedUserProperties
= Arrays
.asList(new String
[] {
56 LdifName
.uid
.name(), LdifName
.mail
.name(), LdifName
.cn
.name() });
58 private String memberAttributeId
= "member";
59 private List
<String
> credentialAttributeIds
= Arrays
60 .asList(new String
[] { LdifName
.userpassword
.name() });
62 private TransactionManager transactionManager
;
63 private ThreadLocal
<UserDirectoryWorkingCopy
> workingCopy
= new ThreadLocal
<UserDirectoryWorkingCopy
>();
64 private Xid editingTransactionXid
= null;
66 AbstractUserDirectory(Dictionary
<String
, ?
> props
) {
67 properties
= new Hashtable
<String
, Object
>();
68 for (Enumeration
<String
> keys
= props
.keys(); keys
.hasMoreElements();) {
69 String key
= keys
.nextElement();
70 properties
.put(key
, props
.get(key
));
73 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
78 uri
= new URI(uriStr
);
79 } catch (URISyntaxException e
) {
80 throw new UserDirectoryException("Badly formatted URI "
84 baseDn
= UserAdminConf
.baseDn
.getValue(properties
).toString();
85 String readOnlyStr
= UserAdminConf
.readOnly
.getValue(properties
);
86 if (readOnlyStr
== null) {
87 readOnly
= readOnlyDefault(uri
);
88 properties
.put(UserAdminConf
.readOnly
.property(),
89 Boolean
.toString(readOnly
));
91 readOnly
= new Boolean(readOnlyStr
);
93 userObjectClass
= UserAdminConf
.userObjectClass
.getValue(properties
);
94 groupObjectClass
= UserAdminConf
.groupObjectClass
.getValue(properties
);
97 /** Returns the groups this user is a direct member of. */
98 protected abstract List
<LdapName
> getDirectGroups(LdapName dn
);
100 protected abstract Boolean
daoHasRole(LdapName dn
);
102 protected abstract DirectoryUser
daoGetRole(LdapName key
);
104 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
110 public void destroy() {
114 boolean isEditing() {
115 if (editingTransactionXid
== null)
117 return workingCopy
.get() != null;
120 protected UserDirectoryWorkingCopy
getWorkingCopy() {
121 UserDirectoryWorkingCopy wc
= workingCopy
.get();
124 if (wc
.getXid() == null) {
125 workingCopy
.set(null);
132 Transaction transaction
;
134 transaction
= transactionManager
.getTransaction();
135 } catch (SystemException e
) {
136 throw new UserDirectoryException("Cannot get transaction", e
);
138 if (transaction
== null)
139 throw new UserDirectoryException(
140 "A transaction needs to be active in order to edit");
141 if (editingTransactionXid
== null) {
142 UserDirectoryWorkingCopy wc
= new UserDirectoryWorkingCopy(this);
144 transaction
.enlistResource(wc
);
145 editingTransactionXid
= wc
.getXid();
147 } catch (Exception e
) {
148 throw new UserDirectoryException("Cannot enlist " + wc
, e
);
151 if (workingCopy
.get() == null)
152 throw new UserDirectoryException("Transaction "
153 + editingTransactionXid
+ " already editing");
154 else if (!editingTransactionXid
.equals(workingCopy
.get().getXid()))
155 throw new UserDirectoryException("Working copy Xid "
156 + workingCopy
.get().getXid() + " inconsistent with"
157 + editingTransactionXid
);
161 List
<Role
> getAllRoles(DirectoryUser user
) {
162 List
<Role
> allRoles
= new ArrayList
<Role
>();
164 collectRoles(user
, allRoles
);
167 collectAnonymousRoles(allRoles
);
171 private void collectRoles(DirectoryUser user
, List
<Role
> allRoles
) {
172 for (LdapName groupDn
: getDirectGroups(user
.getDn())) {
173 // TODO check for loops
174 DirectoryUser group
= doGetRole(groupDn
);
176 collectRoles(group
, allRoles
);
180 private void collectAnonymousRoles(List
<Role
> allRoles
) {
181 // TODO gather anonymous roles
186 public Role
getRole(String name
) {
187 return doGetRole(toDn(name
));
190 protected DirectoryUser
doGetRole(LdapName dn
) {
191 UserDirectoryWorkingCopy wc
= getWorkingCopy();
192 DirectoryUser user
= daoGetRole(dn
);
194 if (user
== null && wc
.getNewUsers().containsKey(dn
))
195 user
= wc
.getNewUsers().get(dn
);
196 else if (wc
.getDeletedUsers().containsKey(dn
))
202 @SuppressWarnings("unchecked")
204 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
205 UserDirectoryWorkingCopy wc
= getWorkingCopy();
206 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
207 List
<DirectoryUser
> res
= doGetRoles(f
);
209 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
210 DirectoryUser user
= it
.next();
211 LdapName dn
= user
.getDn();
212 if (wc
.getDeletedUsers().containsKey(dn
))
215 for (DirectoryUser user
: wc
.getNewUsers().values()) {
216 if (f
== null || f
.match(user
.getProperties()))
219 // no need to check modified users,
220 // since doGetRoles was already based on the modified attributes
222 return res
.toArray(new Role
[res
.size()]);
226 public User
getUser(String key
, String value
) {
227 // TODO check value null or empty
228 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>(
229 getIndexedUserProperties().size());
231 doGetUser(key
, value
, collectedUsers
);
234 DirectoryUser user
= null;
236 user
= (DirectoryUser
) getRole(value
);
238 collectedUsers
.add(user
);
239 } catch (Exception e
) {
243 for (String attr
: getIndexedUserProperties())
244 doGetUser(attr
, value
, collectedUsers
);
246 if (collectedUsers
.size() == 1)
247 return collectedUsers
.get(0);
248 else if (collectedUsers
.size() > 1)
249 log
.warn(collectedUsers
.size() + " users for "
250 + (key
!= null ? key
+ "=" : "") + value
);
254 protected void doGetUser(String key
, String value
,
255 List
<DirectoryUser
> collectedUsers
) {
257 Filter f
= FrameworkUtil
.createFilter("(&(" + objectClass
+ "="
258 + getUserObjectClass() + ")(" + key
+ "=" + value
+ "))");
259 List
<DirectoryUser
> users
= doGetRoles(f
);
260 collectedUsers
.addAll(users
);
261 } catch (InvalidSyntaxException e
) {
262 throw new UserDirectoryException("Cannot get user with " + key
268 public Authorization
getAuthorization(User user
) {
269 return new LdifAuthorization((DirectoryUser
) user
,
270 getAllRoles((DirectoryUser
) user
));
274 public Role
createRole(String name
, int type
) {
276 UserDirectoryWorkingCopy wc
= getWorkingCopy();
277 LdapName dn
= toDn(name
);
278 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
))
279 || wc
.getNewUsers().containsKey(dn
))
280 throw new UserDirectoryException("Already a role " + name
);
281 BasicAttributes attrs
= new BasicAttributes();
282 attrs
.put("dn", dn
.toString());
283 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
284 // TODO deal with multiple attr RDN
285 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
286 if (wc
.getDeletedUsers().containsKey(dn
)) {
287 wc
.getDeletedUsers().remove(dn
);
288 wc
.getModifiedUsers().put(dn
, attrs
);
290 wc
.getModifiedUsers().put(dn
, attrs
);
291 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
292 wc
.getNewUsers().put(dn
, newRole
);
294 return getRole(name
);
297 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
299 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
300 if (type
== Role
.USER
) {
301 String userObjClass
= getUserObjectClass();
302 objClass
.add(userObjClass
);
303 if (inetOrgPerson
.name().equals(userObjClass
)) {
304 objClass
.add(organizationalPerson
.name());
305 objClass
.add(person
.name());
306 } else if (organizationalPerson
.name().equals(userObjClass
)) {
307 objClass
.add(person
.name());
311 newRole
= new LdifUser(this, dn
, attrs
);
312 } else if (type
== Role
.GROUP
) {
313 objClass
.add(getGroupObjectClass());
316 newRole
= new LdifGroup(this, dn
, attrs
);
318 throw new UserDirectoryException("Unsupported type " + type
);
323 public boolean removeRole(String name
) {
325 UserDirectoryWorkingCopy wc
= getWorkingCopy();
326 LdapName dn
= toDn(name
);
327 boolean actuallyDeleted
;
328 if (daoHasRole(dn
) || wc
.getNewUsers().containsKey(dn
)) {
329 DirectoryUser user
= (DirectoryUser
) getRole(name
);
330 wc
.getDeletedUsers().put(dn
, user
);
331 actuallyDeleted
= true;
332 } else {// just removing from groups (e.g. system roles)
333 actuallyDeleted
= false;
335 for (LdapName groupDn
: getDirectGroups(dn
)) {
336 DirectoryUser group
= doGetRole(groupDn
);
337 group
.getAttributes().get(getMemberAttributeId())
338 .remove(dn
.toString());
340 return actuallyDeleted
;
344 protected void prepare(UserDirectoryWorkingCopy wc
) {
348 protected void commit(UserDirectoryWorkingCopy wc
) {
352 protected void rollback(UserDirectoryWorkingCopy wc
) {
356 void clearEditingTransactionXid() {
357 editingTransactionXid
= null;
361 protected LdapName
toDn(String name
) {
363 return new LdapName(name
);
364 } catch (InvalidNameException e
) {
365 throw new UserDirectoryException("Badly formatted name", e
);
371 String
getMemberAttributeId() {
372 return memberAttributeId
;
375 List
<String
> getCredentialAttributeIds() {
376 return credentialAttributeIds
;
379 protected URI
getUri() {
383 protected List
<String
> getIndexedUserProperties() {
384 return indexedUserProperties
;
387 protected void setIndexedUserProperties(List
<String
> indexedUserProperties
) {
388 this.indexedUserProperties
= indexedUserProperties
;
391 private static boolean readOnlyDefault(URI uri
) {
394 if (uri
.getScheme().equals("file")) {
395 File file
= new File(uri
);
397 return !file
.canWrite();
399 return !file
.getParentFile().canWrite();
404 public boolean isReadOnly() {
408 UserAdmin
getExternalRoles() {
409 return externalRoles
;
412 public String
getBaseDn() {
416 protected String
getUserObjectClass() {
417 return userObjectClass
;
420 protected String
getGroupObjectClass() {
421 return groupObjectClass
;
424 public Dictionary
<String
, ?
> getProperties() {
428 public void setExternalRoles(UserAdmin externalRoles
) {
429 this.externalRoles
= externalRoles
;
432 public void setTransactionManager(TransactionManager transactionManager
) {
433 this.transactionManager
= transactionManager
;