1 package org
.argeo
.util
.directory
.ldap
;
3 import static org
.argeo
.util
.directory
.ldap
.LdapNameUtils
.toLdapName
;
7 import java
.net
.URISyntaxException
;
8 import java
.util
.Arrays
;
9 import java
.util
.Dictionary
;
10 import java
.util
.Enumeration
;
11 import java
.util
.Hashtable
;
12 import java
.util
.List
;
13 import java
.util
.Optional
;
14 import java
.util
.StringJoiner
;
16 import javax
.naming
.InvalidNameException
;
17 import javax
.naming
.NameNotFoundException
;
18 import javax
.naming
.NamingEnumeration
;
19 import javax
.naming
.NamingException
;
20 import javax
.naming
.directory
.Attribute
;
21 import javax
.naming
.directory
.Attributes
;
22 import javax
.naming
.ldap
.LdapName
;
23 import javax
.naming
.ldap
.Rdn
;
24 import javax
.transaction
.xa
.XAResource
;
26 import org
.argeo
.osgi
.useradmin
.OsUserDirectory
;
27 import org
.argeo
.util
.directory
.Directory
;
28 import org
.argeo
.util
.directory
.DirectoryConf
;
29 import org
.argeo
.util
.directory
.HierarchyUnit
;
30 import org
.argeo
.util
.naming
.LdapAttrs
;
31 import org
.argeo
.util
.naming
.LdapObjs
;
32 import org
.argeo
.util
.transaction
.WorkControl
;
33 import org
.argeo
.util
.transaction
.WorkingCopyXaResource
;
34 import org
.argeo
.util
.transaction
.XAResourceProvider
;
36 public abstract class AbstractLdapDirectory
implements Directory
, XAResourceProvider
{
37 protected static final String SHARED_STATE_USERNAME
= "javax.security.auth.login.name";
38 protected static final String SHARED_STATE_PASSWORD
= "javax.security.auth.login.password";
40 protected final LdapName baseDn
;
41 protected final Hashtable
<String
, Object
> properties
;
42 private final Rdn userBaseRdn
, groupBaseRdn
, systemRoleBaseRdn
;
43 private final String userObjectClass
, groupObjectClass
;
44 private String memberAttributeId
= "member";
46 private final boolean readOnly
;
47 private final boolean disabled
;
48 private final String uri
;
50 private String forcedPassword
;
52 private final boolean scoped
;
54 private List
<String
> credentialAttributeIds
= Arrays
55 .asList(new String
[] { LdapAttrs
.userPassword
.name(), LdapAttrs
.authPassword
.name() });
57 private WorkControl transactionControl
;
58 private WorkingCopyXaResource
<LdapEntryWorkingCopy
> xaResource
;
60 private LdapDirectoryDao directoryDao
;
62 public AbstractLdapDirectory(URI uriArg
, Dictionary
<String
, ?
> props
, boolean scoped
) {
63 this.properties
= new Hashtable
<String
, Object
>();
64 for (Enumeration
<String
> keys
= props
.keys(); keys
.hasMoreElements();) {
65 String key
= keys
.nextElement();
66 properties
.put(key
, props
.get(key
));
68 baseDn
= toLdapName(DirectoryConf
.baseDn
.getValue(properties
));
72 uri
= uriArg
.toString();
73 // uri from properties is ignored
75 String uriStr
= DirectoryConf
.uri
.getValue(properties
);
82 forcedPassword
= DirectoryConf
.forcedPassword
.getValue(properties
);
84 userObjectClass
= DirectoryConf
.userObjectClass
.getValue(properties
);
85 groupObjectClass
= DirectoryConf
.groupObjectClass
.getValue(properties
);
87 String userBase
= DirectoryConf
.userBase
.getValue(properties
);
88 String groupBase
= DirectoryConf
.groupBase
.getValue(properties
);
89 String systemRoleBase
= DirectoryConf
.systemRoleBase
.getValue(properties
);
91 // baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
92 userBaseRdn
= new Rdn(userBase
);
93 // userBaseDn = new LdapName(userBase + "," + baseDn);
94 groupBaseRdn
= new Rdn(groupBase
);
95 // groupBaseDn = new LdapName(groupBase + "," + baseDn);
96 systemRoleBaseRdn
= new Rdn(systemRoleBase
);
97 } catch (InvalidNameException e
) {
98 throw new IllegalArgumentException("Badly formated base DN " + DirectoryConf
.baseDn
.getValue(properties
),
103 String readOnlyStr
= DirectoryConf
.readOnly
.getValue(properties
);
104 if (readOnlyStr
== null) {
105 readOnly
= readOnlyDefault(uri
);
106 properties
.put(DirectoryConf
.readOnly
.name(), Boolean
.toString(readOnly
));
108 readOnly
= Boolean
.parseBoolean(readOnlyStr
);
111 String disabledStr
= DirectoryConf
.disabled
.getValue(properties
);
112 if (disabledStr
!= null)
113 disabled
= Boolean
.parseBoolean(disabledStr
);
117 URI u
= URI
.create(uri
);
118 if (!getRealm().isEmpty() || DirectoryConf
.SCHEME_LDAP
.equals(u
.getScheme())
119 || DirectoryConf
.SCHEME_LDAPS
.equals(u
.getScheme())) {
120 directoryDao
= new LdapDao(this);
121 } else if (DirectoryConf
.SCHEME_FILE
.equals(u
.getScheme())) {
122 directoryDao
= new LdifDao(this);
123 } else if (DirectoryConf
.SCHEME_OS
.equals(u
.getScheme())) {
124 directoryDao
= new OsUserDirectory(this);
125 // singleUser = true;
127 throw new IllegalArgumentException("Unsupported scheme " + u
.getScheme());
129 xaResource
= new WorkingCopyXaResource
<>(directoryDao
);
136 // public abstract HierarchyUnit doGetHierarchyUnit(LdapName dn);
138 // public abstract Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
140 // protected abstract Boolean daoHasEntry(LdapName dn);
142 // protected abstract LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException;
144 // protected abstract List<LdapEntry> doGetEntries(LdapName searchBase, Filter f, boolean deep);
146 // /** Returns the groups this user is a direct member of. */
147 // protected abstract List<LdapName> getDirectGroups(LdapName dn);
153 getDirectoryDao().init();
156 public void destroy() {
157 getDirectoryDao().destroy();
163 protected abstract LdapEntry
newUser(LdapName name
, Attributes attrs
);
165 protected abstract LdapEntry
newGroup(LdapName name
, Attributes attrs
);
171 public boolean isEditing() {
172 return xaResource
.wc() != null;
175 public LdapEntryWorkingCopy
getWorkingCopy() {
176 LdapEntryWorkingCopy wc
= xaResource
.wc();
182 public void checkEdit() {
183 if (xaResource
.wc() == null) {
185 transactionControl
.getWorkContext().registerXAResource(xaResource
, null);
186 } catch (Exception e
) {
187 throw new IllegalStateException("Cannot enlist " + xaResource
, e
);
193 public void setTransactionControl(WorkControl transactionControl
) {
194 this.transactionControl
= transactionControl
;
197 public XAResource
getXaResource() {
201 public boolean removeEntry(LdapName dn
) {
203 LdapEntryWorkingCopy wc
= getWorkingCopy();
204 boolean actuallyDeleted
;
205 if (getDirectoryDao().daoHasEntry(dn
) || wc
.getNewData().containsKey(dn
)) {
206 LdapEntry user
= doGetRole(dn
);
207 wc
.getDeletedData().put(dn
, user
);
208 actuallyDeleted
= true;
209 } else {// just removing from groups (e.g. system roles)
210 actuallyDeleted
= false;
212 for (LdapName groupDn
: getDirectoryDao().getDirectGroups(dn
)) {
213 LdapEntry group
= doGetRole(groupDn
);
214 group
.getAttributes().get(getMemberAttributeId()).remove(dn
.toString());
216 return actuallyDeleted
;
223 protected LdapEntry
doGetRole(LdapName dn
) {
224 LdapEntryWorkingCopy wc
= getWorkingCopy();
227 user
= getDirectoryDao().daoGetEntry(dn
);
228 } catch (NameNotFoundException e
) {
232 if (user
== null && wc
.getNewData().containsKey(dn
))
233 user
= wc
.getNewData().get(dn
);
234 else if (wc
.getDeletedData().containsKey(dn
))
240 protected void collectGroups(LdapEntry user
, List
<LdapEntry
> allRoles
) {
241 Attributes attrs
= user
.getAttributes();
242 // TODO centralize attribute name
243 Attribute memberOf
= attrs
.get(LdapAttrs
.memberOf
.name());
244 // if user belongs to this directory, we only check memberOf
245 if (memberOf
!= null && user
.getDn().startsWith(getBaseDn())) {
247 NamingEnumeration
<?
> values
= memberOf
.getAll();
248 while (values
.hasMore()) {
249 Object value
= values
.next();
250 LdapName groupDn
= new LdapName(value
.toString());
251 LdapEntry group
= doGetRole(groupDn
);
255 } catch (NamingException e
) {
256 throw new IllegalStateException("Cannot get memberOf groups for " + user
, e
);
259 for (LdapName groupDn
: getDirectoryDao().getDirectGroups(user
.getDn())) {
260 // TODO check for loops
261 LdapEntry group
= doGetRole(groupDn
);
264 collectGroups(group
, allRoles
);
274 public HierarchyUnit
getHierarchyUnit(String path
) {
275 LdapName dn
= pathToName(path
);
276 return directoryDao
.doGetHierarchyUnit(dn
);
280 public Iterable
<HierarchyUnit
> getDirectHierarchyUnits(boolean functionalOnly
) {
281 return directoryDao
.doGetDirectHierarchyUnits(baseDn
, functionalOnly
);
289 public String
getContext() {
290 return getBaseDn().toString();
294 public String
getName() {
295 return nameToSimple(getBaseDn(), ".");
298 protected String
nameToRelativePath(LdapName dn
) {
299 LdapName name
= LdapNameUtils
.relativeName(getBaseDn(), dn
);
300 return nameToSimple(name
, "/");
303 protected String
nameToSimple(LdapName name
, String separator
) {
304 StringJoiner path
= new StringJoiner(separator
);
305 for (int i
= 0; i
< name
.size(); i
++) {
306 path
.add(name
.getRdn(i
).getValue().toString());
308 return path
.toString();
312 protected LdapName
pathToName(String path
) {
314 LdapName name
= (LdapName
) getBaseDn().clone();
315 String
[] segments
= path
.split("/");
316 Rdn parentRdn
= null;
317 for (String segment
: segments
) {
318 // TODO make attr names configurable ?
319 String attr
= LdapAttrs
.ou
.name();
320 if (parentRdn
!= null) {
321 if (getUserBaseRdn().equals(parentRdn
))
322 attr
= LdapAttrs
.uid
.name();
323 else if (getGroupBaseRdn().equals(parentRdn
))
324 attr
= LdapAttrs
.cn
.name();
325 else if (getSystemRoleBaseRdn().equals(parentRdn
))
326 attr
= LdapAttrs
.cn
.name();
328 Rdn rdn
= new Rdn(attr
, segment
);
333 } catch (InvalidNameException e
) {
334 throw new IllegalStateException("Cannot get role " + path
, e
);
342 protected static boolean hasObjectClass(Attributes attrs
, LdapObjs objectClass
) {
343 return hasObjectClass(attrs
, objectClass
.name());
346 protected static boolean hasObjectClass(Attributes attrs
, String objectClass
) {
348 Attribute attr
= attrs
.get(LdapAttrs
.objectClass
.name());
349 NamingEnumeration
<?
> en
= attr
.getAll();
350 while (en
.hasMore()) {
351 String v
= en
.next().toString();
352 if (v
.equalsIgnoreCase(objectClass
))
357 } catch (NamingException e
) {
358 throw new IllegalStateException("Cannot search for objectClass " + objectClass
, e
);
362 private static boolean readOnlyDefault(String uriStr
) {
365 /// TODO make it more generic
368 uri
= new URI(uriStr
.split(" ")[0]);
369 } catch (URISyntaxException e
) {
370 throw new IllegalArgumentException(e
);
372 if (uri
.getScheme() == null)
373 return false;// assume relative file to be writable
374 if (uri
.getScheme().equals(DirectoryConf
.SCHEME_FILE
)) {
375 File file
= new File(uri
);
377 return !file
.canWrite();
379 return !file
.getParentFile().canWrite();
380 } else if (uri
.getScheme().equals(DirectoryConf
.SCHEME_LDAP
)) {
381 if (uri
.getAuthority() != null)// assume writable if authenticated
383 } else if (uri
.getScheme().equals(DirectoryConf
.SCHEME_OS
)) {
386 return true;// read only by default
393 public Optional
<String
> getRealm() {
394 Object realm
= getProperties().get(DirectoryConf
.realm
.name());
396 return Optional
.empty();
397 return Optional
.of(realm
.toString());
400 public LdapName
getBaseDn() {
401 return (LdapName
) baseDn
.clone();
404 public boolean isReadOnly() {
408 public boolean isDisabled() {
412 public Rdn
getUserBaseRdn() {
416 public Rdn
getGroupBaseRdn() {
420 public Rdn
getSystemRoleBaseRdn() {
421 return systemRoleBaseRdn
;
424 public Dictionary
<String
, Object
> getProperties() {
428 public Dictionary
<String
, Object
> cloneProperties() {
429 return new Hashtable
<>(properties
);
432 public String
getForcedPassword() {
433 return forcedPassword
;
436 public boolean isScoped() {
440 public List
<String
> getCredentialAttributeIds() {
441 return credentialAttributeIds
;
444 public String
getUri() {
448 public LdapDirectoryDao
getDirectoryDao() {
452 /** dn can be null, in that case a default should be returned. */
453 public String
getUserObjectClass() {
454 return userObjectClass
;
457 public String
getGroupObjectClass() {
458 return groupObjectClass
;
461 public String
getMemberAttributeId() {
462 return memberAttributeId
;
470 public int hashCode() {
471 return baseDn
.hashCode();
475 public String
toString() {
476 return "Directory " + baseDn
.toString();