Hi,

the attached patches fix <https://fedorahosted.org/freeipa/ticket/3488>.

Honza

--
Jan Cholasta
>From 7fa3ca5c581b54bfb3d8b6c904d33bde0c3845da Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:09:56 +0100
Subject: [PATCH 01/12] Rename LDAPEntry method commit to reset_modlist.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py            | 2 +-
 ipaserver/install/ldapupdate.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 39d0edb..c468c6a 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -740,7 +740,7 @@ class LDAPEntry(collections.MutableMapping):
 
         return result
 
-    def commit(self):
+    def reset_modlist(self):
         """
         Make the current state of the entry a new reference point for change
         tracking.
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 87bd0c8..0c44a85 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -281,7 +281,7 @@ class LDAPUpdate:
 
     def _entry_to_entity(self, ent):
         entry = ent.copy()
-        entry.commit()
+        entry.reset_modlist()
         return entry
 
     def _combine_updates(self, all_updates, update):
-- 
1.8.4.2

>From 323209be795748a4dda2994c8cdf324912c25213 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:13:48 +0100
Subject: [PATCH 02/12] Use old entry state in LDAPClient.update_entry.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py | 33 ++++++++++++---------------------
 1 file changed, 12 insertions(+), 21 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index c468c6a..92528f4 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -440,6 +440,7 @@ class IPASimpleLDAPObject(object):
 
             for attr, original_values in original_attrs.items():
                 ipa_entry.raw[attr] = original_values
+            ipa_entry.reset_modlist()
 
             ipa_result.append(ipa_entry)
 
@@ -1530,16 +1531,6 @@ class LDAPClient(object):
             raise errors.LimitsExceeded()
         return entry[0]
 
-    def _get_dn_and_attrs(self, entry_or_dn, entry_attrs):
-        """Helper for legacy calling style for {add,update}_entry
-        """
-        if entry_attrs is None:
-            return entry_or_dn.dn, entry_or_dn
-        else:
-            assert isinstance(entry_or_dn, DN)
-            entry_attrs = self.make_entry(entry_or_dn, entry_attrs)
-            return entry_or_dn, entry_attrs
-
     def add_entry(self, entry, entry_attrs=None):
         """Create a new entry.
 
@@ -1548,13 +1539,14 @@ class LDAPClient(object):
         The legacy two-argument variant is:
             add_entry(dn, entry_attrs)
         """
-        dn, attrs = self._get_dn_and_attrs(entry, entry_attrs)
+        if entry_attrs is not None:
+            entry = self.make_entry(entry, entry_attrs)
 
         # remove all [] values (python-ldap hates 'em)
-        attrs = dict((k, v) for k, v in attrs.raw.iteritems() if v)
+        attrs = dict((k, v) for k, v in entry.raw.iteritems() if v)
 
         with self.error_handler():
-            self.conn.add_s(dn, attrs.items())
+            self.conn.add_s(entry.dn, attrs.items())
 
     def update_entry_rdn(self, dn, new_rdn, del_old=True):
         """
@@ -1576,20 +1568,17 @@ class LDAPClient(object):
     def _generate_modlist(self, dn, entry_attrs):
         assert isinstance(dn, DN)
 
-        # get original entry
-        dn, entry_attrs_old = self.get_entry(dn, entry_attrs.keys())
-
         # generate modlist
         # for multi value attributes: no MOD_REPLACE to handle simultaneous
         # updates better
         # for single value attribute: always MOD_REPLACE
         modlist = []
         for (k, v) in entry_attrs.raw.iteritems():
-            if not v and k in entry_attrs_old:
+            if not v and k in entry_attrs.orig_data:
                 modlist.append((ldap.MOD_DELETE, k, None))
             else:
                 v = set(v)
-                old_v = set(entry_attrs_old.raw.get(k, []))
+                old_v = set(entry_attrs.orig_data.raw.get(k, []))
 
                 adds = list(v.difference(old_v))
                 rems = list(old_v.difference(v))
