1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.naming
.LdapObjs
.extensibleObject
;
5 import static org
.argeo
.naming
.LdapObjs
.inetOrgPerson
;
6 import static org
.argeo
.naming
.LdapObjs
.organizationalPerson
;
7 import static org
.argeo
.naming
.LdapObjs
.person
;
8 import static org
.argeo
.naming
.LdapObjs
.top
;
12 import java
.net
.URISyntaxException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Arrays
;
15 import java
.util
.Dictionary
;
16 import java
.util
.Enumeration
;
17 import java
.util
.Hashtable
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
21 import javax
.naming
.InvalidNameException
;
22 import javax
.naming
.NameNotFoundException
;
23 import javax
.naming
.NamingEnumeration
;
24 import javax
.naming
.directory
.Attribute
;
25 import javax
.naming
.directory
.Attributes
;
26 import javax
.naming
.directory
.BasicAttribute
;
27 import javax
.naming
.directory
.BasicAttributes
;
28 import javax
.naming
.ldap
.LdapName
;
29 import javax
.naming
.ldap
.Rdn
;
30 import javax
.transaction
.SystemException
;
31 import javax
.transaction
.Transaction
;
32 import javax
.transaction
.TransactionManager
;
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 Hashtable
<String
, Object
> properties
;
49 private final LdapName baseDn
, userBaseDn
, groupBaseDn
;
50 private final String userObjectClass
, userBase
, groupObjectClass
, groupBase
;
52 private final boolean readOnly
;
53 private final boolean disabled
;
54 private final URI uri
;
56 private UserAdmin externalRoles
;
57 // private List<String> indexedUserProperties = Arrays
58 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
59 // LdapAttrs.cn.name() });
61 private String memberAttributeId
= "member";
62 private List
<String
> credentialAttributeIds
= Arrays
63 .asList(new String
[] { LdapAttrs
.userPassword
.name(), LdapAttrs
.authPassword
.name() });
66 private TransactionManager transactionManager
;
67 private WcXaResource xaResource
= new WcXaResource(this);
69 public AbstractUserDirectory(URI uriArg
, Dictionary
<String
, ?
> props
) {
70 properties
= new Hashtable
<String
, Object
>();
71 for (Enumeration
<String
> keys
= props
.keys(); keys
.hasMoreElements();) {
72 String key
= keys
.nextElement();
73 properties
.put(key
, props
.get(key
));
78 // uri from properties is ignored
80 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
85 uri
= new URI(uriStr
);
86 } catch (URISyntaxException e
) {
87 throw new UserDirectoryException("Badly formatted URI " + uriStr
, e
);
91 userObjectClass
= UserAdminConf
.userObjectClass
.getValue(properties
);
92 userBase
= UserAdminConf
.userBase
.getValue(properties
);
93 groupObjectClass
= UserAdminConf
.groupObjectClass
.getValue(properties
);
94 groupBase
= UserAdminConf
.groupBase
.getValue(properties
);
96 baseDn
= new LdapName(UserAdminConf
.baseDn
.getValue(properties
));
97 userBaseDn
= new LdapName(userBase
+ "," + baseDn
);
98 groupBaseDn
= new LdapName(groupBase
+ "," + baseDn
);
99 } catch (InvalidNameException e
) {
100 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf
.baseDn
.getValue(properties
), e
);
102 String readOnlyStr
= UserAdminConf
.readOnly
.getValue(properties
);
103 if (readOnlyStr
== null) {
104 readOnly
= readOnlyDefault(uri
);
105 properties
.put(UserAdminConf
.readOnly
.name(), Boolean
.toString(readOnly
));
107 readOnly
= new Boolean(readOnlyStr
);
108 String disabledStr
= UserAdminConf
.disabled
.getValue(properties
);
109 if (disabledStr
!= null)
110 disabled
= new Boolean(disabledStr
);
115 /** Returns the groups this user is a direct member of. */
116 protected abstract List
<LdapName
> getDirectGroups(LdapName dn
);
118 protected abstract Boolean
daoHasRole(LdapName dn
);
120 protected abstract DirectoryUser
daoGetRole(LdapName key
) throws NameNotFoundException
;
122 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
124 protected abstract AbstractUserDirectory
scope(User user
);
130 public void destroy() {
134 protected boolean isEditing() {
135 return xaResource
.wc() != null;
138 protected UserDirectoryWorkingCopy
getWorkingCopy() {
139 UserDirectoryWorkingCopy wc
= xaResource
.wc();
145 protected void checkEdit() {
146 Transaction transaction
;
148 transaction
= transactionManager
.getTransaction();
149 } catch (SystemException e
) {
150 throw new UserDirectoryException("Cannot get transaction", e
);
152 if (transaction
== null)
153 throw new UserDirectoryException("A transaction needs to be active in order to edit");
154 if (xaResource
.wc() == null) {
156 transaction
.enlistResource(xaResource
);
157 } catch (Exception e
) {
158 throw new UserDirectoryException("Cannot enlist " + xaResource
, e
);
164 protected List
<Role
> getAllRoles(DirectoryUser user
) {
165 List
<Role
> allRoles
= new ArrayList
<Role
>();
167 collectRoles(user
, allRoles
);
170 collectAnonymousRoles(allRoles
);
174 private void collectRoles(DirectoryUser user
, List
<Role
> allRoles
) {
175 Attributes attrs
= user
.getAttributes();
176 // TODO centralize attribute name
177 Attribute memberOf
= attrs
.get(LdapAttrs
.memberOf
.name());
178 if (memberOf
!= null) {
180 NamingEnumeration
<?
> values
= memberOf
.getAll();
181 while (values
.hasMore()) {
182 Object value
= values
.next();
183 LdapName groupDn
= new LdapName(value
.toString());
184 DirectoryUser group
= doGetRole(groupDn
);
187 } catch (Exception e
) {
188 throw new UserDirectoryException("Cannot get memberOf groups for " + user
, e
);
191 for (LdapName groupDn
: getDirectGroups(user
.getDn())) {
192 // TODO check for loops
193 DirectoryUser group
= doGetRole(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();
214 user
= daoGetRole(dn
);
215 } catch (NameNotFoundException e
) {
219 if (user
== null && wc
.getNewUsers().containsKey(dn
))
220 user
= wc
.getNewUsers().get(dn
);
221 else if (wc
.getDeletedUsers().containsKey(dn
))
228 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
229 UserDirectoryWorkingCopy wc
= getWorkingCopy();
230 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
231 List
<DirectoryUser
> res
= doGetRoles(f
);
233 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
234 DirectoryUser user
= it
.next();
235 LdapName dn
= user
.getDn();
236 if (wc
.getDeletedUsers().containsKey(dn
))
239 for (DirectoryUser user
: wc
.getNewUsers().values()) {
240 if (f
== null || f
.match(user
.getProperties()))
243 // no need to check modified users,
244 // since doGetRoles was already based on the modified attributes
246 return res
.toArray(new Role
[res
.size()]);
250 public User
getUser(String key
, String value
) {
251 // TODO check value null or empty
252 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
254 doGetUser(key
, value
, collectedUsers
);
256 throw new UserDirectoryException("Key cannot be null");
259 if (collectedUsers
.size() == 1) {
260 return collectedUsers
.get(0);
261 } else if (collectedUsers
.size() > 1) {
262 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
268 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
270 Filter f
= FrameworkUtil
.createFilter("(" + key
+ "=" + value
+ ")");
271 List
<DirectoryUser
> users
= doGetRoles(f
);
272 collectedUsers
.addAll(users
);
273 } catch (InvalidSyntaxException e
) {
274 throw new UserDirectoryException("Cannot get user with " + key
+ "=" + value
, e
);
279 public Authorization
getAuthorization(User user
) {
280 if (user
== null || user
instanceof DirectoryUser
) {
281 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
284 AbstractUserDirectory scopedUserAdmin
= scope(user
);
286 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
287 if (directoryUser
== null)
288 throw new UserDirectoryException("No scoped user found for " + user
);
289 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
290 scopedUserAdmin
.getAllRoles(directoryUser
));
291 return authorization
;
293 scopedUserAdmin
.destroy();
299 public Role
createRole(String name
, int type
) {
301 UserDirectoryWorkingCopy wc
= getWorkingCopy();
302 LdapName dn
= toDn(name
);
303 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
)) || wc
.getNewUsers().containsKey(dn
))
304 throw new UserDirectoryException("Already a role " + name
);
305 BasicAttributes attrs
= new BasicAttributes(true);
306 // attrs.put(LdifName.dn.name(), dn.toString());
307 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
308 // TODO deal with multiple attr RDN
309 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
310 if (wc
.getDeletedUsers().containsKey(dn
)) {
311 wc
.getDeletedUsers().remove(dn
);
312 wc
.getModifiedUsers().put(dn
, attrs
);
313 return getRole(name
);
315 wc
.getModifiedUsers().put(dn
, attrs
);
316 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
317 wc
.getNewUsers().put(dn
, newRole
);
322 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
324 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
325 if (type
== Role
.USER
) {
326 String userObjClass
= newUserObjectClass(dn
);
327 objClass
.add(userObjClass
);
328 if (inetOrgPerson
.name().equals(userObjClass
)) {
329 objClass
.add(organizationalPerson
.name());
330 objClass
.add(person
.name());
331 } else if (organizationalPerson
.name().equals(userObjClass
)) {
332 objClass
.add(person
.name());
334 objClass
.add(top
.name());
335 objClass
.add(extensibleObject
.name());
337 newRole
= new LdifUser(this, dn
, attrs
);
338 } else if (type
== Role
.GROUP
) {
339 String groupObjClass
= getGroupObjectClass();
340 objClass
.add(groupObjClass
);
341 // objClass.add(LdifName.extensibleObject.name());
342 objClass
.add(top
.name());
344 newRole
= new LdifGroup(this, dn
, attrs
);
346 throw new UserDirectoryException("Unsupported type " + type
);
351 public boolean removeRole(String name
) {
353 UserDirectoryWorkingCopy wc
= getWorkingCopy();
354 LdapName dn
= toDn(name
);
355 boolean actuallyDeleted
;
356 if (daoHasRole(dn
) || wc
.getNewUsers().containsKey(dn
)) {
357 DirectoryUser user
= (DirectoryUser
) getRole(name
);
358 wc
.getDeletedUsers().put(dn
, user
);
359 actuallyDeleted
= true;
360 } else {// just removing from groups (e.g. system roles)
361 actuallyDeleted
= false;
363 for (LdapName groupDn
: getDirectGroups(dn
)) {
364 DirectoryUser group
= doGetRole(groupDn
);
365 group
.getAttributes().get(getMemberAttributeId()).remove(dn
.toString());
367 return actuallyDeleted
;
371 protected void prepare(UserDirectoryWorkingCopy wc
) {
375 protected void commit(UserDirectoryWorkingCopy wc
) {
379 protected void rollback(UserDirectoryWorkingCopy wc
) {
384 protected LdapName
toDn(String name
) {
386 return new LdapName(name
);
387 } catch (InvalidNameException e
) {
388 throw new UserDirectoryException("Badly formatted name", e
);
393 protected String
getMemberAttributeId() {
394 return memberAttributeId
;
397 protected List
<String
> getCredentialAttributeIds() {
398 return credentialAttributeIds
;
401 protected URI
getUri() {
405 private static boolean readOnlyDefault(URI uri
) {
408 if (uri
.getScheme() == null)
409 return false;// assume relative file to be writable
410 if (uri
.getScheme().equals(UserAdminConf
.SCHEME_FILE
)) {
411 File file
= new File(uri
);
413 return !file
.canWrite();
415 return !file
.getParentFile().canWrite();
416 } else if (uri
.getScheme().equals(UserAdminConf
.SCHEME_LDAP
)) {
417 if (uri
.getAuthority() != null)// assume writable if authenticated
419 } else if (uri
.getScheme().equals(UserAdminConf
.SCHEME_OS
)) {
422 return true;// read only by default
425 public boolean isReadOnly() {
429 public boolean isDisabled() {
433 protected UserAdmin
getExternalRoles() {
434 return externalRoles
;
437 protected int roleType(LdapName dn
) {
438 if (dn
.startsWith(groupBaseDn
))
440 else if (dn
.startsWith(userBaseDn
))
446 /** dn can be null, in that case a default should be returned. */
447 public String
getUserObjectClass() {
448 return userObjectClass
;
451 public String
getUserBase() {
455 protected String
newUserObjectClass(LdapName dn
) {
456 return getUserObjectClass();
459 public String
getGroupObjectClass() {
460 return groupObjectClass
;
463 public String
getGroupBase() {
467 public LdapName
getBaseDn() {
468 return (LdapName
) baseDn
.clone();
471 public Dictionary
<String
, Object
> getProperties() {
475 public Dictionary
<String
, Object
> cloneProperties() {
476 return new Hashtable
<>(properties
);
479 public void setExternalRoles(UserAdmin externalRoles
) {
480 this.externalRoles
= externalRoles
;
483 public void setTransactionManager(TransactionManager transactionManager
) {
484 this.transactionManager
= transactionManager
;
487 public WcXaResource
getXaResource() {