Hi,

these patches implement attribute name case preservation in LDAPEntry. Apply on top of Petr Viktorin's LDAP code refactoring patchset (up to part 5).


Honza

--
Jan Cholasta
>From 8778f668591e28d78741df55dc2bca98917073e5 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 31 Jan 2013 11:19:13 +0100
Subject: [PATCH 1/4] Use the dn attribute of LDAPEntry to set/get DNs of
 entries.

Convert all code that uses the 'dn' key of LDAPEntry for this to use the dn
attribute instead.
---
 install/tools/ipa-compliance      | 10 +++----
 install/tools/ipa-replica-install |  2 +-
 ipalib/plugins/automember.py      |  9 ++++--
 ipalib/plugins/baseldap.py        | 58 +++++++++++++++++++++++++++------------
 ipalib/plugins/krbtpolicy.py      |  6 ++--
 ipalib/plugins/permission.py      |  6 ++--
 ipalib/plugins/sudorule.py        |  8 ++++--
 ipalib/plugins/trust.py           |  2 +-
 ipalib/plugins/user.py            |  9 ++----
 ipaserver/ipaldap.py              |  4 +--
 ipaserver/plugins/ldap2.py        |  2 --
 11 files changed, 73 insertions(+), 43 deletions(-)

diff --git a/install/tools/ipa-compliance b/install/tools/ipa-compliance
index c82e415..9b34350 100644
--- a/install/tools/ipa-compliance
+++ b/install/tools/ipa-compliance
@@ -116,7 +116,7 @@ def check_compliance(tmpdir, debug=False):
     hostcount = 0
     # Get the hosts first
     try:
-        (entries, truncated) = conn.find_entries('(krblastpwdchange=*)', ['dn'],
+        (entries, truncated) = conn.find_entries('(krblastpwdchange=*)', [],
             DN(api.env.container_host, api.env.basedn),
             conn.SCOPE_ONELEVEL,
             size_limit = -1)
@@ -136,10 +136,10 @@ def check_compliance(tmpdir, debug=False):
     available = 0
     try:
         (entries, truncated) = conn.find_entries('(objectclass=ipaentitlement)',
-        ['dn', 'userCertificate'],
-        DN(api.env.container_entitlements, api.env.basedn),
-        conn.SCOPE_ONELEVEL,
-        size_limit = -1)
+            ['userCertificate'],
+            DN(api.env.container_entitlements, api.env.basedn),
+            conn.SCOPE_ONELEVEL,
+            size_limit = -1)
 
         for entry in entries:
             (dn, attrs) = entry
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 13c3260..846122d 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -572,7 +572,7 @@ def main():
                                      config.dirman_password)
         found = False
         try:
-            entry = conn.find_entries(u'fqdn=%s' % host, ['dn', 'fqdn'], DN(api.env.container_host, api.env.basedn))
+            entry = conn.find_entries(u'fqdn=%s' % host, ['fqdn'], DN(api.env.container_host, api.env.basedn))
             print "The host %s already exists on the master server.\nYou should remove it before proceeding:" % host
             print "    %% ipa host-del %s" % host
             found = True
diff --git a/ipalib/plugins/automember.py b/ipalib/plugins/automember.py
index af39f6a..520f8a0 100644
--- a/ipalib/plugins/automember.py
+++ b/ipalib/plugins/automember.py
@@ -316,10 +316,12 @@ class automember_add_condition(LDAPUpdate):
                 except errors.NotFound:
                     failed['failed'][attr].append(regex)
 
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
         # Set failed and completed to they can be harvested in the execute super
         setattr(context, 'failed', failed)
         setattr(context, 'completed', completed)
-        setattr(context, 'entry_attrs', dict(entry_attrs))
+        setattr(context, 'entry_attrs', entry_attrs)
 
         # Make sure to returned the failed results if there is nothing to remove
         if completed == 0:
@@ -406,10 +408,13 @@ class automember_remove_condition(LDAPUpdate):
                     else:
                         failed['failed'][attr].append(regex)
                 entry_attrs[attr] = old_entry
+
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
         # Set failed and completed to they can be harvested in the execute super
         setattr(context, 'failed', failed)
         setattr(context, 'completed', completed)