@@ -1629,16 +1618,18 @@ class LDAPClient(object):
         The legacy two-argument variant is:
             update_entry(dn, entry_attrs)
         """
-        dn, attrs = self._get_dn_and_attrs(entry, entry_attrs)
+        if entry_attrs is not None:
+            entry = self.get_entry(entry, entry_attrs.keys())
+            entry.update(entry_attrs)
 
         # generate modlist
-        modlist = self._generate_modlist(dn, attrs)
+        modlist = self._generate_modlist(entry.dn, entry)
         if not modlist:
             raise errors.EmptyModlist()
 
         # pass arguments to python-ldap
         with self.error_handler():
-            self.conn.modify_s(dn, modlist)
+            self.conn.modify_s(entry.dn, modlist)
 
     def delete_entry(self, entry_or_dn):
         """Delete an entry given either the DN or the entry itself"""
-- 
1.8.4.2

>From 62d7d18cf0b223d00ab268bacc23bf24f7a7526b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:38:12 +0100
Subject: [PATCH 03/12] Move LDAPClient method get_single_value to
 IPASimpleLDAPObject.

Refactor IPASimpleLDAPObject methods get_syntax and get_single_value.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py | 48 ++++++++++++++++++++++++++++++------------------
 1 file changed, 30 insertions(+), 18 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 92528f4..f30ba7c 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -323,21 +323,23 @@ class IPASimpleLDAPObject(object):
         self._schema = None
 
     def get_syntax(self, attr):
+        if isinstance(attr, unicode):
+            attr = attr.encode('utf-8')
+
         # Is this a special case attribute?
-        syntax = self._SCHEMA_OVERRIDE.get(attr)
-        if syntax is not None:
-            return syntax
+        if attr in self._SCHEMA_OVERRIDE:
+            return self._SCHEMA_OVERRIDE[attr]
 
         if self.schema is None:
             return None
 
         # Try to lookup the syntax in the schema returned by the server
         obj = self.schema.get_obj(ldap.schema.AttributeType, attr)
-        if obj is not None:
-            return obj.syntax
-        else:
+        if obj is None:
             return None
 
+        return obj.syntax
+
     def has_dn_syntax(self, attr):
         """
         Check the schema to see if the attribute uses DN syntax.
@@ -347,6 +349,27 @@ class IPASimpleLDAPObject(object):
         syntax = self.get_syntax(attr)
         return syntax == DN_SYNTAX_OID
 
+    def get_single_value(self, attr):
+        """
+        Check the schema to see if the attribute is single-valued.
+
+        If the attribute is in the schema then returns True/False
+
+        If there is a problem loading the schema or the attribute is
+        not in the schema return None
+        """
+        if isinstance(attr, unicode):
+            attr = attr.encode('utf-8')
+
+        if self.schema is None:
+            return None
+
+        obj = self.schema.get_obj(ldap.schema.AttributeType, attr)
+        if obj is None:
+            return None
+
+        return obj.single_value
+
 
     def encode(self, val):
         """
@@ -1189,18 +1212,7 @@ class LDAPClient(object):
         return [unicode(a).lower() for a in list(set(allowed_attributes))]
 
     def get_single_value(self, attr):
-        """
-        Check the schema to see if the attribute is single-valued.
-
-        If the attribute is in the schema then returns True/False
-
-        If there is a problem loading the schema or the attribute is
-        not in the schema return None
-        """
-        if self.schema is None:
-            return None
-        obj = self.schema.get_obj(ldap.schema.AttributeType, attr)
-        return obj and obj.single_value
+        return self.conn.get_single_value(attr)
 
     def make_dn_from_attr(self, attr, value, parent_dn=None):
         """
-- 
1.8.4.2

>From 108665332f9ac50e1394f6190bf11f8c112d9e62 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:39:38 +0100
Subject: [PATCH 04/12] Make IPASimpleLDAPObject.get_single_value result
 overridable.

Add some default overrides.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index f30ba7c..35a0cff 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -246,11 +246,18 @@ class IPASimpleLDAPObject(object):
     #
     # FWIW, many entries under cn=config are undefined :-(
 
