1 package org
.argeo
.util
.directory
.ldap
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.util
.naming
.LdapObjs
.inetOrgPerson
;
7 import java
.io
.FileOutputStream
;
8 import java
.io
.IOException
;
9 import java
.io
.InputStream
;
10 import java
.io
.OutputStream
;
12 import java
.net
.URISyntaxException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
15 import java
.util
.HashSet
;
16 import java
.util
.List
;
17 import java
.util
.NavigableMap
;
18 import java
.util
.Objects
;
20 import java
.util
.SortedMap
;
21 import java
.util
.TreeMap
;
23 import javax
.naming
.NameNotFoundException
;
24 import javax
.naming
.NamingEnumeration
;
25 import javax
.naming
.NamingException
;
26 import javax
.naming
.directory
.Attributes
;
27 import javax
.naming
.ldap
.LdapName
;
29 import org
.argeo
.util
.directory
.HierarchyUnit
;
30 import org
.argeo
.util
.naming
.LdapObjs
;
31 import org
.osgi
.framework
.Filter
;
32 import org
.osgi
.framework
.FrameworkUtil
;
33 import org
.osgi
.framework
.InvalidSyntaxException
;
34 import org
.osgi
.service
.useradmin
.Role
;
36 /** A user admin based on a LDIF files. */
37 public class LdifDao
extends AbstractLdapDirectoryDao
{
38 // private NavigableMap<LdapName, LdapEntry> users = new TreeMap<>();
39 // private NavigableMap<LdapName, LdapEntry> groups = new TreeMap<>();
40 private NavigableMap
<LdapName
, LdapEntry
> entries
= new TreeMap
<>();
42 private NavigableMap
<LdapName
, LdapHierarchyUnit
> hierarchy
= new TreeMap
<>();
43 // private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
45 // public LdifUserAdmin(String uri, String baseDn) {
46 // this(fromUri(uri, baseDn), false);
49 public LdifDao(AbstractLdapDirectory directory
) {
53 // protected LdifUserAdmin(Hashtable<String, ?> properties, boolean scoped) {
54 // super( properties, scoped);
57 // public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
58 // super(uri, properties, false);
62 // protected AbstractUserDirectory scope(User user) {
63 // Dictionary<String, Object> credentials = user.getCredentials();
64 // String username = (String) credentials.get(SHARED_STATE_USERNAME);
65 // if (username == null)
66 // username = user.getName();
67 // Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
68 // byte[] pwd = (byte[]) pwdCred;
70 // char[] password = DirectoryDigestUtils.bytesToChars(pwd);
71 // User directoryUser = (User) getRole(username);
72 // if (!directoryUser.hasCredential(null, password))
73 // throw new IllegalStateException("Invalid credentials");
75 // throw new IllegalStateException("Password is required");
77 // Dictionary<String, Object> properties = cloneProperties();
78 // properties.put(DirectoryConf.readOnly.name(), "true");
79 // LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
80 //// scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
81 //// scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
82 // scopedUserAdmin.entries = Collections.unmodifiableNavigableMap(entries);
83 // return scopedUserAdmin;
86 // private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
87 // Hashtable<String, Object> res = new Hashtable<String, Object>();
88 // res.put(DirectoryConf.uri.name(), uri);
89 // res.put(DirectoryConf.baseDn.name(), baseDn);
96 URI u
= new URI(getDirectory().getUri());
97 if (u
.getScheme().equals("file")) {
98 File file
= new File(u
);
102 load(u
.toURL().openStream());
103 } catch (IOException
| URISyntaxException e
) {
104 throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e
);
109 if (getDirectory().getUri() == null)
110 throw new IllegalStateException("Cannot save LDIF user admin: no URI is set");
111 if (getDirectory().isReadOnly())
112 throw new IllegalStateException(
113 "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
114 try (FileOutputStream out
= new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
116 } catch (IOException
| URISyntaxException e
) {
117 throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e
);
121 public void save(OutputStream out
) throws IOException
{
123 LdifWriter ldifWriter
= new LdifWriter(out
);
124 for (LdapName name
: hierarchy
.keySet())
125 ldifWriter
.writeEntry(name
, hierarchy
.get(name
).getAttributes());
126 // for (LdapName name : groups.keySet())
127 // ldifWriter.writeEntry(name, groups.get(name).getAttributes());
128 // for (LdapName name : users.keySet())
129 // ldifWriter.writeEntry(name, users.get(name).getAttributes());
130 for (LdapName name
: entries
.keySet())
131 ldifWriter
.writeEntry(name
, entries
.get(name
).getAttributes());
137 public void load(InputStream in
) {
144 LdifParser ldifParser
= new LdifParser();
145 SortedMap
<LdapName
, Attributes
> allEntries
= ldifParser
.read(in
);
146 for (LdapName key
: allEntries
.keySet()) {
147 Attributes attributes
= allEntries
.get(key
);
148 // check for inconsistency
149 Set
<String
> lowerCase
= new HashSet
<String
>();
150 NamingEnumeration
<String
> ids
= attributes
.getIDs();
151 while (ids
.hasMoreElements()) {
152 String id
= ids
.nextElement().toLowerCase();
153 if (lowerCase
.contains(id
))
154 throw new IllegalStateException(key
+ " has duplicate id " + id
);
158 // analyse object classes
159 NamingEnumeration
<?
> objectClasses
= attributes
.get(objectClass
.name()).getAll();
160 // System.out.println(key);
161 objectClasses
: while (objectClasses
.hasMore()) {
162 String objectClass
= objectClasses
.next().toString();
163 // System.out.println(" " + objectClass);
164 if (objectClass
.toLowerCase().equals(inetOrgPerson
.name().toLowerCase())) {
165 entries
.put(key
, newUser(key
, attributes
));
167 } else if (objectClass
.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
168 entries
.put(key
, newGroup(key
, attributes
));
170 // } else if (objectClass.equalsIgnoreCase(LdapObjs.organization.name())) {
171 // // we only consider organizations which are not groups
172 // hierarchy.put(key, new LdifHierarchyUnit(this, key, HierarchyUnit.ORGANIZATION, attributes));
173 // break objectClasses;
174 } else if (objectClass
.equalsIgnoreCase(LdapObjs
.organizationalUnit
.name())) {
175 // String name = key.getRdn(key.size() - 1).toStrindirectoryDaog();
176 // if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
177 // break objectClasses; // skip
178 // TODO skip if it does not contain groups or users
179 hierarchy
.put(key
, new LdapHierarchyUnit(getDirectory(), key
, attributes
));
186 // hierachyUnits: for (LdapName dn : hierarchy.keySet()) {
187 // LdifHierarchyUnit unit = hierarchy.get(dn);
188 // LdapName parentDn = (LdapName) dn.getPrefix(dn.size() - 1);
189 // LdifHierarchyUnit parent = hierarchy.get(parentDn);
190 // if (parent == null) {
191 // rootHierarchyUnits.add(unit);
192 // unit.parent = null;
193 // continue hierachyUnits;
195 // parent.children.add(unit);
196 // unit.parent = parent;
198 } catch (NamingException
| IOException e
) {
199 throw new IllegalStateException("Cannot load user admin service from LDIF", e
);
203 public void destroy() {
204 // if (users == null || groups == null)
206 throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
217 public LdapEntry
doGetEntry(LdapName key
) throws NameNotFoundException
{
218 // if (groups.containsKey(key))
219 // return groups.get(key);
220 // if (users.containsKey(key))
221 // return users.get(key);
222 if (entries
.containsKey(key
))
223 return entries
.get(key
);
224 throw new NameNotFoundException(key
+ " not persisted");
228 public Attributes
doGetAttributes(LdapName name
) {
230 return doGetEntry(name
).getAttributes();
231 } catch (NameNotFoundException e
) {
232 throw new IllegalStateException(name
+ " doe not exist in " + getDirectory().getBaseDn(), e
);
237 public Boolean
entryExists(LdapName dn
) {
238 return entries
.containsKey(dn
);// || groups.containsKey(dn);
242 public List
<LdapEntry
> doGetEntries(LdapName searchBase
, String f
, boolean deep
) {
243 Objects
.requireNonNull(searchBase
);
244 ArrayList
<LdapEntry
> res
= new ArrayList
<>();
245 if (f
== null && deep
&& getDirectory().getBaseDn().equals(searchBase
)) {
246 // res.addAll(users.values());
247 // res.addAll(groups.values());
248 res
.addAll(entries
.values());
250 // filterRoles(users, searchBase, f, deep, res);
251 // filterRoles(groups, searchBase, f, deep, res);
252 filterRoles(entries
, searchBase
, f
, deep
, res
);
257 private void filterRoles(SortedMap
<LdapName
, ?
extends LdapEntry
> map
, LdapName searchBase
, String f
, boolean deep
,
258 List
<LdapEntry
> res
) {
259 // FIXME get rid of OSGi references
261 // TODO reduce map with search base ?
262 Filter filter
= f
!= null ? FrameworkUtil
.createFilter(f
) : null;
263 roles
: for (LdapEntry user
: map
.values()) {
264 LdapName dn
= user
.getDn();
265 if (dn
.startsWith(searchBase
)) {
266 if (!deep
&& dn
.size() != (searchBase
.size() + 1))
271 if (user
instanceof Role
) {
272 if (filter
.match(((Role
) user
).getProperties()))
278 } catch (InvalidSyntaxException e
) {
279 throw new IllegalArgumentException("Cannot create filter " + f
, e
);
285 public List
<LdapName
> getDirectGroups(LdapName dn
) {
286 List
<LdapName
> directGroups
= new ArrayList
<LdapName
>();
287 entries
: for (LdapName name
: entries
.keySet()) {
290 LdapEntry entry
= doGetEntry(name
);
291 if (AbstractLdapDirectory
.hasObjectClass(entry
.getAttributes(), getDirectory().getGroupObjectClass())) {
296 } catch (NameNotFoundException e
) {
297 throw new IllegalArgumentException("Group " + dn
+ " not found", e
);
299 if (group
.getReferences(getDirectory().getMemberAttributeId()).contains(dn
)) {
300 directGroups
.add(group
.getDn());
307 public void prepare(LdapEntryWorkingCopy wc
) {
309 for (LdapName dn
: wc
.getDeletedData().keySet()) {
310 if (entries
.containsKey(dn
))
312 // if (users.containsKey(dn))
314 // else if (groups.containsKey(dn))
315 // groups.remove(dn);
317 throw new IllegalStateException("User to delete not found " + dn
);
320 for (LdapName dn
: wc
.getNewData().keySet()) {
321 LdapEntry user
= (LdapEntry
) wc
.getNewData().get(dn
);
322 // if (users.containsKey(dn) || groups.containsKey(dn))
323 if (entries
.containsKey(dn
))
324 throw new IllegalStateException("User to create found " + dn
);
325 entries
.put(dn
, user
);
326 // else if (Role.USER == user.getType())
327 // users.put(dn, user);
328 // else if (Role.GROUP == user.getType())
329 // groups.put(dn, (DirectoryGroup) user);
331 // throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
334 for (LdapName dn
: wc
.getModifiedData().keySet()) {
335 Attributes modifiedAttrs
= wc
.getModifiedData().get(dn
);
338 user
= doGetEntry(dn
);
339 } catch (NameNotFoundException e
) {
340 throw new IllegalStateException("User to modify no found " + dn
, e
);
343 throw new IllegalStateException("User to modify no found " + dn
);
344 user
.publishAttributes(modifiedAttrs
);
349 public void commit(LdapEntryWorkingCopy wc
) {
354 public void rollback(LdapEntryWorkingCopy wc
) {
363 // public int getHierarchyChildCount() {
364 // return rootHierarchyUnits.size();
368 // public HierarchyUnit getHierarchyChild(int i) {
369 // return rootHierarchyUnits.get(i);
372 public HierarchyUnit
doGetHierarchyUnit(LdapName dn
) {
373 if (getDirectory().getBaseDn().equals(dn
))
374 return getDirectory();
375 return hierarchy
.get(dn
);
379 public Iterable
<HierarchyUnit
> doGetDirectHierarchyUnits(LdapName searchBase
, boolean functionalOnly
) {
380 List
<HierarchyUnit
> res
= new ArrayList
<>();
381 for (LdapName n
: hierarchy
.keySet()) {
382 if (n
.size() == searchBase
.size() + 1) {
383 if (n
.startsWith(searchBase
)) {
384 HierarchyUnit hu
= hierarchy
.get(n
);
385 if (functionalOnly
) {
386 if (hu
.isFunctional())
397 public void scope(LdifDao scoped
) {
398 scoped
.entries
= Collections
.unmodifiableNavigableMap(entries
);
402 // public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
403 // if (functionalOnly) {
404 // List<HierarchyUnit> res = new ArrayList<>();
405 // for (HierarchyUnit hu : rootHierarchyUnits) {
406 // if (hu.isFunctional())
412 // return rootHierarchyUnits;