X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.util%2Fsrc%2Forg%2Fargeo%2Futil%2Fdirectory%2Fldap%2FAbstractLdapDirectory.java;h=54d9776b5fd15106bf6de8b560ef81f863f1d470;hb=285c23f26c4d634cd139d393ebcb708187d5e960;hp=27f9c55e3db314e1686694e7216cc1cfece150cf;hpb=e2ffdf6872592aa22d0de2b0ec69ee4eca698c45;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java index 27f9c55e3..54d9776b5 100644 --- a/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java +++ b/org.argeo.util/src/org/argeo/util/directory/ldap/AbstractLdapDirectory.java @@ -19,30 +19,31 @@ import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import javax.transaction.xa.XAResource; +import org.argeo.osgi.useradmin.OsUserDirectory; import org.argeo.util.directory.Directory; import org.argeo.util.directory.DirectoryConf; import org.argeo.util.directory.HierarchyUnit; import org.argeo.util.naming.LdapAttrs; import org.argeo.util.naming.LdapObjs; import org.argeo.util.transaction.WorkControl; -import org.argeo.util.transaction.WorkingCopyProcessor; import org.argeo.util.transaction.WorkingCopyXaResource; import org.argeo.util.transaction.XAResourceProvider; -import org.osgi.framework.Filter; -public abstract class AbstractLdapDirectory - implements Directory, WorkingCopyProcessor, XAResourceProvider { +/** A {@link Directory} based either on LDAP or LDIF. */ +public abstract class AbstractLdapDirectory implements Directory, XAResourceProvider { protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name"; protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password"; - protected final LdapName baseDn; - protected final Hashtable properties; + private final LdapName baseDn; + private final Hashtable configProperties; private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn; private final String userObjectClass, groupObjectClass; + private String memberAttributeId = "member"; private final boolean readOnly; private final boolean disabled; @@ -52,40 +53,46 @@ public abstract class AbstractLdapDirectory private final boolean scoped; - private String memberAttributeId = "member"; private List credentialAttributeIds = Arrays .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() }); private WorkControl transactionControl; - private WorkingCopyXaResource xaResource = new WorkingCopyXaResource<>(this); + private WorkingCopyXaResource xaResource; + + private LdapDirectoryDao directoryDao; public AbstractLdapDirectory(URI uriArg, Dictionary props, boolean scoped) { - this.properties = new Hashtable(); + this.configProperties = new Hashtable(); for (Enumeration keys = props.keys(); keys.hasMoreElements();) { String key = keys.nextElement(); - properties.put(key, props.get(key)); + configProperties.put(key, props.get(key)); } - baseDn = toLdapName(DirectoryConf.baseDn.getValue(properties)); + + String baseDnStr = DirectoryConf.baseDn.getValue(configProperties); + if (baseDnStr == null) + throw new IllegalArgumentException("Base DN must be specified: " + configProperties); + baseDn = toLdapName(baseDnStr); this.scoped = scoped; if (uriArg != null) { uri = uriArg.toString(); // uri from properties is ignored } else { - String uriStr = DirectoryConf.uri.getValue(properties); + String uriStr = DirectoryConf.uri.getValue(configProperties); if (uriStr == null) uri = null; else uri = uriStr; } - forcedPassword = DirectoryConf.forcedPassword.getValue(properties); + forcedPassword = DirectoryConf.forcedPassword.getValue(configProperties); + + userObjectClass = DirectoryConf.userObjectClass.getValue(configProperties); + groupObjectClass = DirectoryConf.groupObjectClass.getValue(configProperties); - userObjectClass = DirectoryConf.userObjectClass.getValue(properties); - String userBase = DirectoryConf.userBase.getValue(properties); - groupObjectClass = DirectoryConf.groupObjectClass.getValue(properties); - String groupBase = DirectoryConf.groupBase.getValue(properties); - String systemRoleBase = DirectoryConf.systemRoleBase.getValue(properties); + String userBase = DirectoryConf.userBase.getValue(configProperties); + String groupBase = DirectoryConf.groupBase.getValue(configProperties); + String systemRoleBase = DirectoryConf.systemRoleBase.getValue(configProperties); try { // baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties)); userBaseRdn = new Rdn(userBase); @@ -94,39 +101,69 @@ public abstract class AbstractLdapDirectory // groupBaseDn = new LdapName(groupBase + "," + baseDn); systemRoleBaseRdn = new Rdn(systemRoleBase); } catch (InvalidNameException e) { - throw new IllegalArgumentException("Badly formated base DN " + DirectoryConf.baseDn.getValue(properties), - e); + throw new IllegalArgumentException( + "Badly formated base DN " + DirectoryConf.baseDn.getValue(configProperties), e); } // read only - String readOnlyStr = DirectoryConf.readOnly.getValue(properties); + String readOnlyStr = DirectoryConf.readOnly.getValue(configProperties); if (readOnlyStr == null) { readOnly = readOnlyDefault(uri); - properties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly)); + configProperties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly)); } else readOnly = Boolean.parseBoolean(readOnlyStr); // disabled - String disabledStr = DirectoryConf.disabled.getValue(properties); + String disabledStr = DirectoryConf.disabled.getValue(configProperties); if (disabledStr != null) disabled = Boolean.parseBoolean(disabledStr); else disabled = false; + if (!getRealm().isEmpty()) { + // IPA multiple LDAP causes URI parsing to fail + // TODO manage generic redundant LDAP case + directoryDao = new LdapDao(this); + } else { + if (uri != null) { + URI u = URI.create(uri); + if (DirectoryConf.SCHEME_LDAP.equals(u.getScheme()) + || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) { + directoryDao = new LdapDao(this); + } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) { + directoryDao = new LdifDao(this); + } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) { + directoryDao = new OsUserDirectory(this); + // singleUser = true; + } else { + throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); + } + } else { + // in memory + directoryDao = new LdifDao(this); + } + } + if (directoryDao != null) + xaResource = new WorkingCopyXaResource<>(directoryDao); } /* - * ABSTRACT METHODS + * INITIALISATION */ - public abstract HierarchyUnit doGetHierarchyUnit(LdapName dn); - - public abstract Iterable doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly); + public void init() { + getDirectoryDao().init(); + } - protected abstract Boolean daoHasEntry(LdapName dn); + public void destroy() { + getDirectoryDao().destroy(); + } - protected abstract LdapEntry daoGetEntry(LdapName key) throws NameNotFoundException; + /* + * CREATION + */ + protected abstract LdapEntry newUser(LdapName name); - protected abstract List doGetEntries(LdapName searchBase, Filter f, boolean deep); + protected abstract LdapEntry newGroup(LdapName name); /* * EDITION @@ -162,9 +199,86 @@ public abstract class AbstractLdapDirectory return xaResource; } - @Override - public LdapEntryWorkingCopy newWorkingCopy() { - return new LdapEntryWorkingCopy(); + public boolean removeEntry(LdapName dn) { + checkEdit(); + LdapEntryWorkingCopy wc = getWorkingCopy(); + boolean actuallyDeleted; + if (getDirectoryDao().entryExists(dn) || wc.getNewData().containsKey(dn)) { + LdapEntry user = doGetRole(dn); + wc.getDeletedData().put(dn, user); + actuallyDeleted = true; + } else {// just removing from groups (e.g. system roles) + actuallyDeleted = false; + } + for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) { + LdapEntry group = doGetRole(groupDn); + group.getAttributes().get(getMemberAttributeId()).remove(dn.toString()); + } + return actuallyDeleted; + } + + /* + * RETRIEVAL + */ + + protected LdapEntry doGetRole(LdapName dn) { + LdapEntryWorkingCopy wc = getWorkingCopy(); + LdapEntry user; + try { + user = getDirectoryDao().doGetEntry(dn); + } catch (NameNotFoundException e) { + user = null; + } + if (wc != null) { + if (user == null && wc.getNewData().containsKey(dn)) + user = wc.getNewData().get(dn); + else if (wc.getDeletedData().containsKey(dn)) + user = null; + } + return user; + } + + protected void collectGroups(LdapEntry user, List allRoles) { + Attributes attrs = user.getAttributes(); + // TODO centralize attribute name + Attribute memberOf = attrs.get(LdapAttrs.memberOf.name()); + // if user belongs to this directory, we only check memberOf + if (memberOf != null && user.getDn().startsWith(getBaseDn())) { + try { + NamingEnumeration values = memberOf.getAll(); + while (values.hasMore()) { + Object value = values.next(); + LdapName groupDn = new LdapName(value.toString()); + LdapEntry group = doGetRole(groupDn); + if (group != null) { + allRoles.add(group); + } else { + // user doesn't have the right to retrieve role, but we know it exists + // otherwise memberOf would not work +// Attributes a = new BasicAttributes(); +// a.put(LdapNameUtils.getLastRdn(groupDn).getType(), +// LdapNameUtils.getLastRdn(groupDn).getValue()); +// a.put(LdapAttrs.objectClass.name(), LdapObjs.groupOfNames.name()); + group = newGroup(groupDn); + allRoles.add(group); + } + } + } catch (NamingException e) { + throw new IllegalStateException("Cannot get memberOf groups for " + user, e); + } + } else { + directGroups: for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) { + LdapEntry group = doGetRole(groupDn); + if (group != null) { + if (allRoles.contains(group)) { + // important in order to avoi loops + continue directGroups; + } + allRoles.add(group); + collectGroups(group, allRoles); + } + } + } } /* @@ -173,12 +287,51 @@ public abstract class AbstractLdapDirectory @Override public HierarchyUnit getHierarchyUnit(String path) { LdapName dn = pathToName(path); - return doGetHierarchyUnit(dn); + return directoryDao.doGetHierarchyUnit(dn); } @Override public Iterable getDirectHierarchyUnits(boolean functionalOnly) { - return doGetDirectHierarchyUnits(baseDn, functionalOnly); + return directoryDao.doGetDirectHierarchyUnits(baseDn, functionalOnly); + } + + @Override + public String getHierarchyUnitName() { + return getName(); + } + + @Override + public HierarchyUnit getParent() { + return null; + } + + @Override + public boolean isFunctional() { + return true; + } + + @Override + public Directory getDirectory() { + return this; + } + + @Override + public HierarchyUnit createHierarchyUnit(String path) { + checkEdit(); + LdapEntryWorkingCopy wc = getWorkingCopy(); + LdapName dn = pathToName(path); + if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn)) + || wc.getNewData().containsKey(dn)) + throw new IllegalArgumentException("Already a hierarchy unit " + path); + BasicAttributes attrs = new BasicAttributes(true); + attrs.put(LdapAttrs.objectClass.name(), LdapObjs.organizationalUnit.name()); + Rdn nameRdn = dn.getRdn(dn.size() - 1); + // TODO deal with multiple attr RDN + attrs.put(nameRdn.getType(), nameRdn.getValue()); + wc.getModifiedData().put(dn, attrs); + LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn); + wc.getNewData().put(dn, newHierarchyUnit); + return newHierarchyUnit; } /* @@ -186,7 +339,7 @@ public abstract class AbstractLdapDirectory */ @Override - public String getContext() { + public String getBase() { return getBaseDn().toString(); } @@ -214,9 +367,11 @@ public abstract class AbstractLdapDirectory LdapName name = (LdapName) getBaseDn().clone(); String[] segments = path.split("/"); Rdn parentRdn = null; - for (String segment : segments) { + // segments[0] is the directory itself + for (int i = 0; i < segments.length; i++) { + String segment = segments[i]; // TODO make attr names configurable ? - String attr = LdapAttrs.ou.name(); + String attr = path.startsWith("accounts/")/* IPA */ ? LdapAttrs.cn.name() : LdapAttrs.ou.name(); if (parentRdn != null) { if (getUserBaseRdn().equals(parentRdn)) attr = LdapAttrs.uid.name(); @@ -239,20 +394,27 @@ public abstract class AbstractLdapDirectory /* * UTILITIES */ + protected boolean isExternal(LdapName name) { + return !name.startsWith(baseDn); + } protected static boolean hasObjectClass(Attributes attrs, LdapObjs objectClass) { + return hasObjectClass(attrs, objectClass.name()); + } + + protected static boolean hasObjectClass(Attributes attrs, String objectClass) { try { Attribute attr = attrs.get(LdapAttrs.objectClass.name()); NamingEnumeration en = attr.getAll(); while (en.hasMore()) { String v = en.next().toString(); - if (v.equalsIgnoreCase(objectClass.name())) + if (v.equalsIgnoreCase(objectClass)) return true; } return false; } catch (NamingException e) { - throw new IllegalStateException("Cannot search for objectClass " + objectClass.name(), e); + throw new IllegalStateException("Cannot search for objectClass " + objectClass, e); } } @@ -283,18 +445,33 @@ public abstract class AbstractLdapDirectory return true;// read only by default } + /* + * AS AN ENTRY + */ + public LdapEntry asLdapEntry() { + try { + return directoryDao.doGetEntry(baseDn); + } catch (NameNotFoundException e) { + throw new IllegalStateException("Cannot get " + baseDn + " entry", e); + } + } + + public Dictionary getProperties() { + return asLdapEntry().getProperties(); + } + /* * ACCESSORS */ @Override public Optional getRealm() { - Object realm = getProperties().get(DirectoryConf.realm.name()); + Object realm = configProperties.get(DirectoryConf.realm.name()); if (realm == null) return Optional.empty(); return Optional.of(realm.toString()); } - protected LdapName getBaseDn() { + public LdapName getBaseDn() { return (LdapName) baseDn.clone(); } @@ -306,23 +483,10 @@ public abstract class AbstractLdapDirectory return disabled; } - /** dn can be null, in that case a default should be returned. */ - public String getUserObjectClass() { - return userObjectClass; - } - public Rdn getUserBaseRdn() { return userBaseRdn; } - protected String newUserObjectClass(LdapName dn) { - return getUserObjectClass(); - } - - public String getGroupObjectClass() { - return groupObjectClass; - } - public Rdn getGroupBaseRdn() { return groupBaseRdn; } @@ -331,12 +495,12 @@ public abstract class AbstractLdapDirectory return systemRoleBaseRdn; } - public Dictionary getProperties() { - return properties; - } +// public Dictionary getConfigProperties() { +// return configProperties; +// } - public Dictionary cloneProperties() { - return new Hashtable<>(properties); + public Dictionary cloneConfigProperties() { + return new Hashtable<>(configProperties); } public String getForcedPassword() { @@ -347,18 +511,31 @@ public abstract class AbstractLdapDirectory return scoped; } - public String getMemberAttributeId() { - return memberAttributeId; - } - public List getCredentialAttributeIds() { return credentialAttributeIds; } - protected String getUri() { + public String getUri() { return uri; } + public LdapDirectoryDao getDirectoryDao() { + return directoryDao; + } + + /** dn can be null, in that case a default should be returned. */ + public String getUserObjectClass() { + return userObjectClass; + } + + public String getGroupObjectClass() { + return groupObjectClass; + } + + public String getMemberAttributeId() { + return memberAttributeId; + } + /* * OBJECT METHODS */