1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.naming
.LdapObjs
.inetOrgPerson
;
5 import static org
.argeo
.naming
.LdapObjs
.organizationalPerson
;
6 import static org
.argeo
.naming
.LdapObjs
.person
;
7 import static org
.argeo
.naming
.LdapObjs
.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
.NamingEnumeration
;
22 import javax
.naming
.directory
.Attribute
;
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
;
32 import org
.apache
.commons
.logging
.Log
;
33 import org
.apache
.commons
.logging
.LogFactory
;
34 import org
.argeo
.naming
.LdapAttrs
;
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
.Role
;
40 import org
.osgi
.service
.useradmin
.User
;
41 import org
.osgi
.service
.useradmin
.UserAdmin
;
43 /** Base class for a {@link UserDirectory}. */
44 public abstract class AbstractUserDirectory
implements UserAdmin
, UserDirectory
{
45 static final String SHARED_STATE_USERNAME
= "javax.security.auth.login.name";
46 static final String SHARED_STATE_PASSWORD
= "javax.security.auth.login.password";
48 private final static Log log
= LogFactory
.getLog(AbstractUserDirectory
.class);
50 private final Hashtable
<String
, Object
> properties
;
51 private final LdapName baseDn
, userBaseDn
, groupBaseDn
;
52 private final String userObjectClass
, userBase
, groupObjectClass
, groupBase
;
54 private final boolean readOnly
;
55 private final URI uri
;
57 private UserAdmin externalRoles
;
58 // private List<String> indexedUserProperties = Arrays
59 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
60 // LdapAttrs.cn.name() });
62 private String memberAttributeId
= "member";
63 private List
<String
> credentialAttributeIds
= Arrays
64 .asList(new String
[] { LdapAttrs
.userPassword
.name(), LdapAttrs
.authPassword
.name() });
67 private TransactionManager transactionManager
;
68 private WcXaResource xaResource
= new WcXaResource(this);
70 public AbstractUserDirectory(URI uriArg
, 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
));
79 // uri from properties is ignored
81 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
86 uri
= new URI(uriStr
);
87 } catch (URISyntaxException e
) {
88 throw new UserDirectoryException("Badly formatted URI " + uriStr
, e
);
92 userObjectClass
= UserAdminConf
.userObjectClass
.getValue(properties
);
93 userBase
= UserAdminConf
.userBase
.getValue(properties
);
94 groupObjectClass
= UserAdminConf
.groupObjectClass
.getValue(properties
);
95 groupBase
= UserAdminConf
.groupBase
.getValue(properties
);
97 baseDn
= new LdapName(UserAdminConf
.baseDn
.getValue(properties
));
98 userBaseDn
= new LdapName(userBase
+ "," + baseDn
);
99 groupBaseDn
= new LdapName(groupBase
+ "," + baseDn
);
100 } catch (InvalidNameException e
) {
101 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf
.baseDn
.getValue(properties
), e
);
103 String readOnlyStr
= UserAdminConf
.readOnly
.getValue(properties
);
104 if (readOnlyStr
== null) {
105 readOnly
= readOnlyDefault(uri
);
106 properties
.put(UserAdminConf
.readOnly
.name(), Boolean
.toString(readOnly
));
108 readOnly
= new Boolean(readOnlyStr
);
111 /** Returns the groups this user is a direct member of. */
112 protected abstract List
<LdapName
> getDirectGroups(LdapName dn
);
114 protected abstract Boolean
daoHasRole(LdapName dn
);
116 protected abstract DirectoryUser
daoGetRole(LdapName key
);
118 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
120 protected abstract AbstractUserDirectory
scope(User user
);
126 public void destroy() {
130 protected boolean isEditing() {
131 return xaResource
.wc() != null;
134 protected UserDirectoryWorkingCopy
getWorkingCopy() {
135 UserDirectoryWorkingCopy wc
= xaResource
.wc();
141 protected void checkEdit() {
142 Transaction transaction
;
144 transaction
= transactionManager
.getTransaction();
145 } catch (SystemException e
) {
146 throw new UserDirectoryException("Cannot get transaction", e
);
148 if (transaction
== null)
149 throw new UserDirectoryException("A transaction needs to be active in order to edit");
150 if (xaResource
.wc() == null) {
152 transaction
.enlistResource(xaResource
);
153 } catch (Exception e
) {
154 throw new UserDirectoryException("Cannot enlist " + xaResource
, e
);
160 protected List
<Role
> getAllRoles(DirectoryUser user
) {
161 List
<Role
> allRoles
= new ArrayList
<Role
>();
163 collectRoles(user
, allRoles
);
166 collectAnonymousRoles(allRoles
);
170 private void collectRoles(DirectoryUser user
, List
<Role
> allRoles
) {
171 Attributes attrs
= user
.getAttributes();
172 // TODO centralize attribute name
173 Attribute memberOf
= attrs
.get("memberOf");
174 if (memberOf
!= null) {
176 NamingEnumeration
<?
> values
= memberOf
.getAll();
177 while (values
.hasMore()) {
178 Object value
= values
.next();
179 LdapName groupDn
= new LdapName(value
.toString());
180 DirectoryUser group
= doGetRole(groupDn
);
182 if (log
.isDebugEnabled())
183 log
.debug("Add memberOf " + groupDn
);
185 } catch (Exception e
) {
186 throw new UserDirectoryException("Cannot get memberOf groups for " + user
, e
);
189 for (LdapName groupDn
: getDirectGroups(user
.getDn())) {
190 // TODO check for loops
191 DirectoryUser group
= doGetRole(groupDn
);
193 if (log
.isDebugEnabled())
194 log
.debug("Add direct group " + groupDn
);
195 collectRoles(group
, allRoles
);
200 private void collectAnonymousRoles(List
<Role
> allRoles
) {
201 // TODO gather anonymous roles
206 public Role
getRole(String name
) {
207 return doGetRole(toDn(name
));
210 protected DirectoryUser
doGetRole(LdapName dn
) {
211 UserDirectoryWorkingCopy wc
= getWorkingCopy();
212 DirectoryUser user
= daoGetRole(dn
);
214 if (user
== null && wc
.getNewUsers().containsKey(dn
))
215 user
= wc
.getNewUsers().get(dn
);
216 else if (wc
.getDeletedUsers().containsKey(dn
))
222 @SuppressWarnings("unchecked")
224 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
225 UserDirectoryWorkingCopy wc
= getWorkingCopy();
226 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
227 List
<DirectoryUser
> res
= doGetRoles(f
);
229 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
230 DirectoryUser user
= it
.next();
231 LdapName dn
= user
.getDn();
232 if (wc
.getDeletedUsers().containsKey(dn
))
235 for (DirectoryUser user
: wc
.getNewUsers().values()) {
236 if (f
== null || f
.match(user
.getProperties()))
239 // no need to check modified users,
240 // since doGetRoles was already based on the modified attributes
242 return res
.toArray(new Role
[res
.size()]);
246 public User
getUser(String key
, String value
) {
247 // TODO check value null or empty
248 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
250 doGetUser(key
, value
, collectedUsers
);
252 throw new UserDirectoryException("Key cannot be null");
254 // DirectoryUser user = null;
256 // user = (DirectoryUser) getRole(value);
258 // collectedUsers.add(user);
259 // } catch (Exception e) {
262 // // try all indexes
263 // for (String attr : getIndexedUserProperties())
264 // doGetUser(attr, value, collectedUsers);
266 if (collectedUsers
.size() == 1)
267 return collectedUsers
.get(0);
268 else if (collectedUsers
.size() > 1)
269 log
.warn(collectedUsers
.size() + " users for " + (key
!= null ? key
+ "=" : "") + value
);
273 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
275 Filter f
= FrameworkUtil
.createFilter("(" + key
+ "=" + value
+ ")");
276 List
<DirectoryUser
> users
= doGetRoles(f
);
277 collectedUsers
.addAll(users
);
278 } catch (InvalidSyntaxException e
) {
279 throw new UserDirectoryException("Cannot get user with " + key
+ "=" + value
, e
);
284 public Authorization
getAuthorization(User user
) {
285 if (user
== null || user
instanceof DirectoryUser
) {
286 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
289 AbstractUserDirectory scopedUserAdmin
= scope(user
);
291 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
292 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
293 scopedUserAdmin
.getAllRoles(directoryUser
));
294 return authorization
;
296 scopedUserAdmin
.destroy();
302 public Role
createRole(String name
, int type
) {
304 UserDirectoryWorkingCopy wc
= getWorkingCopy();
305 LdapName dn
= toDn(name
);
306 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
)) || wc
.getNewUsers().containsKey(dn
))
307 throw new UserDirectoryException("Already a role " + name
);
308 BasicAttributes attrs
= new BasicAttributes(true);
309 // attrs.put(LdifName.dn.name(), dn.toString());
310 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
311 // TODO deal with multiple attr RDN
312 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
313 if (wc
.getDeletedUsers().containsKey(dn
)) {
314 wc
.getDeletedUsers().remove(dn
);
315 wc
.getModifiedUsers().put(dn
, attrs
);
317 wc
.getModifiedUsers().put(dn
, attrs
);
318 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
319 wc
.getNewUsers().put(dn
, newRole
);
321 return getRole(name
);
324 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
326 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
327 if (type
== Role
.USER
) {
328 String userObjClass
= newUserObjectClass(dn
);
329 objClass
.add(userObjClass
);
330 if (inetOrgPerson
.name().equals(userObjClass
)) {
331 objClass
.add(organizationalPerson
.name());
332 objClass
.add(person
.name());
333 } else if (organizationalPerson
.name().equals(userObjClass
)) {
334 objClass
.add(person
.name());
336 objClass
.add(top
.name());
338 newRole
= new LdifUser(this, dn
, attrs
);
339 } else if (type
== Role
.GROUP
) {
340 String groupObjClass
= getGroupObjectClass();
341 objClass
.add(groupObjClass
);
342 // objClass.add(LdifName.extensibleObject.name());
343 objClass
.add(top
.name());
345 newRole
= new LdifGroup(this, dn
, attrs
);
347 throw new UserDirectoryException("Unsupported type " + type
);
352 public boolean removeRole(String name
) {
354 UserDirectoryWorkingCopy wc
= getWorkingCopy();
355 LdapName dn
= toDn(name
);
356 boolean actuallyDeleted
;
357 if (daoHasRole(dn
) || wc
.getNewUsers().containsKey(dn
)) {
358 DirectoryUser user
= (DirectoryUser
) getRole(name
);
359 wc
.getDeletedUsers().put(dn
, user
);
360 actuallyDeleted
= true;
361 } else {// just removing from groups (e.g. system roles)
362 actuallyDeleted
= false;
364 for (LdapName groupDn
: getDirectGroups(dn
)) {
365 DirectoryUser group
= doGetRole(groupDn
);
366 group
.getAttributes().get(getMemberAttributeId()).remove(dn
.toString());
368 return actuallyDeleted
;
372 protected void prepare(UserDirectoryWorkingCopy wc
) {
376 protected void commit(UserDirectoryWorkingCopy wc
) {
380 protected void rollback(UserDirectoryWorkingCopy wc
) {
385 protected LdapName
toDn(String name
) {
387 return new LdapName(name
);
388 } catch (InvalidNameException e
) {
389 throw new UserDirectoryException("Badly formatted name", e
);
394 protected String
getMemberAttributeId() {
395 return memberAttributeId
;
398 protected List
<String
> getCredentialAttributeIds() {
399 return credentialAttributeIds
;
402 protected URI
getUri() {
406 // protected List<String> getIndexedUserProperties() {
407 // return indexedUserProperties;
410 // protected void setIndexedUserProperties(List<String>
411 // indexedUserProperties) {
412 // this.indexedUserProperties = indexedUserProperties;
415 private static boolean readOnlyDefault(URI uri
) {
418 if (uri
.getScheme() == null)
419 return false;// assume relative file to be writable
420 if (uri
.getScheme().equals("file")) {
421 File file
= new File(uri
);
423 return !file
.canWrite();
425 return !file
.getParentFile().canWrite();
430 public boolean isReadOnly() {
434 protected UserAdmin
getExternalRoles() {
435 return externalRoles
;
438 protected int roleType(LdapName dn
) {
439 if (dn
.startsWith(groupBaseDn
))
441 else if (dn
.startsWith(userBaseDn
))
447 /** dn can be null, in that case a default should be returned. */
448 public String
getUserObjectClass() {
449 return userObjectClass
;
452 public String
getUserBase() {
456 protected String
newUserObjectClass(LdapName dn
) {
457 return getUserObjectClass();
460 public String
getGroupObjectClass() {
461 return groupObjectClass
;
464 public String
getGroupBase() {
468 public LdapName
getBaseDn() {
469 return (LdapName
) baseDn
.clone();
472 public Dictionary
<String
, Object
> getProperties() {
476 public Dictionary
<String
, Object
> cloneProperties() {
477 return new Hashtable
<>(properties
);
480 public void setExternalRoles(UserAdmin externalRoles
) {
481 this.externalRoles
= externalRoles
;
484 public void setTransactionManager(TransactionManager transactionManager
) {
485 this.transactionManager
= transactionManager
;
488 public WcXaResource
getXaResource() {