]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java
JDK HTTP server authentication
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / directory / ldap / LdifDao.java
1 package org.argeo.util.directory.ldap;
2
3 import static org.argeo.util.naming.LdapAttrs.objectClass;
4 import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
5
6 import java.io.File;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.net.URI;
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;
19 import java.util.Set;
20 import java.util.SortedMap;
21 import java.util.TreeMap;
22
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;
28
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;
35
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<>();
41
42 private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
43 // private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
44
45 // public LdifUserAdmin(String uri, String baseDn) {
46 // this(fromUri(uri, baseDn), false);
47 // }
48
49 public LdifDao(AbstractLdapDirectory directory) {
50 super(directory);
51 }
52
53 // protected LdifUserAdmin(Hashtable<String, ?> properties, boolean scoped) {
54 // super( properties, scoped);
55 // }
56
57 // public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
58 // super(uri, properties, false);
59 // }
60
61 // @Override
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;
69 // if (pwd != null) {
70 // char[] password = DirectoryDigestUtils.bytesToChars(pwd);
71 // User directoryUser = (User) getRole(username);
72 // if (!directoryUser.hasCredential(null, password))
73 // throw new IllegalStateException("Invalid credentials");
74 // } else {
75 // throw new IllegalStateException("Password is required");
76 // }
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;
84 // }
85
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);
90 // return res;
91 // }
92
93 public void init() {
94
95 try {
96 URI u = new URI(getDirectory().getUri());
97 if (u.getScheme().equals("file")) {
98 File file = new File(u);
99 if (!file.exists())
100 return;
101 }
102 load(u.toURL().openStream());
103 } catch (IOException | URISyntaxException e) {
104 throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
105 }
106 }
107
108 public void save() {
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())))) {
115 save(out);
116 } catch (IOException | URISyntaxException e) {
117 throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
118 }
119 }
120
121 public void save(OutputStream out) throws IOException {
122 try {
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());
132 } finally {
133 out.close();
134 }
135 }
136
137 public void load(InputStream in) {
138 try {
139 // users.clear();
140 // groups.clear();
141 entries.clear();
142 hierarchy.clear();
143
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);
155 lowerCase.add(id);
156 }
157
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));
166 break objectClasses;
167 } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
168 entries.put(key, newGroup(key, attributes));
169 break objectClasses;
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));
180 break objectClasses;
181 }
182 }
183 }
184
185 // link hierarchy
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;
194 // }
195 // parent.children.add(unit);
196 // unit.parent = parent;
197 // }
198 } catch (NamingException | IOException e) {
199 throw new IllegalStateException("Cannot load user admin service from LDIF", e);
200 }
201 }
202
203 public void destroy() {
204 // if (users == null || groups == null)
205 if (entries == null)
206 throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
207 // users = null;
208 // groups = null;
209 entries = null;
210 }
211
212 /*
213 * USER ADMIN
214 */
215
216 @Override
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");
225 }
226
227 @Override
228 public Attributes doGetAttributes(LdapName name) {
229 try {
230 return doGetEntry(name).getAttributes();
231 } catch (NameNotFoundException e) {
232 throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn(), e);
233 }
234 }
235
236 @Override
237 public Boolean entryExists(LdapName dn) {
238 return entries.containsKey(dn);// || groups.containsKey(dn);
239 }
240
241 @Override
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());
249 } else {
250 // filterRoles(users, searchBase, f, deep, res);
251 // filterRoles(groups, searchBase, f, deep, res);
252 filterRoles(entries, searchBase, f, deep, res);
253 }
254 return res;
255 }
256
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
260 try {
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))
267 continue roles;
268 if (filter == null)
269 res.add(user);
270 else {
271 if (user instanceof Role) {
272 if (filter.match(((Role) user).getProperties()))
273 res.add(user);
274 }
275 }
276 }
277 }
278 } catch (InvalidSyntaxException e) {
279 throw new IllegalArgumentException("Cannot create filter " + f, e);
280 }
281
282 }
283
284 @Override
285 public List<LdapName> getDirectGroups(LdapName dn) {
286 List<LdapName> directGroups = new ArrayList<LdapName>();
287 entries: for (LdapName name : entries.keySet()) {
288 LdapEntry group;
289 try {
290 LdapEntry entry = doGetEntry(name);
291 if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
292 group = entry;
293 } else {
294 continue entries;
295 }
296 } catch (NameNotFoundException e) {
297 throw new IllegalArgumentException("Group " + dn + " not found", e);
298 }
299 if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
300 directGroups.add(group.getDn());
301 }
302 }
303 return directGroups;
304 }
305
306 @Override
307 public void prepare(LdapEntryWorkingCopy wc) {
308 // delete
309 for (LdapName dn : wc.getDeletedData().keySet()) {
310 if (entries.containsKey(dn))
311 entries.remove(dn);
312 // if (users.containsKey(dn))
313 // users.remove(dn);
314 // else if (groups.containsKey(dn))
315 // groups.remove(dn);
316 else
317 throw new IllegalStateException("User to delete not found " + dn);
318 }
319 // add
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);
330 // else
331 // throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
332 }
333 // modify
334 for (LdapName dn : wc.getModifiedData().keySet()) {
335 Attributes modifiedAttrs = wc.getModifiedData().get(dn);
336 LdapEntry user;
337 try {
338 user = doGetEntry(dn);
339 } catch (NameNotFoundException e) {
340 throw new IllegalStateException("User to modify no found " + dn, e);
341 }
342 if (user == null)
343 throw new IllegalStateException("User to modify no found " + dn);
344 user.publishAttributes(modifiedAttrs);
345 }
346 }
347
348 @Override
349 public void commit(LdapEntryWorkingCopy wc) {
350 save();
351 }
352
353 @Override
354 public void rollback(LdapEntryWorkingCopy wc) {
355 init();
356 }
357
358 /*
359 * HIERARCHY
360 */
361
362 // @Override
363 // public int getHierarchyChildCount() {
364 // return rootHierarchyUnits.size();
365 // }
366 //
367 // @Override
368 // public HierarchyUnit getHierarchyChild(int i) {
369 // return rootHierarchyUnits.get(i);
370 // }
371 @Override
372 public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
373 if (getDirectory().getBaseDn().equals(dn))
374 return getDirectory();
375 return hierarchy.get(dn);
376 }
377
378 @Override
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())
387 res.add(hu);
388 } else {
389 res.add(hu);
390 }
391 }
392 }
393 }
394 return res;
395 }
396
397 public void scope(LdifDao scoped) {
398 scoped.entries = Collections.unmodifiableNavigableMap(entries);
399 }
400
401 // @Override
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())
407 // res.add(hu);
408 // }
409 // return res;
410 //
411 // } else {
412 // return rootHierarchyUnits;
413 // }
414 // }
415
416 }