]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/directory/ldap/LdifDao.java
Working read-only WebDav server
[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> entries = new TreeMap<>();
39 private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
40
41 private NavigableMap<LdapName, Attributes> values = new TreeMap<>();
42
43 public LdifDao(AbstractLdapDirectory directory) {
44 super(directory);
45 }
46
47 public void init() {
48 String uri = getDirectory().getUri();
49 if (uri == null)
50 return;
51 try {
52 URI u = new URI(uri);
53 if (u.getScheme().equals("file")) {
54 File file = new File(u);
55 if (!file.exists())
56 return;
57 }
58 load(u.toURL().openStream());
59 } catch (IOException | URISyntaxException e) {
60 throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
61 }
62 }
63
64 public void save() {
65 if (getDirectory().getUri() == null)
66 return; // ignore
67 if (getDirectory().isReadOnly())
68 throw new IllegalStateException(
69 "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
70 try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
71 save(out);
72 } catch (IOException | URISyntaxException e) {
73 throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
74 }
75 }
76
77 public void save(OutputStream out) throws IOException {
78 try {
79 LdifWriter ldifWriter = new LdifWriter(out);
80 for (LdapName name : hierarchy.keySet())
81 ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
82 for (LdapName name : entries.keySet())
83 ldifWriter.writeEntry(name, entries.get(name).getAttributes());
84 } finally {
85 out.close();
86 }
87 }
88
89 public void load(InputStream in) {
90 try {
91 entries.clear();
92 hierarchy.clear();
93
94 LdifParser ldifParser = new LdifParser();
95 SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
96 for (LdapName key : allEntries.keySet()) {
97 Attributes attributes = allEntries.get(key);
98 // check for inconsistency
99 Set<String> lowerCase = new HashSet<String>();
100 NamingEnumeration<String> ids = attributes.getIDs();
101 while (ids.hasMoreElements()) {
102 String id = ids.nextElement().toLowerCase();
103 if (lowerCase.contains(id))
104 throw new IllegalStateException(key + " has duplicate id " + id);
105 lowerCase.add(id);
106 }
107
108 values.put(key, attributes);
109
110 // analyse object classes
111 NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
112 // System.out.println(key);
113 objectClasses: while (objectClasses.hasMore()) {
114 String objectClass = objectClasses.next().toString();
115 // System.out.println(" " + objectClass);
116 if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
117 entries.put(key, newUser(key));
118 break objectClasses;
119 } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
120 entries.put(key, newGroup(key));
121 break objectClasses;
122 } else if (objectClass.equalsIgnoreCase(LdapObjs.organizationalUnit.name())) {
123 // TODO skip if it does not contain groups or users
124 hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key));
125 break objectClasses;
126 }
127 }
128 }
129
130 } catch (NamingException | IOException e) {
131 throw new IllegalStateException("Cannot load user admin service from LDIF", e);
132 }
133 }
134
135 public void destroy() {
136 // if (users == null || groups == null)
137 if (entries == null)
138 throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
139 // users = null;
140 // groups = null;
141 entries = null;
142 }
143
144 /*
145 * USER ADMIN
146 */
147
148 @Override
149 public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
150 if (entries.containsKey(key))
151 return entries.get(key);
152 throw new NameNotFoundException(key + " not persisted");
153 }
154
155 @Override
156 public Attributes doGetAttributes(LdapName name) {
157 if (!values.containsKey(name))
158 throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn());
159 return values.get(name);
160 }
161
162 @Override
163 public Boolean entryExists(LdapName dn) {
164 return entries.containsKey(dn);// || groups.containsKey(dn);
165 }
166
167 @Override
168 public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
169 Objects.requireNonNull(searchBase);
170 ArrayList<LdapEntry> res = new ArrayList<>();
171 if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
172 res.addAll(entries.values());
173 } else {
174 filterRoles(entries, searchBase, f, deep, res);
175 }
176 return res;
177 }
178
179 private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
180 List<LdapEntry> res) {
181 // FIXME get rid of OSGi references
182 try {
183 // TODO reduce map with search base ?
184 Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
185 roles: for (LdapEntry user : map.values()) {
186 LdapName dn = user.getDn();
187 if (dn.startsWith(searchBase)) {
188 if (!deep && dn.size() != (searchBase.size() + 1))
189 continue roles;
190 if (filter == null)
191 res.add(user);
192 else {
193 if (user instanceof Role) {
194 if (filter.match(((Role) user).getProperties()))
195 res.add(user);
196 }
197 }
198 }
199 }
200 } catch (InvalidSyntaxException e) {
201 throw new IllegalArgumentException("Cannot create filter " + f, e);
202 }
203
204 }
205
206 @Override
207 public List<LdapName> getDirectGroups(LdapName dn) {
208 List<LdapName> directGroups = new ArrayList<LdapName>();
209 entries: for (LdapName name : entries.keySet()) {
210 LdapEntry group;
211 try {
212 LdapEntry entry = doGetEntry(name);
213 if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
214 group = entry;
215 } else {
216 continue entries;
217 }
218 } catch (NameNotFoundException e) {
219 throw new IllegalArgumentException("Group " + dn + " not found", e);
220 }
221 if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
222 directGroups.add(group.getDn());
223 }
224 }
225 return directGroups;
226 }
227
228 @Override
229 public void prepare(LdapEntryWorkingCopy wc) {
230 // delete
231 for (LdapName dn : wc.getDeletedData().keySet()) {
232 if (entries.containsKey(dn))
233 entries.remove(dn);
234 else
235 throw new IllegalStateException("User to delete not found " + dn);
236 }
237 // add
238 for (LdapName dn : wc.getNewData().keySet()) {
239 LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
240 if (entries.containsKey(dn))
241 throw new IllegalStateException("User to create found " + dn);
242 entries.put(dn, user);
243 }
244 // modify
245 for (LdapName dn : wc.getModifiedData().keySet()) {
246 Attributes modifiedAttrs = wc.getModifiedData().get(dn);
247 LdapEntry user;
248 try {
249 user = doGetEntry(dn);
250 } catch (NameNotFoundException e) {
251 throw new IllegalStateException("User to modify no found " + dn, e);
252 }
253 if (user == null)
254 throw new IllegalStateException("User to modify no found " + dn);
255 user.publishAttributes(modifiedAttrs);
256 }
257 }
258
259 @Override
260 public void commit(LdapEntryWorkingCopy wc) {
261 save();
262 }
263
264 @Override
265 public void rollback(LdapEntryWorkingCopy wc) {
266 init();
267 }
268
269 /*
270 * HIERARCHY
271 */
272 @Override
273 public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
274 if (getDirectory().getBaseDn().equals(dn))
275 return getDirectory();
276 return hierarchy.get(dn);
277 }
278
279 @Override
280 public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
281 List<HierarchyUnit> res = new ArrayList<>();
282 for (LdapName n : hierarchy.keySet()) {
283 if (n.size() == searchBase.size() + 1) {
284 if (n.startsWith(searchBase)) {
285 HierarchyUnit hu = hierarchy.get(n);
286 if (functionalOnly) {
287 if (hu.isFunctional())
288 res.add(hu);
289 } else {
290 res.add(hu);
291 }
292 }
293 }
294 }
295 return res;
296 }
297
298 public void scope(LdifDao scoped) {
299 scoped.entries = Collections.unmodifiableNavigableMap(entries);
300 }
301 }