]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java
Disable OSGi configuration admin and LDIF-based deploy config.
[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.Dictionary;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
19 import java.util.NavigableMap;
20 import java.util.Objects;
21 import java.util.Set;
22 import java.util.SortedMap;
23 import java.util.TreeMap;
24
25 import javax.naming.NameNotFoundException;
26 import javax.naming.NamingEnumeration;
27 import javax.naming.NamingException;
28 import javax.naming.directory.Attributes;
29 import javax.naming.ldap.LdapName;
30
31 import org.argeo.util.directory.DirectoryConf;
32 import org.argeo.util.directory.HierarchyUnit;
33 import org.argeo.util.naming.LdapObjs;
34 import org.osgi.framework.Filter;
35 import org.osgi.framework.FrameworkUtil;
36 import org.osgi.framework.InvalidSyntaxException;
37 import org.osgi.service.useradmin.Role;
38
39 /** A user admin based on a LDIF files. */
40 public class LdifDao extends AbstractLdapDirectoryDao {
41 // private NavigableMap<LdapName, LdapEntry> users = new TreeMap<>();
42 // private NavigableMap<LdapName, LdapEntry> groups = new TreeMap<>();
43 private NavigableMap<LdapName, LdapEntry> entries = new TreeMap<>();
44
45 private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
46 // private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
47
48 // public LdifUserAdmin(String uri, String baseDn) {
49 // this(fromUri(uri, baseDn), false);
50 // }
51
52 public LdifDao(AbstractLdapDirectory directory) {
53 super(directory);
54 }
55
56 // protected LdifUserAdmin(Hashtable<String, ?> properties, boolean scoped) {
57 // super( properties, scoped);
58 // }
59
60 // public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
61 // super(uri, properties, false);
62 // }
63
64 // @Override
65 // protected AbstractUserDirectory scope(User user) {
66 // Dictionary<String, Object> credentials = user.getCredentials();
67 // String username = (String) credentials.get(SHARED_STATE_USERNAME);
68 // if (username == null)
69 // username = user.getName();
70 // Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
71 // byte[] pwd = (byte[]) pwdCred;
72 // if (pwd != null) {
73 // char[] password = DirectoryDigestUtils.bytesToChars(pwd);
74 // User directoryUser = (User) getRole(username);
75 // if (!directoryUser.hasCredential(null, password))
76 // throw new IllegalStateException("Invalid credentials");
77 // } else {
78 // throw new IllegalStateException("Password is required");
79 // }
80 // Dictionary<String, Object> properties = cloneProperties();
81 // properties.put(DirectoryConf.readOnly.name(), "true");
82 // LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
83 //// scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
84 //// scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
85 // scopedUserAdmin.entries = Collections.unmodifiableNavigableMap(entries);
86 // return scopedUserAdmin;
87 // }
88
89 private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
90 Hashtable<String, Object> res = new Hashtable<String, Object>();
91 res.put(DirectoryConf.uri.name(), uri);
92 res.put(DirectoryConf.baseDn.name(), baseDn);
93 return res;
94 }
95
96 public void init() {
97
98 try {
99 URI u = new URI(getDirectory().getUri());
100 if (u.getScheme().equals("file")) {
101 File file = new File(u);
102 if (!file.exists())
103 return;
104 }
105 load(u.toURL().openStream());
106 } catch (IOException | URISyntaxException e) {
107 throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
108 }
109 }
110
111 public void save() {
112 if (getDirectory().getUri() == null)
113 throw new IllegalStateException("Cannot save LDIF user admin: no URI is set");
114 if (getDirectory().isReadOnly())
115 throw new IllegalStateException(
116 "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
117 try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
118 save(out);
119 } catch (IOException | URISyntaxException e) {
120 throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
121 }
122 }
123
124 public void save(OutputStream out) throws IOException {
125 try {
126 LdifWriter ldifWriter = new LdifWriter(out);
127 for (LdapName name : hierarchy.keySet())
128 ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
129 // for (LdapName name : groups.keySet())
130 // ldifWriter.writeEntry(name, groups.get(name).getAttributes());
131 // for (LdapName name : users.keySet())
132 // ldifWriter.writeEntry(name, users.get(name).getAttributes());
133 for (LdapName name : entries.keySet())
134 ldifWriter.writeEntry(name, entries.get(name).getAttributes());
135 } finally {
136 out.close();
137 }
138 }
139
140 public void load(InputStream in) {
141 try {
142 // users.clear();
143 // groups.clear();
144 entries.clear();
145 hierarchy.clear();
146
147 LdifParser ldifParser = new LdifParser();
148 SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
149 for (LdapName key : allEntries.keySet()) {
150 Attributes attributes = allEntries.get(key);
151 // check for inconsistency
152 Set<String> lowerCase = new HashSet<String>();
153 NamingEnumeration<String> ids = attributes.getIDs();
154 while (ids.hasMoreElements()) {
155 String id = ids.nextElement().toLowerCase();
156 if (lowerCase.contains(id))
157 throw new IllegalStateException(key + " has duplicate id " + id);
158 lowerCase.add(id);
159 }
160
161 // analyse object classes
162 NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
163 // System.out.println(key);
164 objectClasses: while (objectClasses.hasMore()) {
165 String objectClass = objectClasses.next().toString();
166 // System.out.println(" " + objectClass);
167 if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
168 entries.put(key, newUser(key, attributes));
169 break objectClasses;
170 } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
171 entries.put(key, newGroup(key, attributes));
172 break objectClasses;
173 // } else if (objectClass.equalsIgnoreCase(LdapObjs.organization.name())) {
174 // // we only consider organizations which are not groups
175 // hierarchy.put(key, new LdifHierarchyUnit(this, key, HierarchyUnit.ORGANIZATION, attributes));
176 // break objectClasses;
177 } else if (objectClass.equalsIgnoreCase(LdapObjs.organizationalUnit.name())) {
178 // String name = key.getRdn(key.size() - 1).toStrindirectoryDaog();
179 // if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
180 // break objectClasses; // skip
181 // TODO skip if it does not contain groups or users
182 hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key, attributes));
183 break objectClasses;
184 }
185 }
186 }
187
188 // link hierarchy
189 // hierachyUnits: for (LdapName dn : hierarchy.keySet()) {
190 // LdifHierarchyUnit unit = hierarchy.get(dn);
191 // LdapName parentDn = (LdapName) dn.getPrefix(dn.size() - 1);
192 // LdifHierarchyUnit parent = hierarchy.get(parentDn);
193 // if (parent == null) {
194 // rootHierarchyUnits.add(unit);
195 // unit.parent = null;
196 // continue hierachyUnits;
197 // }
198 // parent.children.add(unit);
199 // unit.parent = parent;
200 // }
201 } catch (NamingException | IOException e) {
202 throw new IllegalStateException("Cannot load user admin service from LDIF", e);
203 }
204 }
205
206 public void destroy() {
207 // if (users == null || groups == null)
208 if (entries == null)
209 throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
210 // users = null;
211 // groups = null;
212 entries = null;
213 }
214
215 /*
216 * USER ADMIN
217 */
218
219 @Override
220 public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
221 // if (groups.containsKey(key))
222 // return groups.get(key);
223 // if (users.containsKey(key))
224 // return users.get(key);
225 if (entries.containsKey(key))
226 return entries.get(key);
227 throw new NameNotFoundException(key + " not persisted");
228 }
229
230 @Override
231 public Boolean entryExists(LdapName dn) {
232 return entries.containsKey(dn);// || groups.containsKey(dn);
233 }
234
235 @Override
236 public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
237 Objects.requireNonNull(searchBase);
238 ArrayList<LdapEntry> res = new ArrayList<>();
239 if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
240 // res.addAll(users.values());
241 // res.addAll(groups.values());
242 res.addAll(entries.values());
243 } else {
244 // filterRoles(users, searchBase, f, deep, res);
245 // filterRoles(groups, searchBase, f, deep, res);
246 filterRoles(entries, searchBase, f, deep, res);
247 }
248 return res;
249 }
250
251 private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
252 List<LdapEntry> res) {
253 // FIXME get rid of OSGi references
254 try {
255 // TODO reduce map with search base ?
256 Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
257 roles: for (LdapEntry user : map.values()) {
258 LdapName dn = user.getDn();
259 if (dn.startsWith(searchBase)) {
260 if (!deep && dn.size() != (searchBase.size() + 1))
261 continue roles;
262 if (filter == null)
263 res.add(user);
264 else {
265 if (user instanceof Role) {
266 if (filter.match(((Role) user).getProperties()))
267 res.add(user);
268 }
269 }
270 }
271 }
272 } catch (InvalidSyntaxException e) {
273 throw new IllegalArgumentException("Cannot create filter " + f, e);
274 }
275
276 }
277
278 @Override
279 public List<LdapName> getDirectGroups(LdapName dn) {
280 List<LdapName> directGroups = new ArrayList<LdapName>();
281 entries: for (LdapName name : entries.keySet()) {
282 LdapEntry group;
283 try {
284 LdapEntry entry = doGetEntry(name);
285 if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
286 group = entry;
287 } else {
288 continue entries;
289 }
290 } catch (NameNotFoundException e) {
291 throw new IllegalArgumentException("Group " + dn + " not found", e);
292 }
293 if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
294 directGroups.add(group.getDn());
295 }
296 }
297 return directGroups;
298 }
299
300 @Override
301 public void prepare(LdapEntryWorkingCopy wc) {
302 // delete
303 for (LdapName dn : wc.getDeletedData().keySet()) {
304 if (entries.containsKey(dn))
305 entries.remove(dn);
306 // if (users.containsKey(dn))
307 // users.remove(dn);
308 // else if (groups.containsKey(dn))
309 // groups.remove(dn);
310 else
311 throw new IllegalStateException("User to delete not found " + dn);
312 }
313 // add
314 for (LdapName dn : wc.getNewData().keySet()) {
315 LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
316 // if (users.containsKey(dn) || groups.containsKey(dn))
317 if (entries.containsKey(dn))
318 throw new IllegalStateException("User to create found " + dn);
319 entries.put(dn, user);
320 // else if (Role.USER == user.getType())
321 // users.put(dn, user);
322 // else if (Role.GROUP == user.getType())
323 // groups.put(dn, (DirectoryGroup) user);
324 // else
325 // throw new IllegalStateException("Unsupported role type " + user.getType() + " for new user " + dn);
326 }
327 // modify
328 for (LdapName dn : wc.getModifiedData().keySet()) {
329 Attributes modifiedAttrs = wc.getModifiedData().get(dn);
330 LdapEntry user;
331 try {
332 user = doGetEntry(dn);
333 } catch (NameNotFoundException e) {
334 throw new IllegalStateException("User to modify no found " + dn, e);
335 }
336 if (user == null)
337 throw new IllegalStateException("User to modify no found " + dn);
338 user.publishAttributes(modifiedAttrs);
339 }
340 }
341
342 @Override
343 public void commit(LdapEntryWorkingCopy wc) {
344 save();
345 }
346
347 @Override
348 public void rollback(LdapEntryWorkingCopy wc) {
349 init();
350 }
351
352 /*
353 * HIERARCHY
354 */
355
356 // @Override
357 // public int getHierarchyChildCount() {
358 // return rootHierarchyUnits.size();
359 // }
360 //
361 // @Override
362 // public HierarchyUnit getHierarchyChild(int i) {
363 // return rootHierarchyUnits.get(i);
364 // }
365 @Override
366 public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
367 if (getDirectory().getBaseDn().equals(dn))
368 return getDirectory();
369 return hierarchy.get(dn);
370 }
371
372 @Override
373 public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
374 List<HierarchyUnit> res = new ArrayList<>();
375 for (LdapName n : hierarchy.keySet()) {
376 if (n.size() == searchBase.size() + 1) {
377 if (n.startsWith(searchBase)) {
378 HierarchyUnit hu = hierarchy.get(n);
379 if (functionalOnly) {
380 if (hu.isFunctional())
381 res.add(hu);
382 } else {
383 res.add(hu);
384 }
385 }
386 }
387 }
388 return res;
389 }
390
391 public void scope(LdifDao scoped) {
392 scoped.entries = Collections.unmodifiableNavigableMap(entries);
393 }
394
395 // @Override
396 // public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
397 // if (functionalOnly) {
398 // List<HierarchyUnit> res = new ArrayList<>();
399 // for (HierarchyUnit hu : rootHierarchyUnits) {
400 // if (hu.isFunctional())
401 // res.add(hu);
402 // }
403 // return res;
404 //
405 // } else {
406 // return rootHierarchyUnits;
407 // }
408 // }
409
410 }