Massive package refactoring
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / directory / ldap / LdifDao.java
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java
new file mode 100644 (file)
index 0000000..1f33898
--- /dev/null
@@ -0,0 +1,306 @@
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttrs.objectClass;
+import static org.argeo.api.acr.ldap.LdapObjs.inetOrgPerson;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapObjs;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+
+/** A user admin based on a LDIF files. */
+public class LdifDao extends AbstractLdapDirectoryDao {
+       private NavigableMap<LdapName, LdapEntry> entries = new TreeMap<>();
+       private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
+
+       private NavigableMap<LdapName, Attributes> values = new TreeMap<>();
+
+       public LdifDao(AbstractLdapDirectory directory) {
+               super(directory);
+       }
+
+       public void init() {
+               String uri = getDirectory().getUri();
+               if (uri == null)
+                       return;
+               try {
+                       URI u = new URI(uri);
+                       if (u.getScheme().equals("file")) {
+                               File file = new File(u);
+                               if (!file.exists())
+                                       return;
+                       }
+                       load(u.toURL().openStream());
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
+               }
+       }
+
+       public void save() {
+               if (getDirectory().getUri() == null)
+                       return; // ignore
+               if (getDirectory().isReadOnly())
+                       throw new IllegalStateException(
+                                       "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
+               try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
+                       save(out);
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
+               }
+       }
+
+       public void save(OutputStream out) throws IOException {
+               try {
+                       LdifWriter ldifWriter = new LdifWriter(out);
+                       for (LdapName name : hierarchy.keySet())
+                               ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
+                       for (LdapName name : entries.keySet())
+                               ldifWriter.writeEntry(name, entries.get(name).getAttributes());
+               } finally {
+                       out.close();
+               }
+       }
+
+       public void load(InputStream in) {
+               try {
+                       entries.clear();
+                       hierarchy.clear();
+
+                       LdifParser ldifParser = new LdifParser();
+                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
+                       for (LdapName key : allEntries.keySet()) {
+                               Attributes attributes = allEntries.get(key);
+                               // check for inconsistency
+                               Set<String> lowerCase = new HashSet<String>();
+                               NamingEnumeration<String> ids = attributes.getIDs();
+                               while (ids.hasMoreElements()) {
+                                       String id = ids.nextElement().toLowerCase();
+                                       if (lowerCase.contains(id))
+                                               throw new IllegalStateException(key + " has duplicate id " + id);
+                                       lowerCase.add(id);
+                               }
+
+                               values.put(key, attributes);
+
+                               // analyse object classes
+                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
+                               // System.out.println(key);
+                               objectClasses: while (objectClasses.hasMore()) {
+                                       String objectClass = objectClasses.next().toString();
+                                       // System.out.println(" " + objectClass);
+                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
+                                               entries.put(key, newUser(key));
+                                               break objectClasses;
+                                       } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
+                                               entries.put(key, newGroup(key));
+                                               break objectClasses;
+                                       } else if (objectClass.equalsIgnoreCase(LdapObjs.organizationalUnit.name())) {
+                                               // TODO skip if it does not contain groups or users
+                                               hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key));
+                                               break objectClasses;
+                                       }
+                               }
+                       }
+
+               } catch (NamingException | IOException e) {
+                       throw new IllegalStateException("Cannot load user admin service from LDIF", e);
+               }
+       }
+
+       public void destroy() {
+//             if (users == null || groups == null)
+               if (entries == null)
+                       throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
+//             users = null;
+//             groups = null;
+               entries = null;
+       }
+
+       /*
+        * USER ADMIN
+        */
+
+       @Override
+       public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
+               if (entries.containsKey(key))
+                       return entries.get(key);
+               throw new NameNotFoundException(key + " not persisted");
+       }
+
+       @Override
+       public Attributes doGetAttributes(LdapName name) {
+               if (!values.containsKey(name))
+                       throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn());
+               return values.get(name);
+       }
+
+       @Override
+       public boolean checkConnection() {
+               return true;
+       }
+
+       @Override
+       public boolean entryExists(LdapName dn) {
+               return entries.containsKey(dn);// || groups.containsKey(dn);
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               Objects.requireNonNull(searchBase);
+               ArrayList<LdapEntry> res = new ArrayList<>();
+               if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
+                       res.addAll(entries.values());
+               } else {
+                       filterRoles(entries, searchBase, f, deep, res);
+               }
+               return res;
+       }
+
+       private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
+                       List<LdapEntry> res) {
+               // FIXME get rid of OSGi references
+               try {
+                       // TODO reduce map with search base ?
+                       Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
+                       roles: for (LdapEntry user : map.values()) {
+                               LdapName dn = user.getDn();
+                               if (dn.startsWith(searchBase)) {
+                                       if (!deep && dn.size() != (searchBase.size() + 1))
+                                               continue roles;
+                                       if (filter == null)
+                                               res.add(user);
+                                       else {
+                                               if (user instanceof Role) {
+                                                       if (filter.match(((Role) user).getProperties()))
+                                                               res.add(user);
+                                               }
+                                       }
+                               }
+                       }
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot create filter " + f, e);
+               }
+
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               entries: for (LdapName name : entries.keySet()) {
+                       LdapEntry group;
+                       try {
+                               LdapEntry entry = doGetEntry(name);
+                               if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
+                                       group = entry;
+                               } else {
+                                       continue entries;
+                               }
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalArgumentException("Group " + dn + " not found", e);
+                       }
+                       if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
+                               directGroups.add(group.getDn());
+                       }
+               }
+               return directGroups;
+       }
+
+       @Override
+       public void prepare(LdapEntryWorkingCopy wc) {
+               // delete
+               for (LdapName dn : wc.getDeletedData().keySet()) {
+                       if (entries.containsKey(dn))
+                               entries.remove(dn);
+                       else
+                               throw new IllegalStateException("User to delete not found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
+                       if (entries.containsKey(dn))
+                               throw new IllegalStateException("User to create found " + dn);
+                       entries.put(dn, user);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+                       LdapEntry user;
+                       try {
+                               user = doGetEntry(dn);
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalStateException("User to modify no found " + dn, e);
+                       }
+                       if (user == null)
+                               throw new IllegalStateException("User to modify no found " + dn);
+                       user.publishAttributes(modifiedAttrs);
+               }
+       }
+
+       @Override
+       public void commit(LdapEntryWorkingCopy wc) {
+               save();
+       }
+
+       @Override
+       public void rollback(LdapEntryWorkingCopy wc) {
+               init();
+       }
+
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               if (getDirectory().getBaseDn().equals(dn))
+                       return getDirectory();
+               return hierarchy.get(dn);
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               List<HierarchyUnit> res = new ArrayList<>();
+               for (LdapName n : hierarchy.keySet()) {
+                       if (n.size() == searchBase.size() + 1) {
+                               if (n.startsWith(searchBase)) {
+                                       HierarchyUnit hu = hierarchy.get(n);
+                                       if (functionalOnly) {
+                                               if (hu.isFunctional())
+                                                       res.add(hu);
+                                       } else {
+                                               res.add(hu);
+                                       }
+                               }
+                       }
+               }
+               return res;
+       }
+
+       public void scope(LdifDao scoped) {
+               scoped.entries = Collections.unmodifiableNavigableMap(entries);
+       }
+}