-    _SCHEMA_OVERRIDE = CIDict({
+    _SYNTAX_OVERRIDE = CIDict({
         'managedtemplate': DN_SYNTAX_OID, # DN
         'managedbase':     DN_SYNTAX_OID, # DN
         'originscope':     DN_SYNTAX_OID, # DN
     })
+    _SINGLE_VALUE_OVERRIDE = CIDict({
+        'nsslapd-ssl-check-hostname': True,
+        'nsslapd-lookthroughlimit': True,
+        'nsslapd-idlistscanlimit': True,
+        'nsslapd-anonlimitsdn': True,
+        'nsslapd-minssf-exclude-rootdse': True,
+    })
 
     def __init__(self, uri, force_schema_updates, no_schema=False,
                  decode_attrs=True):
@@ -327,8 +334,8 @@ class IPASimpleLDAPObject(object):
             attr = attr.encode('utf-8')
 
         # Is this a special case attribute?
-        if attr in self._SCHEMA_OVERRIDE:
-            return self._SCHEMA_OVERRIDE[attr]
+        if attr in self._SYNTAX_OVERRIDE:
+            return self._SYNTAX_OVERRIDE[attr]
 
         if self.schema is None:
             return None
@@ -361,6 +368,9 @@ class IPASimpleLDAPObject(object):
         if isinstance(attr, unicode):
             attr = attr.encode('utf-8')
 
+        if attr in self._SINGLE_VALUE_OVERRIDE:
+            return self._SINGLE_VALUE_OVERRIDE[attr]
+
         if self.schema is None:
             return None
 
-- 
1.8.4.2

>From 222ecdd30685dd38c93edad8a1302dfe75e7b6fa Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:41:17 +0100
Subject: [PATCH 05/12] Use LDAPClient.update_entry for LDAP mods in
 ldapupdate.

Remove legacy IPAdmin methods generateModList and updateEntry.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py            | 60 -----------------------------------------
 ipaserver/install/ldapupdate.py |  4 +--
 2 files changed, 2 insertions(+), 62 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 35a0cff..73f4c54 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1772,66 +1772,6 @@ class IPAdmin(LDAPClient):
         self.__bind_with_wait(
             self.conn.sasl_interactive_bind_s, timeout, None, auth_tokens)
 
-    def updateEntry(self,dn,oldentry,newentry):
-        # FIXME: for backwards compatibility only
-        """This wraps the mod function. It assumes that the entry is already
-           populated with all of the desired objectclasses and attributes"""
-
-        assert isinstance(dn, DN)
-
-        modlist = self.generateModList(oldentry, newentry)
-
-        if len(modlist) == 0:
-            raise errors.EmptyModlist
-
-        with self.error_handler():
-            self.modify_s(dn, modlist)
-        return True
-
-    def generateModList(self, old_entry, new_entry):
-        # FIXME: for backwards compatibility only
-        """A mod list generator that computes more precise modification lists
-           than the python-ldap version.  For single-value attributes always
-           use a REPLACE operation, otherwise use ADD/DEL.
-        """
-
-        # Some attributes, like those in cn=config, need to be replaced
-        # not deleted/added.
-        FORCE_REPLACE_ON_UPDATE_ATTRS = ('nsslapd-ssl-check-hostname', 'nsslapd-lookthroughlimit', 'nsslapd-idlistscanlimit', 'nsslapd-anonlimitsdn', 'nsslapd-minssf-exclude-rootdse')
-        modlist = []
-
-        keys = set(old_entry.keys())
-        keys.update(new_entry.keys())
-
-        for key in keys:
-            new_values = new_entry.raw.get(key, [])
-            old_values = old_entry.raw.get(key, [])
-
-            # We used to convert to sets and use difference to calculate
-            # the changes but this did not preserve order which is important
-            # particularly for schema
-            adds = [x for x in new_values if x not in old_values]
-            removes = [x for x in old_values if x not in new_values]
-
-            if len(adds) == 0 and len(removes) == 0:
-                continue
-
-            is_single_value = self.get_single_value(key)
-            force_replace = False
-            if key in FORCE_REPLACE_ON_UPDATE_ATTRS or is_single_value:
-                force_replace = True
-
-            if adds:
-                if force_replace:
-                    modlist.append((ldap.MOD_REPLACE, key, adds))
-                else:
-                    modlist.append((ldap.MOD_ADD, key, adds))
-            if removes:
-                if not force_replace or not new_values:
-                    modlist.append((ldap.MOD_DELETE, key, removes))
-
-        return modlist
-
     def modify_s(self, *args, **kwargs):
         # FIXME: for backwards compatibility only
         return self.conn.modify_s(*args, **kwargs)
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 0c44a85..97d7a35 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -722,7 +722,7 @@ class LDAPUpdate:
         else:
             # Update LDAP
             try:
-                changes = self.conn.generateModList(entry.orig_data, entry)
+                changes = self.conn._generate_modlist(entry.dn, entry)
                 if len(changes) >= 1:
                     updated = True
                 safe_changes = []
@@ -731,7 +731,7 @@ class LDAPUpdate:
                 self.debug("%s" % safe_changes)
                 self.debug("Live %d, updated %d" % (self.live_run, updated))
                 if self.live_run and updated:
-                    self.conn.updateEntry(entry.dn, entry.orig_data, entry)
+                    self.conn.update_entry(entry)
                 self.info("Done")
             except errors.EmptyModlist:
                 self.info("Entry already up-to-date")
-- 
1.8.4.2

>From 6e4c849b59b80ea6142732983112210bce3d522f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:42:35 +0100
Subject: [PATCH 06/12] Reduce amount of LDAPEntry.reset_modlist calls in
 ldapupdate.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipaserver/install/ldapupdate.py | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 97d7a35..087cfcf 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -279,11 +279,6 @@ class LDAPUpdate:
         if fd != sys.stdin: fd.close()
         return text
 
-    def _entry_to_entity(self, ent):
-        entry = ent.copy()
-        entry.reset_modlist()
-        return entry
-
     def _combine_updates(self, all_updates, update):
         'Combine a new update with the list of total updates'
         dn = update.get('dn')
@@ -518,7 +513,7 @@ class LDAPUpdate:
 
         if not default:
             # This means that the entire entry needs to be created with add
-            return self._entry_to_entity(entry)
+            return entry
 
         for item in default:
             # We already do syntax-parsing so this is safe
@@ -531,8 +526,9 @@ class LDAPUpdate:
             else:
                 e = [value]
             entry[attr] = e
+        entry.reset_modlist()
 
-        return self._entry_to_entity(entry)
+        return entry
 
     def _get_entry(self, dn):
         """Retrieve an object from LDAP.
@@ -672,7 +668,7 @@ class LDAPUpdate:
             if len(e) > 1:
                 # we should only ever get back one entry
                 raise BadSyntax, "More than 1 entry returned on a dn search!? %s" % new_entry.dn
-            entry = self._entry_to_entity(e[0])
+            entry = e[0]
             found = True
             self.info("Updating existing entry: %s", entry.dn)
         except errors.NotFound:
-- 
1.8.4.2

>From 6b3be87e56a603a0ca7e393274d6c01adbbc5760 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:45:10 +0100
Subject: [PATCH 07/12] Add LDAPEntry method generate_modlist.

Use LDAPEntry.generate_modlist instead of LDAPClient._generate_modlist and
remove LDAPClient._generate_modlist.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py              | 74 +++++++++++++++------------------------
 ipaserver/install/ldapupdate.py   |  2 +-
 ipaserver/install/schemaupdate.py |  2 +-
 3 files changed, 30 insertions(+), 48 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 73f4c54..a3258a2 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -995,6 +995,33 @@ class LDAPEntry(collections.MutableMapping):
             return NotImplemented
         return other is not self
 
+    def generate_modlist(self):
+        modlist = []
+
+        for name, value in self.raw.iteritems():
+            if name not in self._orig:
+                modlist.append((ldap.MOD_REPLACE, name, value))
+                continue
+
+            new = set(value)
+            old = set(self._orig[name])
+
+            adds = new - old
+            dels = old - new
+            if adds and self.conn.get_single_value(name):
+                modlist.append((ldap.MOD_REPLACE, name, list(adds)))
+            else:
+                if adds:
+                    modlist.append((ldap.MOD_ADD, name, list(adds)))
+                if dels:
+                    modlist.append((ldap.MOD_DELETE, name, list(dels)))
+
+        for name in self._orig:
+            if name not in self:
+                modlist.append((ldap.MOD_DELETE, name, None))
+
+        return modlist
+
     # FIXME: Remove when python-ldap tuple compatibility is dropped
     def __iter__(self):
         yield self._dn
@@ -1587,51 +1614,6 @@ class LDAPClient(object):
             self.conn.rename_s(dn, new_rdn, delold=int(del_old))
             time.sleep(.3)  # Give memberOf plugin a chance to work
 
-    def _generate_modlist(self, dn, entry_attrs):
-        assert isinstance(dn, DN)
-
-        # generate modlist
-        # for multi value attributes: no MOD_REPLACE to handle simultaneous
-        # updates better
-        # for single value attribute: always MOD_REPLACE
-        modlist = []
-        for (k, v) in entry_attrs.raw.iteritems():
-            if not v and k in entry_attrs.orig_data:
-                modlist.append((ldap.MOD_DELETE, k, None))
-            else:
-                v = set(v)
-                old_v = set(entry_attrs.orig_data.raw.get(k, []))
-
-                adds = list(v.difference(old_v))
-                rems = list(old_v.difference(v))
-
-                is_single_value = self.get_single_value(k)
-
-                value_count = len(old_v) + len(adds) - len(rems)
-                if is_single_value and value_count > 1:
-                    raise errors.OnlyOneValueAllowed(attr=k)
-
-                force_replace = False
-                if len(v) > 0 and len(v.intersection(old_v)) == 0:
-                    force_replace = True
-
-                if adds:
-                    if force_replace:
-                        modlist.append((ldap.MOD_REPLACE, k, adds))
-                    else:
-                        modlist.append((ldap.MOD_ADD, k, adds))
-                if rems:
-                    if not force_replace:
-                        modlist.append((ldap.MOD_DELETE, k, rems))
-
-        # Usually the modlist order does not matter.
-        # However, for schema updates, we want 'attributetypes' before
-        # 'objectclasses'.
-        # A simple sort will ensure this.
-        modlist.sort(key=lambda m: m[1].lower())
-
-        return modlist
-
     def update_entry(self, entry, entry_attrs=None):
         """Update entry's attributes.
 
@@ -1645,7 +1627,7 @@ class LDAPClient(object):
             entry.update(entry_attrs)
 
         # generate modlist
-        modlist = self._generate_modlist(entry.dn, entry)
+        modlist = entry.generate_modlist()
         if not modlist:
             raise errors.EmptyModlist()
 
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 087cfcf..42964c0 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -718,7 +718,7 @@ class LDAPUpdate:
         else:
             # Update LDAP
             try:
-                changes = self.conn._generate_modlist(entry.dn, entry)
+                changes = entry.generate_modlist()
                 if len(changes) >= 1:
                     updated = True
                 safe_changes = []
diff --git a/ipaserver/install/schemaupdate.py b/ipaserver/install/schemaupdate.py
index f51b29b..bb2f0f1 100644
--- a/ipaserver/install/schemaupdate.py
+++ b/ipaserver/install/schemaupdate.py
@@ -114,7 +114,7 @@ def update_schema(schema_files, ldapi=False, dm_password=None, live_run=True):
 
     # FIXME: We should have a better way to display the modlist,
     # for now display raw output of our internal routine
-    modlist = conn._generate_modlist(schema_entry.dn, schema_entry)
+    modlist = schema_entry.generate_modlist()
     log.debug("Complete schema modlist:\n%s", pprint.pformat(modlist))
 
     if modified and live_run:
-- 
1.8.4.2

>From 2f9e07971eb8d7cce0b900cc3e6cd8b6ffd5c2e5 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:48:23 +0100
Subject: [PATCH 08/12] Remove unused LDAPClient methods get_syntax and
 get_single_value.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index a3258a2..7bf856e 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1223,15 +1223,6 @@ class LDAPClient(object):
         """schema associated with this LDAP server"""
         return self.conn.schema
 
-    def get_syntax(self, attr, value):
-        if self.schema is None:
-            return None
-        obj = self.schema.get_obj(ldap.schema.AttributeType, attr)
-        if obj is not None:
-            return obj.syntax
-        else:
-            return None
-
     def has_dn_syntax(self, attr):
         return self.conn.has_dn_syntax(attr)
 
@@ -1248,9 +1239,6 @@ class LDAPClient(object):
                     reason=_('objectclass %s not found') % oc)
         return [unicode(a).lower() for a in list(set(allowed_attributes))]
 
-    def get_single_value(self, attr):
-        return self.conn.get_single_value(attr)
-
     def make_dn_from_attr(self, attr, value, parent_dn=None):
         """
         Make distinguished name from attribute.
-- 
1.8.4.2

>From a23ef973e244dff66b73ea80120464aec80b3d71 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:52:31 +0100
Subject: [PATCH 09/12] Remove legacy LDAPEntry properties data and orig_data.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py                        | 11 -----------
 ipaserver/install/plugins/rename_managed.py |  8 ++++----
 2 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 7bf856e..62cff91 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -721,7 +721,6 @@ class LDAPEntry(collections.MutableMapping):
     def conn(self):
         return self._conn
 
-    # properties for Entry and Entity compatibility
     @property
     def dn(self):
         return self._dn
@@ -743,16 +742,6 @@ class LDAPEntry(collections.MutableMapping):
             self._single_value_view = SingleValueLDAPEntryView(self)
         return self._single_value_view
 
-    @property
-    def data(self):
-        # FIXME: for backwards compatibility only
-        return self
-
-    @property
-    def orig_data(self):
-        # FIXME: for backwards compatibility only
-        return self._orig
-
     def __repr__(self):
         data = dict(self._raw)
         data.update((k, v) for k, v in self._nice.iteritems() if v is not None)
diff --git a/ipaserver/install/plugins/rename_managed.py b/ipaserver/install/plugins/rename_managed.py
index e0fa36b..dad9259 100644
--- a/ipaserver/install/plugins/rename_managed.py
+++ b/ipaserver/install/plugins/rename_managed.py
@@ -74,7 +74,7 @@ class GenerateUpdateMixin(object):
         for entry in definitions_managed_entries:
             assert isinstance(entry.dn, DN)
             if deletes:
-                old_dn = entry.data['managedtemplate'][0]
+                old_dn = entry['managedtemplate'][0]
                 assert isinstance(old_dn, DN)
                 try:
                     (old_dn, entry) = ldap.get_entry(old_dn, ['*'])
@@ -102,14 +102,14 @@ class GenerateUpdateMixin(object):
 
             else:
                 # Update the template dn by replacing the old containter with the new container
-                old_dn = entry.data['managedtemplate'][0]
+                old_dn = entry['managedtemplate'][0]
                 new_dn = EditableDN(old_dn)
                 if new_dn.replace(old_template_container, new_template_container) != 1:
                     self.error("unable to replace '%s' with '%s' in '%s'",
                                old_template_container, new_template_container, old_dn)
                     continue
                 new_dn = DN(new_dn)
-                entry.data['managedtemplate'] = new_dn
+                entry['managedtemplate'] = new_dn
 
                 # Edit the dn, then convert it back to an immutable DN
                 old_dn = entry.dn
@@ -122,7 +122,7 @@ class GenerateUpdateMixin(object):
 
                 # The old attributes become defaults for the new entry
                 new_update = {'dn': new_dn,
-                              'default': entry_to_update(entry.data)}
+                              'default': entry_to_update(entry)}
 
                 # Add the replacement update to the collection of all updates
                 update_list.append({new_dn: new_update})
-- 
1.8.4.2

>From d8edb45ff8529226b1ec48264ed4ba74955458b4 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:53:32 +0100
Subject: [PATCH 10/12] Store old entry state in dict rather than LDAPEntry.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py       | 30 +++++++-----------------------
 ipaserver/plugins/ldap2.py |  4 ++--
 2 files changed, 9 insertions(+), 25 deletions(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 62cff91..159f67f 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -697,14 +697,14 @@ class LDAPEntry(collections.MutableMapping):
         self._raw = {}
         self._sync = {}
         self._not_list = set()
-        self._orig = self
+        self._orig = {}
         self._raw_view = None
         self._single_value_view = None
 
         if isinstance(_obj, LDAPEntry):
             #pylint: disable=E1103
             self._not_list = set(_obj._not_list)
-            self._orig = _obj._orig
+            self._orig = dict(_obj._orig)
             if _obj.conn is _conn:
                 self._names = CIDict(_obj._names)
                 self._nice = dict(_obj._nice)
@@ -750,27 +750,6 @@ class LDAPEntry(collections.MutableMapping):
     def copy(self):
         return LDAPEntry(self)
 
-    def clone(self):
-        result = LDAPEntry(self._conn, self._dn)
-
-        result._names = deepcopy(self._names)
-        result._nice = deepcopy(self._nice)
-        result._raw = deepcopy(self._raw)
-        result._sync = deepcopy(self._sync)
-        result._not_list = deepcopy(self._not_list)
-        if self._orig is not self:
-            result._orig = self._orig.clone()
-
-        return result
-
-    def reset_modlist(self):
-        """
-        Make the current state of the entry a new reference point for change
-        tracking.
-        """
-        self._orig = self
-        self._orig = self.clone()
-
     def _sync_attr(self, name):
         nice = self._nice[name]
         assert isinstance(nice, list)
@@ -843,6 +822,8 @@ class LDAPEntry(collections.MutableMapping):
                 if oldname in self._not_list:
                     self._not_list.remove(oldname)
                     self._not_list.add(name)
+                if oldname in self._orig:
+                    self._orig[name] = self._orig.pop(oldname)
         else:
             if self._conn.schema is not None:
                 attrtype = self._conn.schema.get_obj(ldap.schema.AttributeType,
@@ -984,6 +965,9 @@ class LDAPEntry(collections.MutableMapping):
             return NotImplemented
         return other is not self
 
+    def reset_modlist(self):
+        self._orig = deepcopy(dict(self.raw))
+
     def generate_modlist(self):
         modlist = []
 
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index fa58b22..d6a8ebb 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -272,7 +272,7 @@ class ldap2(LDAPClient, CrudBackend):
         try:
             config_entry = getattr(context, 'config_entry')
             if config_entry.conn is self.conn:
-                return config_entry.clone()
+                return config_entry
         except AttributeError:
             # Not in our context yet
             pass
@@ -289,7 +289,7 @@ class ldap2(LDAPClient, CrudBackend):
         for a in self.config_defaults:
             if a not in config_entry:
                 config_entry[a] = self.config_defaults[a]
-        context.config_entry = config_entry.clone()
+        context.config_entry = config_entry
         return config_entry
 
     def has_upg(self):
-- 
1.8.4.2

>From 9add5f1d42718d7ec94862b2809f40371659572b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:54:25 +0100
Subject: [PATCH 11/12] Do not crash on bad LDAP data when formatting decode
 error message.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipapython/ipaldap.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 159f67f..4fca61d 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -422,7 +422,7 @@ class IPASimpleLDAPObject(object):
             try:
                 return target_type(val)
             except Exception, e:
-                msg = 'unable to convert the attribute "%s" value "%s" to type %s' % (attr, val, target_type)
+                msg = 'unable to convert the attribute %r value %r to type %s' % (attr, val, target_type)
                 self.log.error(msg)
                 raise ValueError(msg)
         elif isinstance(val, list):
-- 
1.8.4.2

>From 08d5a2663b8d15f4a75cca84087633a57f9a57d0 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 10 Dec 2013 11:56:35 +0100
Subject: [PATCH 12/12] Use raw LDAP data in ldapupdate.

https://fedorahosted.org/freeipa/ticket/3488
---
 ipaserver/install/ldapupdate.py | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 42964c0..683c084 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -44,15 +44,14 @@ from ipalib import api
 from ipapython.dn import DN
 from ipapython.ipa_log_manager import *
 from ipaserver.install.plugins import PRE_UPDATE, POST_UPDATE
-from ipaserver.plugins import ldap2
 
 
 def connect(ldapi=False, realm=None, fqdn=None, dm_password=None, pw_name=None):
     """Create a connection for updates"""
     if ldapi:
-        conn = ipaldap.IPAdmin(ldapi=True, realm=realm)
+        conn = ipaldap.IPAdmin(ldapi=True, realm=realm, decode_attrs=False)
     else:
-        conn = ipaldap.IPAdmin(fqdn, ldapi=False, realm=realm)
+        conn = ipaldap.IPAdmin(fqdn, ldapi=False, realm=realm, decode_attrs=False)
     try:
         if dm_password:
             conn.do_simple_bind(binddn=DN(('cn', 'directory manager')),
@@ -235,8 +234,7 @@ class LDAPUpdate:
                                 skipinitialspace=True,
                                 **kwargs)
         for row in csv_reader:
-            # decode UTF-8 back to Unicode, cell by cell:
-            yield [unicode(cell, 'utf-8') for cell in row]
+            yield row
 
     def _identify_arch(self):
         """On multi-arch systems some libraries may be in /lib64, /usr/lib64,
@@ -558,11 +556,6 @@ class LDAPUpdate:
             (action, attr, update_values) = update.split(':',2)
             update_values = self._parse_values(update_values)
 
-            # If the attribute is known to be a DN convert it to a DN object.
-            # This has to be done after _parse_values() due to quoting and comma separated lists.
-            if self.conn.has_dn_syntax(attr):
-                update_values = [DN(x) for x in update_values]
-
             entry_values = entry.get(attr)
             if not isinstance(entry_values, list):
                 if entry_values is None:
-- 
1.8.4.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to