-        setattr(context, 'entry_attrs', dict(entry_attrs))
+        setattr(context, 'entry_attrs', entry_attrs)
 
         # Make sure to returned the failed results if there is nothing to remove
         if completed == 0:
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 44751e1..74e2384 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -229,6 +229,12 @@ def entry_from_entry(entry, newentry):
     for e in newentry.keys():
         entry[e] = newentry[e]
 
+def entry_to_dict(entry, **options):
+    result = dict(entry)
+    if options.get('all', False):
+        result['dn'] = entry.dn
+    return result
+
 def wait_for_value(ldap, dn, attr, value):
     """
     389-ds postoperation plugins are executed after the data has been
@@ -978,6 +984,7 @@ class LDAPCreate(BaseLDAPCommand, crud.Create):
         ldap = self.obj.backend
 
         entry_attrs = self.args_options_2_entry(*keys, **options)
+        entry_attrs = ldap.make_entry(DN(), entry_attrs)
 
         self.process_attr_options(entry_attrs, None, keys, options)
 
@@ -1063,13 +1070,15 @@ class LDAPCreate(BaseLDAPCommand, crud.Create):
         for callback in self.get_callbacks('post'):
             dn = callback(self, ldap, dn, entry_attrs, *keys, **options)
 
+        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
 
-        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
         if self.obj.primary_key and keys[-1] is not None:
-            return dict(result=dict(entry_attrs), value=keys[-1])
-        return dict(result=dict(entry_attrs), value=u'')
+            return dict(result=entry_attrs, value=keys[-1])
+        return dict(result=entry_attrs, value=u'')
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
@@ -1190,11 +1199,14 @@ class LDAPRetrieve(LDAPQuery):
             assert isinstance(dn, DN)
 
         self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
+
         if self.obj.primary_key and keys[-1] is not None:
-            return dict(result=dict(entry_attrs), value=keys[-1])
-        return dict(result=dict(entry_attrs), value=u'')
+            return dict(result=entry_attrs, value=keys[-1])
+        return dict(result=entry_attrs, value=u'')
 
     def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
@@ -1253,6 +1265,7 @@ class LDAPUpdate(LDAPQuery, crud.Update):
         assert isinstance(dn, DN)
 
         entry_attrs = self.args_options_2_entry(**options)
+        entry_attrs = ldap.make_entry(dn, entry_attrs)
 
         self.process_attr_options(entry_attrs, dn, keys, options)
 
@@ -1321,9 +1334,12 @@ class LDAPUpdate(LDAPQuery, crud.Update):
             assert isinstance(dn, DN)
 
         self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
         if self.obj.primary_key and keys[-1] is not None:
-            return dict(result=dict(entry_attrs), value=keys[-1])
-        return dict(result=dict(entry_attrs), value=u'')
+            return dict(result=entry_attrs, value=keys[-1])
+        return dict(result=entry_attrs, value=u'')
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
@@ -1544,13 +1560,16 @@ class LDAPAddMember(LDAPModMember):
                 **options)
             assert isinstance(dn, DN)
 
+        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
-        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
         return dict(
             completed=completed,
             failed=failed,
-            result=dict(entry_attrs),
+            result=entry_attrs,
         )
 
     def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
@@ -1642,14 +1661,16 @@ class LDAPRemoveMember(LDAPModMember):
                 **options)
             assert isinstance(dn, DN)
 
+        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
+
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
 
-        self.obj.convert_attribute_members(entry_attrs, *keys, **options)
         return dict(
             completed=completed,
             failed=failed,
-            result=dict(entry_attrs),
+            result=entry_attrs,
         )
 
     def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
@@ -1856,10 +1877,9 @@ class LDAPSearch(BaseLDAPCommand, crud.Search):
             for e in entries:
                 self.obj.convert_attribute_members(e[1], *args, **options)
 
-        for e in entries:
-            assert isinstance(e[0], DN)
-            e[1]['dn'] = e[0]
-        entries = [dict(e) for (dn, e) in entries]
+        for (i, e) in enumerate(entries):
+            entries[i] = entry_to_dict(e, **options)
+            entries[i]['dn'] = e.dn
 
         return dict(
             result=entries,
@@ -1994,11 +2014,13 @@ class LDAPAddReverseMember(LDAPModReverseMember):
             assert isinstance(dn, DN)
 
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
+
         return dict(
             completed=completed,
             failed=failed,
-            result=dict(entry_attrs),
+            result=entry_attrs,
         )
 
     def pre_callback(self, ldap, dn, *keys, **options):
@@ -2094,11 +2116,13 @@ class LDAPRemoveReverseMember(LDAPModReverseMember):
             assert isinstance(dn, DN)
 
         assert isinstance(dn, DN)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
         entry_attrs['dn'] = dn
+
         return dict(
             completed=completed,
             failed=failed,
-            result=dict(entry_attrs),
+            result=entry_attrs,
         )
 
     def pre_callback(self, ldap, dn, *keys, **options):
diff --git a/ipalib/plugins/krbtpolicy.py b/ipalib/plugins/krbtpolicy.py
index 07e3148..976f92b 100644
--- a/ipalib/plugins/krbtpolicy.py
+++ b/ipalib/plugins/krbtpolicy.py
@@ -176,8 +176,10 @@ class krbtpolicy_reset(LDAPQuery):
             dn = self.obj.get_dn(None)
         (dn, entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
 
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
         if keys[-1] is not None:
-            return dict(result=dict(entry_attrs), value=keys[-1])
-        return dict(result=dict(entry_attrs), value=u'')
+            return dict(result=entry_attrs, value=keys[-1])
+        return dict(result=entry_attrs, value=u'')
 
 api.register(krbtpolicy_reset)
diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py
index 1fbf9e0..91bea32 100644
--- a/ipalib/plugins/permission.py
+++ b/ipalib/plugins/permission.py
@@ -474,10 +474,10 @@ class permission_find(LDAPSearch):
                     dn = permission['dn']
                     del permission['dn']
                     if pkey_only:
-                        new_entry = (dn, {self.obj.primary_key.name: \
-                                          permission[self.obj.primary_key.name]})
+                        pk = self.obj.primary_key.name
+                        new_entry = ldap.make_entry(dn, {pk: permission[pk]})
                     else:
-                        new_entry = (dn, permission)
+                        new_entry = ldap.make_entry(dn, permission)
 
                     if (dn, permission) not in entries:
                        if len(entries) < max_entries:
diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py
index 907c01b..f6a0257 100644
--- a/ipalib/plugins/sudorule.py
+++ b/ipalib/plugins/sudorule.py
@@ -642,7 +642,9 @@ class sudorule_add_option(LDAPQuery):
             dn, attrs_list, normalize=self.obj.normalize_dn
             )
 
-        return dict(result=dict(entry_attrs))
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
+        return dict(result=entry_attrs)
 
     def output_for_cli(self, textui, result, cn, **options):
         textui.print_dashed(_('Added option "%(option)s" to Sudo Rule "%(rule)s"') % \
@@ -697,7 +699,9 @@ class sudorule_remove_option(LDAPQuery):
             dn, attrs_list, normalize=self.obj.normalize_dn
             )
 
-        return dict(result=dict(entry_attrs))
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+
+        return dict(result=entry_attrs)
 
     def output_for_cli(self, textui, result, cn, **options):
         textui.print_dashed(_('Removed option "%(option)s" from Sudo Rule "%(rule)s"') % \
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index a33a7ce..6f7e1aa 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -255,7 +255,7 @@ sides.
                          base_dn = DN(api.env.container_trusts, api.env.basedn),
                          filter = trust_filter)
 
-        result['result'] = dict(trusts[0][1])
+        result['result'] = entry_to_dict(trusts[0][1], **options)
         result['result']['trusttype'] = [trust_type_string(result['result']['ipanttrusttype'][0])]
         result['result']['trustdirection'] = [trust_direction_string(result['result']['ipanttrustdirection'][0])]
         result['result']['truststatus'] = [trust_status_string(result['verified'])]
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 80bdc39..979ade1 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -838,15 +838,14 @@ class user_status(LDAPQuery):
                     other_ldap.connect(ccache=os.environ['KRB5CCNAME'])
                 except Exception, e:
                     self.error("user_status: Connecting to %s failed with %s" % (host, str(e)))
-                    newresult = dict()
-                    newresult['dn'] = dn
+                    newresult = ldap.make_entry(dn)
                     newresult['server'] = _("%(host)s failed: %(error)s") % dict(host=host, error=str(e))
                     entries.append(newresult)
                     count += 1
                     continue
             try:
                 entry = other_ldap.get_entry(dn, attr_list)
-                newresult = dict()
+                newresult = ldap.make_entry(dn)
                 for attr in ['krblastsuccessfulauth', 'krblastfailedauth']:
                     newresult[attr] = entry[1].get(attr, [u'N/A'])
                 newresult['krbloginfailedcount'] = entry[1].get('krbloginfailedcount', u'0')
@@ -860,7 +859,6 @@ class user_status(LDAPQuery):
                         except Exception, e:
                             self.debug("time conversion failed with %s" % str(e))
                             pass
-                newresult['dn'] = dn
                 newresult['server'] = host
                 if options.get('raw', False):
                     time_format = '%Y%m%d%H%M%SZ'
@@ -876,8 +874,7 @@ class user_status(LDAPQuery):
                 self.obj.handle_not_found(*keys)
             except Exception, e:
                 self.error("user_status: Retrieving status for %s failed with %s" % (dn, str(e)))
-                newresult = dict()
-                newresult['dn'] = dn
+                newresult = ldap.make_entry(dn)
                 newresult['server'] = _("%(host)s failed") % dict(host=host)
                 entries.append(newresult)
                 count += 1
diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
index 43e4d8b..20c11b4 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -1234,7 +1234,7 @@ class LDAPClient(object):
             return ([], [])
 
         search_entry_dn = ldap.filter.escape_filter_chars(str(entry_dn))
-        attr_list = ["dn", "memberof"]
+        attr_list = ["memberof"]
         searchfilter = "(|(member=%s)(memberhost=%s)(memberuser=%s))" % (
             search_entry_dn, search_entry_dn, search_entry_dn)
 
@@ -1348,7 +1348,7 @@ class LDAPClient(object):
             return entries
 
         dn, group = self.get_entry(
-            group_dn, ['dn', 'member'],
+            group_dn, ['member'],
             size_limit=size_limit, time_limit=time_limit)
         real_members = group.get('member', [])
 
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index c3d7d33..6f6c206 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -469,7 +469,6 @@ class ldap2(LDAPClient, CrudBackend):
         assert isinstance(dn, DN)
 
         (dn, entry_attrs) = self.get_entry(dn, attrs_list)
-        entry_attrs['dn'] = dn
         return entry_attrs
 
     def create(self, **kw):
@@ -545,7 +544,6 @@ class ldap2(LDAPClient, CrudBackend):
             filter, attrs_list, base_dn, scope
         )
         for (dn, entry_attrs) in entries:
-            entry_attrs['dn'] = [dn]
             output.append(entry_attrs)
 
         if truncated:
-- 
1.8.1

>From 15acfcdaa1f2c37a7e10ee165e89f6d13ee3d7ea Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 31 Jan 2013 11:56:00 +0100
Subject: [PATCH 2/4] Preserve case of attribute names in LDAPEntry.

---
 ipalib/plugins/baseldap.py |   5 ++-
 ipalib/plugins/cert.py     |   6 +--
 ipalib/plugins/pwpolicy.py |   6 +--
 ipaserver/ipaldap.py       | 102 +++++++++++++++++++++++++++++++++------------
 4 files changed, 86 insertions(+), 33 deletions(-)

diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 74e2384..a0708d9 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -230,7 +230,10 @@ def entry_from_entry(entry, newentry):
         entry[e] = newentry[e]
 
 def entry_to_dict(entry, **options):
-    result = dict(entry)
+    if options.get('raw', False):
+        result = dict(entry)
+    else:
+        result = dict((k.lower(), v) for (k, v) in entry.iteritems())
     if options.get('all', False):
         result['dn'] = entry.dn
     return result
diff --git a/ipalib/plugins/cert.py b/ipalib/plugins/cert.py
index 3aa0162..52c242d 100644
--- a/ipalib/plugins/cert.py
+++ b/ipalib/plugins/cert.py
@@ -313,11 +313,11 @@ class cert_request(VirtualCommand):
         # going to add it
         try:
             if not principal.startswith('host/'):
-                service = api.Command['service_show'](principal, all=True, raw=True)['result']
+                service = api.Command['service_show'](principal, all=True)['result']
                 dn = service['dn']
             else:
                 hostname = get_host_from_principal(principal)
-                service = api.Command['host_show'](hostname, all=True, raw=True)['result']
+                service = api.Command['host_show'](hostname, all=True)['result']
                 dn = service['dn']
         except errors.NotFound, e:
             if not add:
@@ -342,7 +342,7 @@ class cert_request(VirtualCommand):
             for name in subjectaltname:
                 name = unicode(name)
                 try:
-                    hostentry = api.Command['host_show'](name, all=True, raw=True)['result']
+                    hostentry = api.Command['host_show'](name, all=True)['result']
                     hostdn = hostentry['dn']
                 except errors.NotFound:
                     # We don't want to issue any certificates referencing
diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py
index 5ae07c4..6c8ad8d 100644
--- a/ipalib/plugins/pwpolicy.py
+++ b/ipalib/plugins/pwpolicy.py
@@ -305,12 +305,12 @@ class pwpolicy(LDAPObject):
         existing_entry = {}
         if not add: # then read existing entry
             existing_entry = self.api.Command.pwpolicy_show(keys[-1],
-                all=True, raw=True,
+                all=True,
             )['result']
             if minlife is None and 'krbminpwdlife' in existing_entry:
-                minlife = int(existing_entry['krbminpwdlife'][0])
+                minlife = int(existing_entry['krbminpwdlife'][0]) * 3600
             if maxlife is None and 'krbmaxpwdlife' in existing_entry:
-                maxlife = int(existing_entry['krbmaxpwdlife'][0])
+                maxlife = int(existing_entry['krbmaxpwdlife'][0]) * 86400
 
         if maxlife is not None and minlife is not None:
             if minlife > maxlife:
diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
index 20c11b4..6268ac0 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -578,7 +578,7 @@ class IPASimpleLDAPObject(object):
 # r[0] == r.dn
 # r[1] == r.data
 class LDAPEntry(dict):
-    __slots__ = ('_dn', '_orig')
+    __slots__ = ('_dn', '_names', '_orig')
 
     def __init__(self, _dn=None, _obj=None, **kwargs):
         super(LDAPEntry, self).__init__()
@@ -599,6 +599,7 @@ class LDAPEntry(dict):
 
         self._dn = _dn
         self._orig = orig
+        self._names = CIDict()
 
         if orig is None:
             self.commit()
@@ -625,23 +626,9 @@ class LDAPEntry(dict):
         # FIXME: for backwards compatibility only
         return self._orig
 
-    def _attr_name(self, name):
-        if not isinstance(name, basestring):
-            raise TypeError(
-                "attribute name must be unicode or str, got %s object %r" % (
-                    name.__class__.__name__, name))
-        if isinstance(name, str):
-            name = name.decode('ascii')
-        return name.lower()
-
-    def _init_iter(self, _obj, **kwargs):
-        _obj = dict(_obj, **kwargs)
-        for (k, v) in _obj.iteritems():
-            yield (self._attr_name(k), v)
-
     def __repr__(self):
-        dict_repr = super(LDAPEntry, self).__repr__()
-        return '%s(%s, %s)' % (type(self).__name__, repr(self._dn), dict_repr)
+        return '%s(%r, %s)' % (type(self).__name__, self._dn,
+            super(LDAPEntry, self).__repr__())
 
     def copy(self):
         return LDAPEntry(self)
@@ -650,14 +637,48 @@ class LDAPEntry(dict):
         self._orig = self
         self._orig = deepcopy(self)
 
+    def _attr_name(self, name):
+        if not isinstance(name, (unicode, str)):
+            raise TypeError(
+                "attribute name must be unicode or str, got %s object %r" % (
+                    name.__class__.__name__, name))
+
+        if isinstance(name, str):
+            name = name.decode('utf-8')
+
+        return name
+
     def __setitem__(self, name, value):
-        super(LDAPEntry, self).__setitem__(self._attr_name(name), value)
+        name = self._attr_name(name)
+
+        if name in self._names:
+            oldname = self._names[name]
+
+            if oldname != name:
+                for (altname, keyname) in self._names.iteritems():
+                    if keyname == oldname:
+                        self._names[altname] = name
+
+                super(LDAPEntry, self).__delitem__(oldname)
+        else:
+            self._names[name] = name
+
+        super(LDAPEntry, self).__setitem__(name, value)
 
     def setdefault(self, name, default):
-        return super(LDAPEntry, self).setdefault(self._attr_name(name), default)
+        if name not in self:
+            self[name] = default
+        return self[name]
 
     def update(self, _obj={}, **kwargs):
-        super(LDAPEntry, self).update(self._init_iter(_obj, **kwargs))
+        _obj = dict(_obj, **kwargs)
+        for (name, value) in _obj.iteritems():
+            self[name] = value
+
+    def _get_attr_name(self, name):
+        name = self._attr_name(name)
+        name = self._names[name]
+        return name
 
     def __getitem__(self, name):
         # for python-ldap tuple compatibility
@@ -666,10 +687,24 @@ class LDAPEntry(dict):
         elif name == 1:
             return self
 
-        return super(LDAPEntry, self).__getitem__(self._attr_name(name))
+        return super(LDAPEntry, self).__getitem__(self._get_attr_name(name))
 
     def get(self, name, default=None):
-        return super(LDAPEntry, self).get(self._attr_name(name), default)
+        try:
+            name = self._get_attr_name(name)
+        except KeyError:
+            return default
+
+        return super(LDAPEntry, self).get(name, default)
+
+    def _del_attr_name(self, name):
+        name = self._get_attr_name(name)
+
+        for (altname, keyname) in self._names.items():
+            if keyname == name:
+                del self._names[altname]
+
+        return name
 
     def get_single(self, name, default=_missing):
         """Return a single attribute value
