1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.util
.naming
.LdapObjs
.extensibleObject
;
5 import static org
.argeo
.util
.naming
.LdapObjs
.inetOrgPerson
;
6 import static org
.argeo
.util
.naming
.LdapObjs
.organizationalPerson
;
7 import static org
.argeo
.util
.naming
.LdapObjs
.person
;
8 import static org
.argeo
.util
.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
;
31 import org
.argeo
.osgi
.transaction
.WorkControl
;
32 import org
.argeo
.util
.naming
.LdapAttrs
;
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 public abstract class AbstractUserDirectory
implements UserAdmin
, UserDirectory
{
43 static final String SHARED_STATE_USERNAME
= "javax.security.auth.login.name";
44 static final String SHARED_STATE_PASSWORD
= "javax.security.auth.login.password";
46 private final Hashtable
<String
, Object
> properties
;
47 private final LdapName baseDn
, userBaseDn
, groupBaseDn
;
48 private final String userObjectClass
, userBase
, groupObjectClass
, groupBase
;
50 private final boolean readOnly
;
51 private final boolean disabled
;
52 private final String uri
;
54 private UserAdmin externalRoles
;
55 // private List<String> indexedUserProperties = Arrays
56 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
57 // LdapAttrs.cn.name() });
59 private final boolean scoped
;
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 WorkControl transactionControl
;
68 private WcXaResource xaResource
= new WcXaResource(this);
70 private String forcedPassword
;
72 AbstractUserDirectory(URI uriArg
, Dictionary
<String
, ?
> props
, boolean scoped
) {
74 properties
= new Hashtable
<String
, Object
>();
75 for (Enumeration
<String
> keys
= props
.keys(); keys
.hasMoreElements();) {
76 String key
= keys
.nextElement();
77 properties
.put(key
, props
.get(key
));
81 uri
= uriArg
.toString();
82 // uri from properties is ignored
84 String uriStr
= UserAdminConf
.uri
.getValue(properties
);
91 forcedPassword
= UserAdminConf
.forcedPassword
.getValue(properties
);
93 userObjectClass
= UserAdminConf
.userObjectClass
.getValue(properties
);
94 userBase
= UserAdminConf
.userBase
.getValue(properties
);
95 groupObjectClass
= UserAdminConf
.groupObjectClass
.getValue(properties
);
96 groupBase
= UserAdminConf
.groupBase
.getValue(properties
);
98 baseDn
= new LdapName(UserAdminConf
.baseDn
.getValue(properties
));
99 userBaseDn
= new LdapName(userBase
+ "," + baseDn
);
100 groupBaseDn
= new LdapName(groupBase
+ "," + baseDn
);
101 } catch (InvalidNameException e
) {
102 throw new UserDirectoryException("Badly formated base DN " + UserAdminConf
.baseDn
.getValue(properties
), e
);
104 String readOnlyStr
= UserAdminConf
.readOnly
.getValue(properties
);
105 if (readOnlyStr
== null) {
106 readOnly
= readOnlyDefault(uri
);
107 properties
.put(UserAdminConf
.readOnly
.name(), Boolean
.toString(readOnly
));
109 readOnly
= Boolean
.parseBoolean(readOnlyStr
);
110 String disabledStr
= UserAdminConf
.disabled
.getValue(properties
);
111 if (disabledStr
!= null)
112 disabled
= Boolean
.parseBoolean(disabledStr
);
117 /** Returns the groups this user is a direct member of. */
118 protected abstract List
<LdapName
> getDirectGroups(LdapName dn
);
120 protected abstract Boolean
daoHasRole(LdapName dn
);
122 protected abstract DirectoryUser
daoGetRole(LdapName key
) throws NameNotFoundException
;
124 protected abstract List
<DirectoryUser
> doGetRoles(Filter f
);
126 protected abstract AbstractUserDirectory
scope(User user
);
132 public void destroy() {
136 protected boolean isEditing() {
137 return xaResource
.wc() != null;
140 protected UserDirectoryWorkingCopy
getWorkingCopy() {
141 UserDirectoryWorkingCopy wc
= xaResource
.wc();
147 protected void checkEdit() {
148 // Transaction transaction;
150 // transaction = transactionManager.getTransaction();
151 // } catch (SystemException e) {
152 // throw new UserDirectoryException("Cannot get transaction", e);
154 // if (transaction == null)
155 // throw new UserDirectoryException("A transaction needs to be active in order to edit");
156 if (xaResource
.wc() == null) {
158 // transaction.enlistResource(xaResource);
159 transactionControl
.getWorkContext().registerXAResource(xaResource
, null);
160 } catch (Exception e
) {
161 throw new UserDirectoryException("Cannot enlist " + xaResource
, e
);
167 protected List
<Role
> getAllRoles(DirectoryUser user
) {
168 List
<Role
> allRoles
= new ArrayList
<Role
>();
170 collectRoles(user
, allRoles
);
173 collectAnonymousRoles(allRoles
);
177 private void collectRoles(DirectoryUser user
, List
<Role
> allRoles
) {
178 Attributes attrs
= user
.getAttributes();
179 // TODO centralize attribute name
180 Attribute memberOf
= attrs
.get(LdapAttrs
.memberOf
.name());
181 // if user belongs to this directory, we only check meberOf
182 if (memberOf
!= null && user
.getDn().startsWith(getBaseDn())) {
184 NamingEnumeration
<?
> values
= memberOf
.getAll();
185 while (values
.hasMore()) {
186 Object value
= values
.next();
187 LdapName groupDn
= new LdapName(value
.toString());
188 DirectoryUser group
= doGetRole(groupDn
);
192 } catch (Exception e
) {
193 throw new UserDirectoryException("Cannot get memberOf groups for " + user
, e
);
196 for (LdapName groupDn
: getDirectGroups(user
.getDn())) {
197 // TODO check for loops
198 DirectoryUser group
= doGetRole(groupDn
);
201 collectRoles(group
, allRoles
);
207 private void collectAnonymousRoles(List
<Role
> allRoles
) {
208 // TODO gather anonymous roles
213 public Role
getRole(String name
) {
214 return doGetRole(toDn(name
));
217 protected DirectoryUser
doGetRole(LdapName dn
) {
218 UserDirectoryWorkingCopy wc
= getWorkingCopy();
221 user
= daoGetRole(dn
);
222 } catch (NameNotFoundException e
) {
226 if (user
== null && wc
.getNewUsers().containsKey(dn
))
227 user
= wc
.getNewUsers().get(dn
);
228 else if (wc
.getDeletedUsers().containsKey(dn
))
235 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
236 UserDirectoryWorkingCopy wc
= getWorkingCopy();
237 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
238 List
<DirectoryUser
> res
= doGetRoles(f
);
240 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
241 DirectoryUser user
= it
.next();
242 LdapName dn
= user
.getDn();
243 if (wc
.getDeletedUsers().containsKey(dn
))
246 for (DirectoryUser user
: wc
.getNewUsers().values()) {
247 if (f
== null || f
.match(user
.getProperties()))
250 // no need to check modified users,
251 // since doGetRoles was already based on the modified attributes
253 return res
.toArray(new Role
[res
.size()]);
257 public User
getUser(String key
, String value
) {
258 // TODO check value null or empty
259 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
261 doGetUser(key
, value
, collectedUsers
);
263 throw new UserDirectoryException("Key cannot be null");
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 + "=" :
275 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
277 Filter f
= FrameworkUtil
.createFilter("(" + key
+ "=" + value
+ ")");
278 List
<DirectoryUser
> users
= doGetRoles(f
);
279 collectedUsers
.addAll(users
);
280 } catch (InvalidSyntaxException e
) {
281 throw new UserDirectoryException("Cannot get user with " + key
+ "=" + value
, e
);
286 public Authorization
getAuthorization(User user
) {
287 if (user
== null || user
instanceof DirectoryUser
) {
288 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
291 AbstractUserDirectory scopedUserAdmin
= scope(user
);
293 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
294 if (directoryUser
== null)
295 throw new UserDirectoryException("No scoped user found for " + user
);
296 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
297 scopedUserAdmin
.getAllRoles(directoryUser
));
298 return authorization
;
300 scopedUserAdmin
.destroy();
306 public Role
createRole(String name
, int type
) {
308 UserDirectoryWorkingCopy wc
= getWorkingCopy();
309 LdapName dn
= toDn(name
);
310 if ((daoHasRole(dn
) && !wc
.getDeletedUsers().containsKey(dn
)) || wc
.getNewUsers().containsKey(dn
))
311 throw new UserDirectoryException("Already a role " + name
);
312 BasicAttributes attrs
= new BasicAttributes(true);
313 // attrs.put(LdifName.dn.name(), dn.toString());
314 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
315 // TODO deal with multiple attr RDN
316 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
317 if (wc
.getDeletedUsers().containsKey(dn
)) {
318 wc
.getDeletedUsers().remove(dn
);
319 wc
.getModifiedUsers().put(dn
, attrs
);
320 return getRole(name
);
322 wc
.getModifiedUsers().put(dn
, attrs
);
323 DirectoryUser newRole
= newRole(dn
, type
, attrs
);
324 wc
.getNewUsers().put(dn
, newRole
);
329 protected DirectoryUser
newRole(LdapName dn
, int type
, Attributes attrs
) {
331 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
332 if (type
== Role
.USER
) {
333 String userObjClass
= newUserObjectClass(dn
);
334 objClass
.add(userObjClass
);
335 if (inetOrgPerson
.name().equals(userObjClass
)) {
336 objClass
.add(organizationalPerson
.name());
337 objClass
.add(person
.name());
338 } else if (organizationalPerson
.name().equals(userObjClass
)) {
339 objClass
.add(person
.name());
341 objClass
.add(top
.name());
342 objClass
.add(extensibleObject
.name());
344 newRole
= new LdifUser(this, dn
, attrs
);
345 } else if (type
== Role
.GROUP
) {
346 String groupObjClass
= getGroupObjectClass();
347 objClass
.add(groupObjClass
);
348 // objClass.add(LdifName.extensibleObject.name());
349 objClass
.add(top
.name());
351 newRole
= new LdifGroup(this, dn
, attrs
);
353 throw new UserDirectoryException("Unsupported type " + type
);
358 public boolean removeRole(String name
) {
360 UserDirectoryWorkingCopy wc
= getWorkingCopy();
361 LdapName dn
= toDn(name
);
362 boolean actuallyDeleted
;
363 if (daoHasRole(dn
) || wc
.getNewUsers().containsKey(dn
)) {
364 DirectoryUser user
= (DirectoryUser
) getRole(name
);
365 wc
.getDeletedUsers().put(dn
, user
);
366 actuallyDeleted
= true;
367 } else {// just removing from groups (e.g. system roles)
368 actuallyDeleted
= false;
370 for (LdapName groupDn
: getDirectGroups(dn
)) {
371 DirectoryUser group
= doGetRole(groupDn
);
372 group
.getAttributes().get(getMemberAttributeId()).remove(dn
.toString());
374 return actuallyDeleted
;
378 protected void prepare(UserDirectoryWorkingCopy wc
) {
382 protected void commit(UserDirectoryWorkingCopy wc
) {
386 protected void rollback(UserDirectoryWorkingCopy wc
) {
391 protected LdapName
toDn(String name
) {
393 return new LdapName(name
);
394 } catch (InvalidNameException e
) {
395 throw new UserDirectoryException("Badly formatted name", e
);
400 protected String
getMemberAttributeId() {
401 return memberAttributeId
;
404 protected List
<String
> getCredentialAttributeIds() {
405 return credentialAttributeIds
;
408 protected String
getUri() {
412 private static boolean readOnlyDefault(String uriStr
) {
415 /// TODO make it more generic
418 uri
= new URI(uriStr
.split(" ")[0]);
419 } catch (URISyntaxException e
) {
420 throw new IllegalArgumentException(e
);
422 if (uri
.getScheme() == null)
423 return false;// assume relative file to be writable
424 if (uri
.getScheme().equals(UserAdminConf
.SCHEME_FILE
)) {
425 File file
= new File(uri
);
427 return !file
.canWrite();
429 return !file
.getParentFile().canWrite();
430 } else if (uri
.getScheme().equals(UserAdminConf
.SCHEME_LDAP
)) {
431 if (uri
.getAuthority() != null)// assume writable if authenticated
433 } else if (uri
.getScheme().equals(UserAdminConf
.SCHEME_OS
)) {
436 return true;// read only by default
439 public boolean isReadOnly() {
443 public boolean isDisabled() {
447 protected UserAdmin
getExternalRoles() {
448 return externalRoles
;
451 protected int roleType(LdapName dn
) {
452 if (dn
.startsWith(groupBaseDn
))
454 else if (dn
.startsWith(userBaseDn
))
460 /** dn can be null, in that case a default should be returned. */
461 public String
getUserObjectClass() {
462 return userObjectClass
;
465 public String
getUserBase() {
469 protected String
newUserObjectClass(LdapName dn
) {
470 return getUserObjectClass();
473 public String
getGroupObjectClass() {
474 return groupObjectClass
;
477 public String
getGroupBase() {
481 public LdapName
getBaseDn() {
482 return (LdapName
) baseDn
.clone();
485 public Dictionary
<String
, Object
> getProperties() {
489 public Dictionary
<String
, Object
> cloneProperties() {
490 return new Hashtable
<>(properties
);
493 public void setExternalRoles(UserAdmin externalRoles
) {
494 this.externalRoles
= externalRoles
;
497 // public void setTransactionManager(TransactionManager transactionManager) {
498 // this.transactionManager = transactionManager;
501 public String
getForcedPassword() {
502 return forcedPassword
;
505 public void setTransactionControl(WorkControl transactionControl
) {
506 this.transactionControl
= transactionControl
;
509 public WcXaResource
getXaResource() {
513 public boolean isScoped() {