]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java
Improve ACR attribute typing.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / directory / ldap / LdifDao.java
1 package org.argeo.cms.directory.ldap;
2
3 import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
4 import static org.argeo.api.acr.ldap.LdapObj.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.api.acr.ldap.LdapObj;
30 import org.argeo.api.cms.directory.HierarchyUnit;
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(LdapObj.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 checkConnection() {
164 return true;
165 }
166
167 @Override
168 public boolean entryExists(LdapName dn) {
169 return entries.containsKey(dn);// || groups.containsKey(dn);
170 }
171
172 @Override
173 public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
174 Objects.requireNonNull(searchBase);
175 ArrayList<LdapEntry> res = new ArrayList<>();
176 if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
177 res.addAll(entries.values());
178 } else {
179 filterRoles(entries, searchBase, f, deep, res);
180 }
181 return res;
182 }
183
184 private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
185 List<LdapEntry> res) {
186 // FIXME get rid of OSGi references
187 try {
188 // TODO reduce map with search base ?
189 Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
190 roles: for (LdapEntry user : map.values()) {
191 LdapName dn = user.getDn();
192 if (dn.startsWith(searchBase)) {
193 if (!deep && dn.size() != (searchBase.size() + 1))
194 continue roles;
195 if (filter == null)
196 res.add(user);
197 else {
198 if (user instanceof Role) {
199 if (filter.match(((Role) user).getProperties()))
200 res.add(user);
201 }
202 }
203 }
204 }
205 } catch (InvalidSyntaxException e) {
206 throw new IllegalArgumentException("Cannot create filter " + f, e);
207 }
208
209 }
210
211 @Override
212 public List<LdapName> getDirectGroups(LdapName dn) {
213 List<LdapName> directGroups = new ArrayList<LdapName>();
214 entries: for (LdapName name : entries.keySet()) {
215 LdapEntry group;
216 try {
217 LdapEntry entry = doGetEntry(name);
218 if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
219 group = entry;
220 } else {
221 continue entries;
222 }
223 } catch (NameNotFoundException e) {
224 throw new IllegalArgumentException("Group " + dn + " not found", e);
225 }
226 if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
227 directGroups.add(group.getDn());
228 }
229 }
230 return directGroups;
231 }
232
233 @Override
234 public void prepare(LdapEntryWorkingCopy wc) {
235 // delete
236 for (LdapName dn : wc.getDeletedData().keySet()) {
237 if (entries.containsKey(dn))
238 entries.remove(dn);
239 else
240 throw new IllegalStateException("User to delete not found " + dn);
241 }
242 // add
243 for (LdapName dn : wc.getNewData().keySet()) {
244 LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
245 if (entries.containsKey(dn))
246 throw new IllegalStateException("User to create found " + dn);
247 entries.put(dn, user);
248 }
249 // modify
250 for (LdapName dn : wc.getModifiedData().keySet()) {
251 Attributes modifiedAttrs = wc.getModifiedData().get(dn);
252 LdapEntry user;
253 try {
254 user = doGetEntry(dn);
255 } catch (NameNotFoundException e) {
256 throw new IllegalStateException("User to modify no found " + dn, e);
257 }
258 if (user == null)
259 throw new IllegalStateException("User to modify no found " + dn);
260 user.publishAttributes(modifiedAttrs);
261 }
262 }
263
264 @Override
265 public void commit(LdapEntryWorkingCopy wc) {
266 save();
267 }
268
269 @Override
270 public void rollback(LdapEntryWorkingCopy wc) {
271 init();
272 }
273
274 /*
275 * HIERARCHY
276 */
277 @Override
278 public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
279 if (getDirectory().getBaseDn().equals(dn))
280 return getDirectory();
281 return hierarchy.get(dn);
282 }
283
284 @Override
285 public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
286 List<HierarchyUnit> res = new ArrayList<>();
287 for (LdapName n : hierarchy.keySet()) {
288 if (n.size() == searchBase.size() + 1) {
289 if (n.startsWith(searchBase)) {
290 HierarchyUnit hu = hierarchy.get(n);
291 if (functionalOnly) {
292 if (hu.isFunctional())
293 res.add(hu);
294 } else {
295 res.add(hu);
296 }
297 }
298 }
299 }
300 return res;
301 }
302
303 public void scope(LdifDao scoped) {
304 scoped.entries = Collections.unmodifiableNavigableMap(entries);
305 }
306 }