@@ -688,16 +723,31 @@ class LDAPEntry(dict):
         return values[0]
 
     def __delitem__(self, name):
-        super(LDAPEntry, self).__delitem__(self._attr_name(name))
+        super(LDAPEntry, self).__delitem__(self._del_attr_name(name))
 
     def pop(self, name, *default):
-        return super(LDAPEntry, self).pop(self._attr_name(name), *default)
+        try:
+            name = self._del_attr_name(name)
+        except KeyError:
+            if not default:
+                raise
+
+        return super(LDAPEntry, self).pop(name, *default)
+
+    def popitem(self):
+        name, value = super(LDAPEntry, self).popitem()
+        self._del_attr_name(name)
+        return (name, value)
+
+    def clear(self):
+        super(LDAPEntry, self).clear()
+        self._names.clear()
 
     def __contains__(self, name):
-        return super(LDAPEntry, self).__contains__(self._attr_name(name))
+        return self._names.has_key(self._attr_name(name))
 
     def has_key(self, name):
-        return super(LDAPEntry, self).has_key(self._attr_name(name))
+        return name in self
 
     # for python-ldap tuple compatibility
     def __iter__(self):
-- 
1.8.1

>From bea28563aa7adcea801ac0538670dbca3ed9c033 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 31 Jan 2013 11:56:47 +0100
Subject: [PATCH 3/4] Aggregate IPASimpleLDAPObject in LDAPEntry.

