On 19.2.2013 17:30, Rob Crittenden wrote:
I think dropping raw=True in patch 99.3 is going to break a check later
where we look at the managedby attribute. Without raw this will be
managedby_host.


Fixed, thanks for the catch.

I have also made 2 changes to patch 100 (made sure the entry returned by ldap2.get_ipa_config is using the correct IPASimpleLDAPObject and changed LDAPEntry.clone to be less fragile).

Updated (and rebased) patches attached.

Honza

--
Jan Cholasta
>From 78d3da5cc8837ae2f3be9783df6d19af2683f8fe 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 78a6d98..2d77213 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -311,7 +311,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 90f667e..66c9a45 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -1244,7 +1244,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)
 
@@ -1358,7 +1358,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 7667952..c096421 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -476,7 +476,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):
@@ -552,7 +551,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 c5fc88281f6eb18906dbd44e76fcf677b02ffaf7 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            |   8 +--
 ipalib/plugins/pwpolicy.py        |   6 +--
 ipaserver/ipaldap.py              | 103 ++++++++++++++++++++++++++++----------
 tests/test_ipaserver/test_ldap.py |  10 +++-
 5 files changed, 97 insertions(+), 35 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 51493c3..e6aecbb 100644
--- a/ipalib/plugins/cert.py
+++ b/ipalib/plugins/cert.py
@@ -347,11 +347,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:
@@ -376,7 +376,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
@@ -386,7 +386,7 @@ class cert_request(VirtualCommand):
                         'subject alt name %s in certificate request') % name)
                 authprincipal = getattr(context, 'principal')
                 if authprincipal.startswith("host/"):
-                    if not hostdn in service.get('managedby', []):
+                    if not hostdn in service.get('managedby_host', []):
                         raise errors.ACIError(info=_(
                             "Insufficient privilege to create a certificate "
                             "with subject alt name '%s'.") % name)
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 66c9a45..9fe8cf5 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -583,7 +583,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__()
@@ -604,6 +604,7 @@ class LDAPEntry(dict):
 
         self._dn = _dn
         self._orig = orig
+        self._names = CIDict()
 
         if orig is None:
             self.commit()
@@ -630,39 +631,59 @@ class LDAPEntry(dict):
         # FIXME: for backwards compatibility only
         return self._orig
 
+    def __repr__(self):
+        return '%s(%r, %s)' % (type(self).__name__, self._dn,
+            super(LDAPEntry, self).__repr__())
+
+    def copy(self):
+        return LDAPEntry(self)
+
+    def commit(self):
+        self._orig = self
+        self._orig = deepcopy(self)
+
     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()
+            name = name.decode('utf-8')
 
-    def _init_iter(self, _obj, **kwargs):
-        _obj = dict(_obj, **kwargs)
-        for (k, v) in _obj.iteritems():
-            yield (self._attr_name(k), v)
+        return name
 
-    def __repr__(self):
-        dict_repr = super(LDAPEntry, self).__repr__()
-        return '%s(%s, %s)' % (type(self).__name__, repr(self._dn), dict_repr)
+    def __setitem__(self, name, value):
+        name = self._attr_name(name)
 
-    def copy(self):
-        return LDAPEntry(self)
+        if self._names.has_key(name):
+            oldname = self._names[name]
 
-    def commit(self):
-        self._orig = self
-        self._orig = deepcopy(self)
+            if oldname != name:
+                for (altname, keyname) in self._names.iteritems():
+                    if keyname == oldname:
+                        self._names[altname] = name
 
-    def __setitem__(self, name, value):
-        super(LDAPEntry, self).__setitem__(self._attr_name(name), value)
+                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
@@ -671,10 +692,15 @@ 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 single_value(self, name, default=_missing):
         """Return a single attribute value
@@ -685,7 +711,8 @@ class LDAPEntry(dict):
         If the entry is missing and default is not given, raise KeyError.
         """
         try:
-            values = super(LDAPEntry, self).__getitem__(self._attr_name(name))
+            attr_name = self._get_attr_name(name)
+            values = super(LDAPEntry, self).__getitem__(attr_name)
         except KeyError:
             if default is _missing:
                 raise
@@ -697,17 +724,41 @@ class LDAPEntry(dict):
                 '%s has %s values, one expected' % (name, len(values)))
         return values[0]
 
+    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 __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):
diff --git a/tests/test_ipaserver/test_ldap.py b/tests/test_ipaserver/test_ldap.py
index 06d5d4e..df777c8 100644
--- a/tests/test_ipaserver/test_ldap.py
+++ b/tests/test_ipaserver/test_ldap.py
@@ -158,18 +158,26 @@ class test_ldap(object):
 
         e = LDAPEntry(dn1, cn=cn1)
         assert e.dn is dn1
+        assert u'cn' in e
+        assert u'cn' in e.keys()
         assert 'CN' in e
+        assert 'CN' not in e.keys()
         assert e['CN'] is cn1
         assert e['CN'] is e[u'cn']
 
         e.dn = dn2
         assert e.dn is dn2
 
-        e['cn'] = cn2
+        e['CN'] = cn2
+        assert u'cn' in e
+        assert u'cn' not in e.keys()
         assert 'CN' in e
+        assert 'CN' in e.keys()
         assert e['CN'] is cn2
         assert e['CN'] is e[u'cn']
 
         del e['CN']
         assert 'CN' not in e
+        assert 'CN' not in e.keys()
         assert u'cn' not in e
+        assert u'cn' not in e.keys()
-- 
1.8.1

