summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/openldap51
-rw-r--r--roles/common/tasks/ldap.yml1
2 files changed, 31 insertions, 21 deletions
diff --git a/lib/openldap b/lib/openldap
index 39ccd8e..5a4cef4 100644
--- a/lib/openldap
+++ b/lib/openldap
@@ -10,44 +10,42 @@
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ldap, ldap.sasl
from ldap.filter import filter_format
from ldap.dn import dn2str,explode_dn,str2dn
from ldap.modlist import addModlist
from ldif import LDIFParser
from functools import partial
import re
# Dirty hack to check equality between the targetted LDIF and that
# currently in the directory. The value of some configuration (olc*)
-# attributes is automatically indexed when added; for those we remove
-# the index before checking the equality between the two lists of
-# values.
-idxAttr_re = re.compile( '^\{\d+\}(.*)' )
+# attributes is automatically indexed when added; for those we'll add
+# explicit indices to what we find in the LDIF.
indexedAttributes = frozenset([
'olcAttributeTypes',
'olcObjectClasses',
'olcAccess',
])
# Another hack. Configuration entries sometimes pollutes the DNs with
# indices, thus it's not possible to directly use them as base.
# Instead, we use their parent as a pase, and search for the *unique*
# match with the same ObjectClass and the matching extra attributes.
# ('%s' in the attribute value is replaced with the value of the source
# entry.)
indexedDN = {
'olcSchemaConfig': [('cn', '{*}%s')],
'olcHdbConfig': [('olcDbDirectory', '%s' )],
}
# Run the given callback on each DN seen. If its return value is not
@@ -128,59 +126,70 @@ def flexibleSearch(module, l, dn, entry):
return r.pop()
# Add or modify (only the attributes that differ from those in the
# directory) the entry for that DN.
# l must be an LDAPObject, and should provide an open connection to the
# directory with disclose/search/write access.
def processEntry(module, l, dn, entry):
changed = False
r = flexibleSearch( module, l, dn, entry )
if r is None:
changed = True
if module.check_mode:
module.exit_json(changed=changed, msg="add DN %s" % dn)
l.add_s( dn, addModlist(entry) )
else:
d,e = r
fst = str2dn(dn).pop(0)[0][0]
diff = []
for a,v in e.iteritems():
- if a == fst:
- # the first attribute of the DN is implicit
- continue
if a not in entry.keys():
- diff.append((ldap.MOD_DELETE, a, v))
- continue
- if a in indexedAttributes:
- # remove indices
- v = [ idxAttr_re.search(v1).group(1) for v1 in v ]
- if v != entry[a]:
- # TODO: finer grain: we should modify/add/delete
- # based on couple (attr,value), not attr only.
- # The difficulty is that we need to preserve the order
- # if a in indexedAttributes. Maybe we should index
- # entry[a] instead, and walk on both lists at once?
- diff.append((ldap.MOD_REPLACE, a, entry[a]))
-
+ if a != fst:
+ # delete all values except for the first attribute,
+ # which is implicit
+ diff.append(( ldap.MOD_DELETE, a, None ))
+ elif a in indexedAttributes:
+ # add explicit indices in the entry from the LDIF
+ entry[a] = map( (lambda x: '{%d}%s' % x)
+ , zip(range(len(entry[a])),entry[a]) )
+ if v != entry[a]:
+ diff.append(( ldap.MOD_REPLACE, a, entry[a] ))
+ elif v != entry[a]:
+ # for non-indexed attribute, we update values in the
+ # symmetric difference only
+ s1 = set(v)
+ s2 = set(entry[a])
+ if s1.isdisjoint(s2):
+ # replace the former values with the new ones
+ diff.append(( ldap.MOD_REPLACE, a, entry[a] ))
+ else:
+ x = list(s1.difference(s2))
+ if x:
+ diff.append(( ldap.MOD_DELETE, a, x ))
+ y = list(s2.difference(s1))
+ if y:
+ diff.append(( ldap.MOD_ADD, a, y ))
+
+ # add attributes that weren't in e
for a in set(entry).difference(e.keys()):
- diff.append((ldap.MOD_ADD, a, entry[a]))
+ diff.append(( ldap.MOD_ADD, a, entry[a] ))
if diff:
changed = True
if module.check_mode:
module.exit_json(changed=changed, msg="mod DN %s" % dn)
l.modify_s( d, diff )
return changed
# Load the given module.
def loadModule(module, name):
changed = False
l = ldap.initialize( 'ldapi://' )
l.sasl_interactive_bind_s('', ldap.sasl.external())
f = filter_format( '(&(objectClass=olcModuleList)(olcModuleLoad=%s))', [name] )
r = l.search_s( 'cn=config', ldap.SCOPE_ONELEVEL, filterstr = f, attrlist = [] )
if not r:
changed = True
diff --git a/roles/common/tasks/ldap.yml b/roles/common/tasks/ldap.yml
index 26ab349..cb1e835 100644
--- a/roles/common/tasks/ldap.yml
+++ b/roles/common/tasks/ldap.yml
@@ -36,31 +36,32 @@
owner=root group=root
state=directory
mode=0755
- name: Copy fripost database definition
template: src=etc/ldap/database.ldif.j2
dest=/etc/ldap/fripost/database.ldif
owner=root group=root
mode=0600
- name: Copy fripost schema
copy: src=etc/ldap/schema/fripost.ldif
dest=/etc/ldap/schema/fripost.ldif
owner=root group=root
mode=0644
- name: Load fripost's schema and configure the database
openldap: target=/etc/ldap/{{ item }} state=present
with_items:
- schema/fripost.ldif
+ # TODO load other required schemas *before* loading the database
- fripost/database.ldif
- name: Load LDAP modules
openldap: module={{ item }}.la state=present
with_items:
# TODO only if provider
- syncprov
# TODO only if writable
- constraint
# TODO: authz constraint syncprov syncrepl