---
 ipaserver/ipaldap.py              | 15 +++++++++++----
 ipaserver/plugins/ldap2.py        |  2 +-
 tests/test_ipaserver/test_ldap.py |  5 ++++-
 3 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
index 6268ac0..01fa0c1 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -398,7 +398,7 @@ class IPASimpleLDAPObject(object):
             original_dn = dn_tuple[0]
             original_attrs = dn_tuple[1]
 
-            ipa_entry = LDAPEntry(DN(original_dn))
+            ipa_entry = LDAPEntry(self, DN(original_dn))
 
             for attr, original_values in original_attrs.items():
                 target_type = self._SYNTAX_MAPPING.get(self.get_syntax(attr), unicode_from_utf8)
@@ -578,11 +578,16 @@ class IPASimpleLDAPObject(object):
 # r[0] == r.dn
 # r[1] == r.data
 class LDAPEntry(dict):
-    __slots__ = ('_dn', '_names', '_orig')
+    __slots__ = ('_conn', '_dn', '_names', '_orig')
 
-    def __init__(self, _dn=None, _obj=None, **kwargs):
+    def __init__(self, _conn, _dn=None, _obj=None, **kwargs):
         super(LDAPEntry, self).__init__()
 
+        if isinstance(_conn, LDAPEntry):
+            assert _dn is None
+            _dn = _conn
+            _conn = _conn._conn()
+
         if isinstance(_dn, LDAPEntry):
             assert _obj is None
             _obj = _dn