>From 6a2f7a7361877c2ab087715a24924b924db7f0f2 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              | 53 +++++++++++++++++++++++++++++++++++----
 ipaserver/plugins/ldap2.py        |  8 +++---
 tests/test_ipaserver/test_ldap.py |  5 +++-
 3 files changed, 56 insertions(+), 10 deletions(-)

diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
index 9fe8cf5..06e8464 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -403,7 +403,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)
@@ -583,11 +583,31 @@ 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):
+        """
+        LDAPEntry constructor.
+
+        Takes 1 to 3 positional arguments and an arbitrary number of keyword
+        arguments. The 3 forms of positional arguments are:
+
+          * LDAPEntry(entry) - create a shallow copy of an existing LDAPEntry.
+          * LDAPEntry(dn, entry) - create a shallow copy of an existing
+            LDAPEntry with a different DN.
+          * LDAPEntry(conn, dn, mapping) - create a new LDAPEntry using the
+            specified IPASimpleLDAPObject and DN and optionally initialize
+            attributes from the specified mapping object.
+
+        Keyword arguments can be used to override values of specific attributes.
+        """
         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
@@ -600,8 +620,10 @@ class LDAPEntry(dict):
                 _obj = {}
             orig = None
 
+        assert isinstance(_conn, IPASimpleLDAPObject)
         assert isinstance(_dn, DN)
 
+        self._conn = _conn
         self._dn = _dn
         self._orig = orig
         self._names = CIDict()
@@ -611,6 +633,10 @@ class LDAPEntry(dict):
 
         self.update(_obj, **kwargs)
 
+    @property
+    def conn(self):
+        return self._conn
+
     # properties for Entry and Entity compatibility
     @property
     def dn(self):
@@ -638,9 +664,26 @@ class LDAPEntry(dict):
     def copy(self):
         return LDAPEntry(self)
 
+    def clone(self):
+        result = LDAPEntry(self._conn, self._dn)
+
+        for name in self.iterkeys():
+            super(LDAPEntry, result).__setitem__(
+                name, deepcopy(super(LDAPEntry, self).__getitem__(name)))
+
+        result._names = deepcopy(self._names)
+        if self._orig is not self:
+            result._orig = self._orig.clone()
+
+        return result
+
     def commit(self):
+        """
+        Make the current state of the entry a new reference point for change
+        tracking.
+        """
         self._orig = self
-        self._orig = deepcopy(self)
+        self._orig = self.clone()
 
     def _attr_name(self, name):
         if not isinstance(name, basestring):
@@ -971,7 +1014,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 c096421..a2241ac 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -27,7 +27,6 @@ Backend plugin for LDAP.
 # binding encodes them into the appropriate representation. This applies to
 # everything except the CrudBackend methods, where dn is part of the entry dict.
 
-import copy
 import os
 import re
 import pwd
@@ -207,7 +206,8 @@ class ldap2(LDAPClient, CrudBackend):
 
         try:
             config_entry = getattr(context, 'config_entry')
-            return copy.deepcopy(config_entry)
+            if config_entry.conn is self.conn:
+                return config_entry.clone()
         except AttributeError:
             # Not in our context yet
             pass
@@ -220,11 +220,11 @@ class ldap2(LDAPClient, CrudBackend):
                 raise errors.LimitsExceeded()
             config_entry = entry[0]
         except errors.NotFound:
-            config_entry = {}
+            config_entry = self.make_entry(cdn)
         for a in self.config_defaults:
             if a not in config_entry:
                 config_entry[a] = self.config_defaults[a]
-        context.config_entry = copy.deepcopy(config_entry)
+        context.config_entry = config_entry.clone()
         return config_entry
 
     def has_upg(self):
diff --git a/tests/test_ipaserver/test_ldap.py b/tests/test_ipaserver/test_ldap.py
index df777c8..7e5ba39 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 u'cn' in e
         assert u'cn' in e.keys()
-- 
1.8.1

>From 5dd111703fe07a5134c28a7211b5c9acc98251e6 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 ++++++++++++
 tests/test_ipaserver/test_ldap.py | 10 ++++++++--
 3 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index f464127..affc3d7 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -412,7 +412,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):
@@ -635,7 +634,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 06e8464..62b80fb 100644
--- a/ipaserver/ipaldap.py
+++ b/ipaserver/ipaldap.py
@@ -711,6 +711,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):
diff --git a/tests/test_ipaserver/test_ldap.py b/tests/test_ipaserver/test_ldap.py
index 7e5ba39..a35f783 100644
--- a/tests/test_ipaserver/test_ldap.py
+++ b/tests/test_ipaserver/test_ldap.py
@@ -165,17 +165,21 @@ class test_ldap(object):
         assert u'cn' in e.keys()
         assert 'CN' in e
         assert 'CN' not in e.keys()
+        assert 'commonName' in e
+        assert 'commonName' not in e.keys()
         assert e['CN'] is cn1
         assert e['CN'] is e[u'cn']
 
         e.dn = dn2
         assert e.dn is dn2
 
-        e['CN'] = cn2
+        e['commonName'] = cn2
         assert u'cn' in e
         assert u'cn' not in e.keys()
         assert 'CN' in e
-        assert 'CN' in e.keys()
+        assert 'CN' not in e.keys()
+        assert 'commonName' in e
+        assert 'commonName' in e.keys()
         assert e['CN'] is cn2
         assert e['CN'] is e[u'cn']
 
@@ -184,3 +188,5 @@ class test_ldap(object):
         assert 'CN' not in e.keys()
         assert u'cn' not in e
         assert u'cn' not in e.keys()
+        assert 'commonName' not in e
+        assert 'commonName' not in e.keys()
-- 
1.8.1

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

Reply via email to