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