@@ -595,8 +600,10 @@ class LDAPEntry(dict):
                 _obj = {}
             orig = None
 
+        assert isinstance(_conn, IPASimpleLDAPObject)
         assert isinstance(_dn, DN)
 
+        self._conn = lambda: _conn  # do not deepcopy me!
         self._dn = _dn
         self._orig = orig
         self._names = CIDict()
@@ -960,7 +967,7 @@ class LDAPClient(object):
         return DN((primary_key, entry_attrs[primary_key]), parent_dn)
 
     def make_entry(self, _dn=None, _obj=None, **kwargs):
-        return LDAPEntry(_dn, _obj, **kwargs)
+        return LDAPEntry(self.conn, _dn, _obj, **kwargs)
 
     # generating filters for find_entry
     # some examples:
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 6f6c206..2872c80 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -200,7 +200,7 @@ class ldap2(LDAPClient, CrudBackend):
 
         try:
             config_entry = getattr(context, 'config_entry')
-            return copy.deepcopy(config_entry)
+            return copy.deepcopy(self.conn.make_entry(config_entry))
         except AttributeError:
             # Not in our context yet
             pass
diff --git a/tests/test_ipaserver/test_ldap.py b/tests/test_ipaserver/test_ldap.py
index 06d5d4e..4b0c9d7 100644
--- a/tests/test_ipaserver/test_ldap.py
+++ b/tests/test_ipaserver/test_ldap.py
@@ -156,7 +156,10 @@ class test_ldap(object):
         dn1 = DN(('cn', cn1[0]))
         dn2 = DN(('cn', cn2[0]))
 
-        e = LDAPEntry(dn1, cn=cn1)
+        self.conn = ldap2(shared_instance=False, ldap_uri=self.ldapuri)
+        self.conn.connect()
+
+        e = LDAPEntry(self.conn.conn, dn1, cn=cn1)
         assert e.dn is dn1
         assert 'CN' in e
         assert e['CN'] is cn1
-- 
1.8.1

>From 017187c245ab4f16ef375a460be16cad62781d02 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 31 Jan 2013 11:57:02 +0100
Subject: [PATCH 4/4] Support attributes with multiple names in LDAPEntry.

---
 ipalib/plugins/host.py |  2 --
 ipaserver/ipaldap.py   | 12 ++++++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index e1c07b5..c9bb1ac 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -406,7 +406,6 @@ class host_add(LDAPCreate):
             util.validate_host_dns(self.log, keys[-1])
         if 'locality' in entry_attrs:
             entry_attrs['l'] = entry_attrs['locality']
-            del entry_attrs['locality']
         entry_attrs['cn'] = keys[-1]
         entry_attrs['serverhostname'] = keys[-1].split('.', 1)[0]
         if 'userpassword' not in entry_attrs and not options.get('random', False):
@@ -629,7 +628,6 @@ class host_mod(LDAPUpdate):
             raise errors.ACIError(info=_('cn is immutable'))
         if 'locality' in entry_attrs:
             entry_attrs['l'] = entry_attrs['locality']
-            del entry_attrs['locality']
         if 'krbprincipalname' in entry_attrs:
             (dn, entry_attrs_old) = ldap.get_entry(
                 dn, ['objectclass', 'krbprincipalname']
diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
index 01fa0c1..b8ce270 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -670,6 +670,18 @@ class LDAPEntry(dict):
         else:
             self._names[name] = name
 
+            try:
+                schema = self._conn().schema
+            except:
+                pass
+            else:
+                attrtype = schema.get_obj(ldap.schema.AttributeType,
+                    name.encode('utf-8'))
+                if attrtype is not None:
+                    for altname in attrtype.names:
+                        altname = altname.decode('utf-8')
+                        self._names[altname] = name
+
         super(LDAPEntry, self).__setitem__(name, value)
 
     def setdefault(self, name, default):
-- 
1.8.1

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

Reply via email to