Dne 9.4.2015 v 17:28 Petr Viktorin napsal(a):
On 04/08/2015 03:18 PM, Jan Cholasta wrote:
Hi,
the attached patches remove IPASimpleLDAPObject from ipaldap.
As a result, the one and only IPA LDAP API is the LDAPClient API.
This is definitely an improvement :)
0408: ACK (woohoo!)
0409: ACK
0410:
I quite like the new __init__ signature, and the context manager
functionality.
Can you add a comment for the `object.__setattr__(self, '_conn', None)`
in _disconnect? It's a real eyesore.
Added.
0411: ACK
0412: Can _force_schema_updates be set already in __init__?
Unfortunately not. ldap2 is now used with different API instances, and
the current API instance is not available in __init__.
I'm working on an additional patch for
<https://fedorahosted.org/freeipa/ticket/3090> to pass the API object to
plugins in their __init__ (because why do it somewhere else), which will
fix this.
0413: ACK
0414: ACK
0415: ACK
0416: I think you should show off the `with` statement support here.
Fixed.
0417: ... and here
Fixed.
0418: ACK
0419: ACK
0420: ACK
0421: ACK
Added a comment about ldap2's locking here as well.
Also moved LDAPClient.schema back to its original location to save some
lines in the patch.
0422: ACK, and good riddance
You missed 423 :-)
Thanks for the review.
Updated patches attached.
--
Jan Cholasta
>From fc74c1b7dfac2d8dfb2cf638dd53852746227c7b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 23 Oct 2014 15:44:40 +0200
Subject: [PATCH 01/16] ldap: Drop python-ldap tuple compatibility
---
ipapython/ipaldap.py | 69 +++-------------------------------------------------
1 file changed, 3 insertions(+), 66 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index ce07006..5ba2b8b 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -90,12 +90,6 @@ def value_to_utf8(val):
return unicode(val).encode('utf-8')
-# FIXME: Remove when python-ldap tuple compatibility is dropped
-def raise_deprecation_error():
- raise RuntimeError(
- "this API has been deprecated, see http://www.freeipa.org/page/"
- "HowTo/Migrate_your_code_to_the_new_LDAP_API")
-
class _ServerSchema(object):
'''
Properties of a schema retrieved from an LDAP server.
@@ -670,13 +664,6 @@ class IPASimpleLDAPObject(object):
return self.conn.unbind_s()
-# Make python-ldap tuple style result compatible with Entry and Entity
-# objects by allowing access to the dn (tuple index 0) via the 'dn'
-# attribute name and the attr dict (tuple index 1) via the 'data'
-# attribute name. Thus:
-# r = result[0]
-# r[0] == r.dn
-# r[1] == r.data
class LDAPEntry(collections.MutableMapping):
__slots__ = ('_conn', '_dn', '_names', '_nice', '_raw', '_sync',
'_not_list', '_orig', '_raw_view', '_single_value_view')
@@ -935,10 +922,6 @@ class LDAPEntry(collections.MutableMapping):
return value
def __getitem__(self, name):
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- if name in (0, 1):
- raise_deprecation_error()
-
return self._get_nice(name)
def __delitem__(self, name):
@@ -1023,49 +1006,9 @@ class LDAPEntry(collections.MutableMapping):
return modlist
- # FIXME: Remove when python-ldap tuple compatibility is dropped
def __iter__(self):
- raise_deprecation_error()
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def iterkeys(self):
- return self._nice.iterkeys()
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def itervalues(self):
- for name in self.iterkeys():
- yield self[name]
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def iteritems(self):
- for name in self.iterkeys():
- yield (name, self[name])
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def keys(self):
- return list(self.iterkeys())
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def values(self):
- return list(self.itervalues())
+ return iter(self._nice)
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def items(self):
- return list(self.iteritems())
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def update(self, _obj={}, **kwargs):
- _obj = dict(_obj, **kwargs)
- super(LDAPEntry, self).update(_obj)
-
- # FIXME: Remove when python-ldap tuple compatibility is dropped
- def popitem(self):
- try:
- name = self.iterkeys().next()
- except StopIteration:
- raise KeyError
-
- return name, self.pop(name)
class LDAPEntryView(collections.MutableMapping):
__slots__ = ('_entry',)
@@ -1577,14 +1520,11 @@ class LDAPClient(object):
raise errors.LimitsExceeded()
return entries[0]
- def add_entry(self, entry, entry_attrs=None):
+ def add_entry(self, entry):
"""Create a new entry.
This should be called as add_entry(entry).
"""
- if entry_attrs is not None:
- raise_deprecation_error()
-
# remove all [] values (python-ldap hates 'em)
attrs = dict((k, v) for k, v in entry.raw.iteritems() if v)
@@ -1610,14 +1550,11 @@ 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 update_entry(self, entry, entry_attrs=None):
+ def update_entry(self, entry):
"""Update entry's attributes.
This should be called as update_entry(entry).
"""
- if entry_attrs is not None:
- raise_deprecation_error()
-
# generate modlist
modlist = entry.generate_modlist()
if not modlist:
--
2.1.0
>From 955eb892b6563ecde1f28b2f7520a67cad199bb9 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 21 Jan 2015 11:14:19 +0000
Subject: [PATCH 02/16] ldap: Remove unused IPAdmin methods
---
ipapython/ipaldap.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 5ba2b8b..d4beaee 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1720,13 +1720,5 @@ class IPAdmin(LDAPClient):
# FIXME: for backwards compatibility only
return self.conn.modify_s(*args, **kwargs)
- def set_option(self, *args, **kwargs):
- # FIXME: for backwards compatibility only
- return self.conn.set_option(*args, **kwargs)
-
- def encode(self, *args, **kwargs):
- # FIXME: for backwards compatibility only
- return self.conn.encode(*args, **kwargs)
-
def unbind(self, *args, **kwargs):
return self.conn.unbind_s(*args, **kwargs)
--
2.1.0
>From 0bf04200baf01434fd948e4293d7fe10b309a7ec Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 26 Jan 2015 15:16:01 +0000
Subject: [PATCH 03/16] ldap: Add connection management to LDAPClient
---
ipapython/ipaldap.py | 73 ++++++++++++++++++++++++++++++++++++++++------
ipaserver/plugins/ldap2.py | 5 +++-
2 files changed, 68 insertions(+), 10 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index d4beaee..683c5e1 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1083,13 +1083,22 @@ class LDAPClient(object):
SCOPE_ONELEVEL = ldap.SCOPE_ONELEVEL
SCOPE_SUBTREE = ldap.SCOPE_SUBTREE
- def __init__(self, ldap_uri):
+ def __init__(self, ldap_uri, start_tls=False, force_schema_updates=False,
+ no_schema=False, decode_attrs=True):
self.ldap_uri = ldap_uri
+ self._start_tls = start_tls
+ self._force_schema_updates = force_schema_updates
+ self._no_schema = no_schema
+ self._decode_attrs = decode_attrs
+
self.log = log_mgr.get_logger(self)
- self._init_connection()
+ self._conn = None
- def _init_connection(self):
- self.conn = None
+ self._connect()
+
+ @property
+ def conn(self):
+ return self._conn
@contextlib.contextmanager
def error_handler(self, arg_desc=None):
@@ -1186,6 +1195,46 @@ class LDAPClient(object):
reason=_('objectclass %s not found') % oc)
return [unicode(a).lower() for a in list(set(allowed_attributes))]
+ def __del__(self):
+ self.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+ def close(self):
+ """
+ Close the connection.
+ """
+ if self._conn is not None:
+ self._disconnect()
+
+ def _connect(self):
+ if self._conn is not None:
+ raise errors.DatabaseError(
+ desc="Can't connect to server", info="Already connected")
+
+ with self.error_handler():
+ # bypass ldap2's locking
+ object.__setattr__(self, '_conn',
+ IPASimpleLDAPObject(self.ldap_uri,
+ self._force_schema_updates,
+ self._no_schema,
+ self._decode_attrs))
+
+ if self._start_tls:
+ self._conn.start_tls_s()
+
+ def _disconnect(self):
+ if self._conn is None:
+ raise errors.DatabaseError(
+ desc="Can't disconnect from server", info="Not connected")
+
+ # bypass ldap2's locking
+ object.__setattr__(self, '_conn', None)
+
def make_dn_from_attr(self, attr, value, parent_dn=None):
"""
Make distinguished name from attribute.
@@ -1626,7 +1675,7 @@ class IPAdmin(LDAPClient):
realm=None, protocol=None, force_schema_updates=True,
start_tls=False, ldap_uri=None, no_schema=False,
decode_attrs=True, sasl_nocanon=False, demand_cert=False):
- self.conn = None
+ self._conn = None
log_mgr.get_logger(self, True)
if debug and debug.lower() == "on":
ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
@@ -1646,10 +1695,10 @@ class IPAdmin(LDAPClient):
LDAPClient.__init__(self, ldap_uri)
with self.error_handler():
- self.conn = IPASimpleLDAPObject(ldap_uri,
- force_schema_updates=True,
- no_schema=no_schema,
- decode_attrs=decode_attrs)
+ self._conn = IPASimpleLDAPObject(ldap_uri,
+ force_schema_updates=True,
+ no_schema=no_schema,
+ decode_attrs=decode_attrs)
if demand_cert:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, True)
@@ -1661,6 +1710,12 @@ class IPAdmin(LDAPClient):
if start_tls:
self.conn.start_tls_s()
+ def _connect(self):
+ pass
+
+ def _disconnect(self):
+ pass
+
def __str__(self):
return self.host + ":" + str(self.port)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index fd4ed29..2671571 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -98,11 +98,14 @@ class ldap2(LDAPClient, CrudBackend):
except AttributeError:
return DN()
- def _init_connection(self):
+ def _connect(self):
# Connectible.conn is a proxy to thread-local storage;
# do not set it
pass
+ def _disconnect(self):
+ pass
+
def __del__(self):
if self.isconnected():
self.disconnect()
--
2.1.0
>From 0dad2a1245c15154884c43cf0b70b08043cb81c7 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 21 Nov 2014 19:45:34 +0100
Subject: [PATCH 04/16] ldap: Use LDAPClient connection management in IPAdmin
---
ipapython/ipaldap.py | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 683c5e1..9ddbb4d 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1692,14 +1692,11 @@ class IPAdmin(LDAPClient):
if not ldap_uri:
ldap_uri = self.__get_ldap_uri(protocol or self.__guess_protocol())
- LDAPClient.__init__(self, ldap_uri)
+ super(IPAdmin, self).__init__(
+ ldap_uri, force_schema_updates=force_schema_updates,
+ no_schema=no_schema, decode_attrs=decode_attrs)
with self.error_handler():
- self._conn = IPASimpleLDAPObject(ldap_uri,
- force_schema_updates=True,
- no_schema=no_schema,
- decode_attrs=decode_attrs)
-
if demand_cert:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, True)
self.conn.set_option(ldap.OPT_X_TLS_DEMAND, True)
@@ -1710,12 +1707,6 @@ class IPAdmin(LDAPClient):
if start_tls:
self.conn.start_tls_s()
- def _connect(self):
- pass
-
- def _disconnect(self):
- pass
-
def __str__(self):
return self.host + ":" + str(self.port)
--
2.1.0
>From 2f430ccf13466cf905e1f83707b2f81e70be48e2 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 21 Nov 2014 20:03:29 +0100
Subject: [PATCH 05/16] ldap: Use LDAPClient connection management in ldap2
---
ipaserver/plugins/ldap2.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 2671571..98b038a 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -145,10 +145,12 @@ class ldap2(LDAPClient, CrudBackend):
if debug_level:
_ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level)
+ object.__setattr__(self, '_force_schema_updates',
+ self.api.env.context in ('installer', 'updates'))
+ LDAPClient._connect(self)
+ conn = self._conn
+
with self.error_handler():
- force_updates = self.api.env.context in ('installer', 'updates')
- conn = IPASimpleLDAPObject(
- self.ldap_uri, force_schema_updates=force_updates)
if self.ldap_uri.startswith('ldapi://') and ccache:
conn.set_option(_ldap.OPT_HOST_NAME, self.api.env.host)
minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN)
@@ -200,6 +202,11 @@ class ldap2(LDAPClient, CrudBackend):
# ignore when trying to unbind multiple times
pass
+ try:
+ LDAPClient._disconnect(self)
+ except errors.PublicError:
+ # ignore when trying to unbind multiple times
+ pass
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
scope=_ldap.SCOPE_SUBTREE, time_limit=None,
--
2.1.0
>From e47af0adee748f34bb9f62eddc44dee1fa4e6d89 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 21 Nov 2014 20:08:17 +0100
Subject: [PATCH 06/16] ldap: Add bind and unbind methods to LDAPClient
---
ipapython/ipaldap.py | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 9ddbb4d..09f0c10 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1235,6 +1235,41 @@ class LDAPClient(object):
# bypass ldap2's locking
object.__setattr__(self, '_conn', None)
+ def simple_bind(self, bind_dn, bind_password, server_controls=None,
+ client_controls=None):
+ """
+ Perform simple bind operation.
+ """
+ with self.error_handler():
+ self._conn.simple_bind_s(
+ bind_dn, bind_password, server_controls, client_controls)
+
+ def external_bind(self, user_name, server_controls=None,
+ client_controls=None):
+ """
+ Perform SASL bind operation using the SASL EXTERNAL mechanism.
+ """
+ with self.error_handler():
+ auth_tokens = ldap.sasl.external(user_name)
+ self._conn.sasl_interactive_bind_s(
+ None, auth_tokens, server_controls, client_controls)
+
+ def gssapi_bind(self, server_controls=None, client_controls=None):
+ """
+ Perform SASL bind operation using the SASL GSSAPI mechanism.
+ """
+ with self.error_handler():
+ auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
+ self._conn.sasl_interactive_bind_s(
+ None, auth_tokens, server_controls, client_controls)
+
+ def unbind(self):
+ """
+ Perform unbind operation.
+ """
+ with self.error_handler():
+ self.conn.unbind_s()
+
def make_dn_from_attr(self, attr, value, parent_dn=None):
"""
Make distinguished name from attribute.
--
2.1.0
>From 6a3ce076b9b3fce5d44df7c03d85debbd9984c9c Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 21 Nov 2014 20:10:23 +0100
Subject: [PATCH 07/16] ldap: Use LDAPClient bind and unbind methods in IPAdmin
---
ipapython/ipaldap.py | 35 ++++++++++++++++-------------------
1 file changed, 16 insertions(+), 19 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 09f0c10..d28d8b4 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -1754,29 +1754,29 @@ class IPAdmin(LDAPClient):
wait_for_open_ports(host, int(port), timeout)
def __bind_with_wait(self, bind_func, timeout, *args, **kwargs):
- with self.error_handler():
- try:
- bind_func(*args, **kwargs)
- except (ldap.CONNECT_ERROR, ldap.SERVER_DOWN), e:
- if not timeout or 'TLS' in e.args[0].get('info', ''):
- # No connection to continue on if we have a TLS failure
- # https://bugzilla.redhat.com/show_bug.cgi?id=784989
- raise
- self.__wait_for_connection(timeout)
- bind_func(*args, **kwargs)
+ try:
+ bind_func(*args, **kwargs)
+ except errors.NetworkError as e:
+ if not timeout and 'TLS' in e.error:
+ # No connection to continue on if we have a TLS failure
+ # https://bugzilla.redhat.com/show_bug.cgi?id=784989
+ raise
+ except errors.DatabaseError:
+ pass
+ else:
+ return
+ self.__wait_for_connection(timeout)
+ bind_func(*args, **kwargs)
def do_simple_bind(self, binddn=DN(('cn', 'directory manager')), bindpw="",
timeout=DEFAULT_TIMEOUT):
- self.__bind_with_wait(self.conn.simple_bind_s, timeout, binddn, bindpw)
+ self.__bind_with_wait(self.simple_bind, timeout, binddn, bindpw)
def do_sasl_gssapi_bind(self, timeout=DEFAULT_TIMEOUT):
- self.__bind_with_wait(
- self.conn.sasl_interactive_bind_s, timeout, None, SASL_GSSAPI)
+ self.__bind_with_wait(self.gssapi_bind, timeout)
def do_external_bind(self, user_name=None, timeout=DEFAULT_TIMEOUT):
- auth_tokens = ldap.sasl.external(user_name)
- self.__bind_with_wait(
- self.conn.sasl_interactive_bind_s, timeout, None, auth_tokens)
+ self.__bind_with_wait(self.external_bind, timeout, user_name)
def do_bind(self, dm_password="", autobind=AUTOBIND_AUTO, timeout=DEFAULT_TIMEOUT):
if dm_password:
@@ -1800,6 +1800,3 @@ class IPAdmin(LDAPClient):
def modify_s(self, *args, **kwargs):
# FIXME: for backwards compatibility only
return self.conn.modify_s(*args, **kwargs)
-
- def unbind(self, *args, **kwargs):
- return self.conn.unbind_s(*args, **kwargs)
--
2.1.0
>From 291f747809bdd846900a4f5d934ae04e513d5ffe Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 21 Nov 2014 20:14:12 +0100
Subject: [PATCH 08/16] ldap: Use LDAPClient bind and unbind methods in ldap2
---
ipaserver/plugins/ldap2.py | 62 +++++++++++++++++++++-------------------------
1 file changed, 28 insertions(+), 34 deletions(-)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 98b038a..1e103dc 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -162,47 +162,41 @@ class ldap2(LDAPClient, CrudBackend):
conn.set_option(_ldap.OPT_X_SASL_SSF_MIN, minssf)
if maxssf < minssf:
conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf)
- if ccache is not None:
- if isinstance(ccache, krbV.CCache):
- principal = ccache.principal().name
- # Get a fully qualified CCACHE name (schema+name)
- # As we do not use the krbV.CCache object later,
- # we can safely overwrite it
- ccache = "%(type)s:%(name)s" % dict(type=ccache.type,
- name=ccache.name)
- else:
- principal = krbV.CCache(name=ccache,
- context=krbV.default_context()).principal().name
-
- os.environ['KRB5CCNAME'] = ccache
- conn.sasl_interactive_bind_s(None, SASL_GSSAPI,
- serverctrls=serverctrls,
- clientctrls=clientctrls)
- setattr(context, 'principal', principal)
+
+ if ccache is not None:
+ if isinstance(ccache, krbV.CCache):
+ principal = ccache.principal().name
+ # Get a fully qualified CCACHE name (schema+name)
+ # As we do not use the krbV.CCache object later,
+ # we can safely overwrite it
+ ccache = "%(type)s:%(name)s" % dict(type=ccache.type,
+ name=ccache.name)
else:
- # no kerberos ccache, use simple bind or external sasl
- if autobind:
- pent = pwd.getpwuid(os.geteuid())
- auth_tokens = _ldap.sasl.external(pent.pw_name)
- conn.sasl_interactive_bind_s(None, auth_tokens,
- serverctrls=serverctrls,
- clientctrls=clientctrls)
- else:
- conn.simple_bind_s(bind_dn, bind_pw,
- serverctrls=serverctrls,
- clientctrls=clientctrls)
+ principal = krbV.CCache(name=ccache,
+ context=krbV.default_context()).principal().name
+
+ os.environ['KRB5CCNAME'] = ccache
+ self.gssapi_bind(server_controls=serverctrls,
+ client_controls=clientctrls)
+ setattr(context, 'principal', principal)
+ else:
+ # no kerberos ccache, use simple bind or external sasl
+ if autobind:
+ pent = pwd.getpwuid(os.geteuid())
+ self.external_bind(pent.pw_name,
+ server_controls=serverctrls,
+ client_controls=clientctrls)
+ else:
+ self.simple_bind(bind_dn, bind_pw,
+ server_controls=serverctrls,
+ client_controls=clientctrls)
return conn
def destroy_connection(self):
"""Disconnect from LDAP server."""
try:
- self.conn.unbind_s()
- except _ldap.LDAPError:
- # ignore when trying to unbind multiple times
- pass
-
- try:
+ self.unbind()
LDAPClient._disconnect(self)
except errors.PublicError:
# ignore when trying to unbind multiple times
--
2.1.0
>From 1c1daa37f74473f33ec0c287d464dc41ab85c577 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Apr 2015 11:31:15 +0000
Subject: [PATCH 09/16] ldap: Use LDAPClient instead of IPASimpleLDAPObject in
ldap2.modify_password
---
ipaserver/plugins/ldap2.py | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 1e103dc..9ab33e2 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -34,7 +34,7 @@ import krbV
import ldap as _ldap
from ipapython.dn import DN
-from ipapython.ipaldap import SASL_GSSAPI, IPASimpleLDAPObject, LDAPClient
+from ipapython.ipaldap import SASL_GSSAPI, LDAPClient
try:
@@ -471,11 +471,10 @@ class ldap2(LDAPClient, CrudBackend):
pw = old_pass
if (otp):
pw = old_pass+otp
- with self.error_handler():
- conn = IPASimpleLDAPObject(
- self.ldap_uri, force_schema_updates=False)
- conn.simple_bind_s(dn, pw)
- conn.unbind_s()
+
+ with LDAPClient(self.ldap_uri, force_schema_updates=False) as conn:
+ conn.simple_bind(dn, pw)
+ conn.unbind()
with self.error_handler():
self.conn.passwd_s(dn, old_pass, new_pass)
--
2.1.0
>From 7ff0b977d9dd1fbb5871fb03a87b03daa58ad35b Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Apr 2015 11:32:21 +0000
Subject: [PATCH 10/16] cainstance: Use LDAPClient instead of
IPASimpleLDAPObject
---
ipaserver/install/cainstance.py | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 8ba6e46..59a6b5f 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -1643,13 +1643,14 @@ def replica_ca_install_check(config):
objectclass = 'ipaObject'
root_logger.debug('Checking if IPA schema is present in %s', ca_ldap_url)
try:
- connection = ldap2.IPASimpleLDAPObject(
- ca_ldap_url, force_schema_updates=False)
- connection.start_tls_s()
- connection.simple_bind_s(DN(('cn', 'Directory Manager')),
- config.dirman_password)
- rschema = connection.schema
- result = rschema.get_obj(ldap.schema.models.ObjectClass, objectclass)
+ with ipaldap.LDAPClient(ca_ldap_url,
+ start_tls=True,
+ force_schema_updates=False) as connection:
+ connection.simple_bind(DN(('cn', 'Directory Manager')),
+ config.dirman_password)
+ rschema = connection.schema
+ result = rschema.get_obj(ldap.schema.models.ObjectClass,
+ objectclass)
except Exception:
root_logger.critical(
'CA DS schema check failed. Make sure the PKI service on the '
--
2.1.0
>From 8c830a7d6c523bde020fd6e225be812ea502486d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Apr 2015 11:32:54 +0000
Subject: [PATCH 11/16] makeaci: Use LDAPClient instead of IPASimpleLDAPObject
---
makeaci | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/makeaci b/makeaci
index c4c6f8a..ab65a99 100755
--- a/makeaci
+++ b/makeaci
@@ -30,16 +30,7 @@ from argparse import ArgumentParser
from ipalib import api
from ipapython.dn import DN
-from ipapython.ipaldap import LDAPEntry, IPASimpleLDAPObject, LDAPClient
-
-
-class FakeLDAPClient(LDAPClient):
- """A LDAP client that can't do any LDAP operations
-
- Used to create and manipulate entries without an LDAP connection.
- """
- def _init_connection(self):
- self.conn = IPASimpleLDAPObject('', False, no_schema=True)
+from ipapython.ipaldap import LDAPEntry, LDAPClient
def parse_options():
@@ -57,7 +48,7 @@ def generate_aci_lines(api):
"""Yields ACI lines as they appear in ACI.txt, with trailing newline"""
update_plugin = api.Updater['update_managed_permissions']
perm_plugin = api.Object['permission']
- fake_ldap = FakeLDAPClient('')
+ fake_ldap = LDAPClient('', force_schema_updates=False, no_schema=True)
for name, template, obj in update_plugin.get_templates():
dn = perm_plugin.get_dn(name)
entry = fake_ldap.make_entry(dn)
--
2.1.0
>From 7d44d21abbc7eeffae451218eddb103cd5c91e98 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 14 Jan 2015 15:51:52 +0000
Subject: [PATCH 12/16] ldap: Move value encoding from IPASimpleLDAPObject to
LDAPClient
---
ipapython/ipaldap.py | 208 +++++++++++++++++----------------------------
ipaserver/plugins/ldap2.py | 22 +++--
2 files changed, 94 insertions(+), 136 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index d28d8b4..042d2de 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -462,153 +462,64 @@ class IPASimpleLDAPObject(object):
else:
raise TypeError("attempt to pass unsupported type from ldap, value=%s type=%s" %(val, type(val)))
- def convert_result(self, result):
- '''
- result is a python-ldap result tuple of the form (dn, attrs),
- where dn is a string containing the dn (distinguished name) of
- the entry, and attrs is a dictionary containing the attributes
- associated with the entry. The keys of attrs are strings, and
- the associated values are lists of strings.
-
- We convert the tuple to an LDAPEntry object.
- '''
-
- ipa_result = []
- for dn_tuple in result:
- original_dn = dn_tuple[0]
- original_attrs = dn_tuple[1]
-
- # original_dn is None if referral instead of an entry was
- # returned from the LDAP server, we need to skip this item
- if original_dn is None:
- log_msg = 'Referral entry ignored: {ref}'\
- .format(ref=str(original_attrs))
- self.log.debug(log_msg)
-
- continue
-
- ipa_entry = LDAPEntry(self, DN(original_dn))
-
- for attr, original_values in original_attrs.items():
- ipa_entry.raw[attr] = original_values
- ipa_entry.reset_modlist()
-
- ipa_result.append(ipa_entry)
-
- if _debug_log_ldap:
- self.log.debug('ldap.result: %s', ipa_result)
- return ipa_result
-
#---------- python-ldap emulations ----------
def add(self, dn, modlist):
- assert isinstance(dn, DN)
- dn = str(dn)
- modlist = self.encode(modlist)
return self.conn.add(dn, modlist)
def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None):
- assert isinstance(dn, DN)
- dn = str(dn)
- modlist = self.encode(modlist)
return self.conn.add_ext(dn, modlist, serverctrls, clientctrls)
def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None):
- assert isinstance(dn, DN)
- dn = str(dn)
- modlist = self.encode(modlist)
return self.conn.add_ext_s(dn, modlist, serverctrls, clientctrls)
def add_s(self, dn, modlist):
- assert isinstance(dn, DN)
- dn = str(dn)
- modlist = self.encode(modlist)
return self.conn.add_s(dn, modlist)
def bind(self, who, cred, method=ldap.AUTH_SIMPLE):
self.flush_cached_schema()
- if who is None:
- who = DN()
- assert isinstance(who, DN)
- who = str(who)
- cred = self.encode(cred)
return self.conn.bind(who, cred, method)
def delete(self, dn):
- assert isinstance(dn, DN)
- dn = str(dn)
return self.conn.delete(dn)
def delete_s(self, dn):
- assert isinstance(dn, DN)
- dn = str(dn)
return self.conn.delete_s(dn)
def get_option(self, option):
return self.conn.get_option(option)
def modify_s(self, dn, modlist):
- assert isinstance(dn, DN)
- dn = str(dn)
- modlist = [(x[0], self.encode(x[1]), self.encode(x[2])) for x in modlist]
return self.conn.modify_s(dn, modlist)
def modrdn_s(self, dn, newrdn, delold=1):
- assert isinstance(dn, DN)
- dn = str(dn)
- assert isinstance(newrdn, (DN, RDN))
- newrdn = str(newrdn)
return self.conn.modrdn_s(dn, newrdn, delold)
def passwd_s(self, dn, oldpw, newpw, serverctrls=None, clientctrls=None):
- assert isinstance(dn, DN)
- dn = str(dn)
- oldpw = self.encode(oldpw)
- newpw = self.encode(newpw)
return self.conn.passwd_s(dn, oldpw, newpw, serverctrls, clientctrls)
def rename_s(self, dn, newrdn, newsuperior=None, delold=1):
# NOTICE: python-ldap of version 2.3.10 and lower does not support
# serverctrls and clientctrls for rename_s operation. Thus, these
# options are ommited from this command until really needed
- assert isinstance(dn, DN)
- dn = str(dn)
- assert isinstance(newrdn, (DN, RDN))
- newrdn = str(newrdn)
return self.conn.rename_s(dn, newrdn, newsuperior, delold)
def result(self, msgid=ldap.RES_ANY, all=1, timeout=None):
- resp_type, resp_data = self.conn.result(msgid, all, timeout)
- resp_data = self.convert_result(resp_data)
- return resp_type, resp_data
+ return self.conn.result(msgid, all, timeout)
def result3(self, msgid=ldap.RES_ANY, all=1, timeout=None):
- rtype, rdata, rmsgid, rctrls = self.conn.result3(msgid, all, timeout)
- rdata = self.convert_result(rdata)
- return rtype, rdata, rmsgid, rctrls
+ return self.conn.result3(msgid, all, timeout)
def sasl_interactive_bind_s(self, who, auth, serverctrls=None,
clientctrls=None, sasl_flags=ldap.SASL_QUIET):
self.flush_cached_schema()
- if who is None:
- who = DN()
- assert isinstance(who, DN)
- who = str(who)
- return self.conn.sasl_interactive_bind_s(who, auth, serverctrls, clientctrls, sasl_flags)
+ return self.conn.sasl_interactive_bind_s(who, auth, serverctrls,
+ clientctrls, sasl_flags)
def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- assert isinstance(base, DN)
- base = str(base)
- filterstr = self.encode(filterstr)
- attrlist = self.encode(attrlist)
return self.conn.search(base, scope, filterstr, attrlist, attrsonly)
def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- assert isinstance(base, DN)
- base = str(base)
- filterstr = self.encode(filterstr)
- attrlist = self.encode(attrlist)
-
if _debug_log_ldap:
self.log.debug(
"ldap.search_ext: dn: %s\nfilter: %s\nattrs_list: %s",
@@ -618,42 +529,23 @@ class IPASimpleLDAPObject(object):
return self.conn.search_ext(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit)
def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- assert isinstance(base, DN)
- base = str(base)
- filterstr = self.encode(filterstr)
- attrlist = self.encode(attrlist)
- ldap_result = self.conn.search_ext_s(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit)
- ipa_result = self.convert_result(ldap_result)
- return ipa_result
+ return self.conn.search_ext_s(base, scope, filterstr, attrlist,
+ attrsonly, serverctrls, clientctrls,
+ timeout, sizelimit)
def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- assert isinstance(base, DN)
- base = str(base)
- filterstr = self.encode(filterstr)
- attrlist = self.encode(attrlist)
- ldap_result = self.conn.search_s(base, scope, filterstr, attrlist, attrsonly)
- ipa_result = self.convert_result(ldap_result)
- return ipa_result
+ return self.conn.search_s(base, scope, filterstr, attrlist,
+ attrsonly)
def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1):
- assert isinstance(base, DN)
- base = str(base)
- filterstr = self.encode(filterstr)
- attrlist = self.encode(attrlist)
- ldap_result = self.conn.search_st(base, scope, filterstr, attrlist, attrsonly, timeout)
- ipa_result = self.convert_result(ldap_result)
- return ipa_result
+ return self.conn.search_st(base, scope, filterstr, attrlist, attrsonly,
+ timeout)
def set_option(self, option, invalue):
return self.conn.set_option(option, invalue)
def simple_bind_s(self, who=None, cred='', serverctrls=None, clientctrls=None):
self.flush_cached_schema()
- if who is None:
- who = DN()
- assert isinstance(who, DN)
- who = str(who)
- cred = self.encode(cred)
return self.conn.simple_bind_s(who, cred, serverctrls, clientctrls)
def start_tls_s(self):
@@ -1100,6 +992,49 @@ class LDAPClient(object):
def conn(self):
return self._conn
+ def encode(self, val):
+ return self.conn.encode(val)
+
+ def decode(self, val, attr):
+ return self.conn.decode(val, attr)
+
+ def _convert_result(self, result):
+ '''
+ result is a python-ldap result tuple of the form (dn, attrs),
+ where dn is a string containing the dn (distinguished name) of
+ the entry, and attrs is a dictionary containing the attributes
+ associated with the entry. The keys of attrs are strings, and
+ the associated values are lists of strings.
+
+ We convert the tuple to an LDAPEntry object.
+ '''
+
+ ipa_result = []
+ for dn_tuple in result:
+ original_dn = dn_tuple[0]
+ original_attrs = dn_tuple[1]
+
+ # original_dn is None if referral instead of an entry was
+ # returned from the LDAP server, we need to skip this item
+ if original_dn is None:
+ log_msg = 'Referral entry ignored: {ref}'\
+ .format(ref=str(original_attrs))
+ self.log.debug(log_msg)
+
+ continue
+
+ ipa_entry = LDAPEntry(self.conn, DN(original_dn))
+
+ for attr, original_values in original_attrs.items():
+ ipa_entry.raw[attr] = original_values
+ ipa_entry.reset_modlist()
+
+ ipa_result.append(ipa_entry)
+
+ if _debug_log_ldap:
+ self.log.debug('ldap.result: %s', ipa_result)
+ return ipa_result
+
@contextlib.contextmanager
def error_handler(self, arg_desc=None):
"""Context manager that handles LDAPErrors
@@ -1241,6 +1176,11 @@ class LDAPClient(object):
Perform simple bind operation.
"""
with self.error_handler():
+ if bind_dn is None:
+ bind_dn = DN()
+ assert isinstance(bind_dn, DN)
+ bind_dn = str(bind_dn)
+ bind_password = self.encode(bind_password)
self._conn.simple_bind_s(
bind_dn, bind_password, server_controls, client_controls)
@@ -1252,7 +1192,7 @@ class LDAPClient(object):
with self.error_handler():
auth_tokens = ldap.sasl.external(user_name)
self._conn.sasl_interactive_bind_s(
- None, auth_tokens, server_controls, client_controls)
+ '', auth_tokens, server_controls, client_controls)
def gssapi_bind(self, server_controls=None, client_controls=None):
"""
@@ -1261,7 +1201,7 @@ class LDAPClient(object):
with self.error_handler():
auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
self._conn.sasl_interactive_bind_s(
- None, auth_tokens, server_controls, client_controls)
+ '', auth_tokens, server_controls, client_controls)
def unbind(self):
"""
@@ -1501,19 +1441,23 @@ class LDAPClient(object):
# pass arguments to python-ldap
with self.error_handler():
+ filter = self.encode(filter)
+ attrs_list = self.encode(attrs_list)
+
while True:
if paged_search:
sctrls = [SimplePagedResultsControl(0, page_size, cookie)]
try:
id = self.conn.search_ext(
- base_dn, scope, filter, attrs_list,
+ str(base_dn), scope, filter, attrs_list,
serverctrls=sctrls, timeout=time_limit,
sizelimit=size_limit
)
while True:
result = self.conn.result3(id, 0)
objtype, res_list, res_id, res_ctrls = result
+ res_list = self._convert_result(res_list)
if not res_list:
break
if (objtype == ldap.RES_SEARCH_ENTRY or
@@ -1535,7 +1479,7 @@ class LDAPClient(object):
sctrls = [SimplePagedResultsControl(0, 0, cookie)]
try:
self.conn.search_ext_s(
- base_dn, scope, filter, attrs_list,
+ str(base_dn), scope, filter, attrs_list,
serverctrls=sctrls, timeout=time_limit,
sizelimit=size_limit)
except ldap.LDAPError, e:
@@ -1613,7 +1557,8 @@ class LDAPClient(object):
attrs = dict((k, v) for k, v in entry.raw.iteritems() if v)
with self.error_handler():
- self.conn.add_s(entry.dn, attrs.items())
+ attrs = self.encode(attrs)
+ self.conn.add_s(str(entry.dn), attrs.items())
entry.reset_modlist()
@@ -1631,7 +1576,7 @@ class LDAPClient(object):
if dn[0] == new_rdn:
raise errors.EmptyModlist()
with self.error_handler():
- self.conn.rename_s(dn, new_rdn, delold=int(del_old))
+ self.conn.rename_s(str(dn), str(new_rdn), delold=int(del_old))
time.sleep(.3) # Give memberOf plugin a chance to work
def update_entry(self, entry):
@@ -1646,7 +1591,9 @@ class LDAPClient(object):
# pass arguments to python-ldap
with self.error_handler():
- self.conn.modify_s(entry.dn, modlist)
+ modlist = [(a, self.encode(b), self.encode(c))
+ for a, b, c in modlist]
+ self.conn.modify_s(str(entry.dn), modlist)
entry.reset_modlist()
@@ -1658,7 +1605,7 @@ class LDAPClient(object):
dn = entry_or_dn.dn
with self.error_handler():
- self.conn.delete_s(dn)
+ self.conn.delete_s(str(dn))
def entry_exists(self, dn):
"""
@@ -1797,6 +1744,9 @@ class IPAdmin(LDAPClient):
#fall back
self.do_sasl_gssapi_bind(timeout=timeout)
- def modify_s(self, *args, **kwargs):
+ def modify_s(self, dn, modlist):
# FIXME: for backwards compatibility only
- return self.conn.modify_s(*args, **kwargs)
+ assert isinstance(dn, DN)
+ dn = str(dn)
+ modlist = [(a, self.encode(b), self.encode(c)) for a, b, c in modlist]
+ return self.conn.modify_s(dn, modlist)
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 9ab33e2..15e07f2 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -360,9 +360,11 @@ class ldap2(LDAPClient, CrudBackend):
('cn', 'etc'), self.api.env.basedn)
try:
- upg_entries = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE,
- attrlist=['*'])
- except _ldap.NO_SUCH_OBJECT:
+ with self.error_handler():
+ upg_entries = self.conn.search_s(str(upg_dn), _ldap.SCOPE_BASE,
+ attrlist=['*'])
+ upg_entries = self._convert_result(upg_entries)
+ except errors.NotFound:
upg_entries = None
if not upg_entries or 'originfilter' not in upg_entries[0]:
raise errors.ACIError(info=_(
@@ -477,7 +479,9 @@ class ldap2(LDAPClient, CrudBackend):
conn.unbind()
with self.error_handler():
- self.conn.passwd_s(dn, old_pass, new_pass)
+ old_pass = self.encode(old_pass)
+ new_pass = self.encode(new_pass)
+ self.conn.passwd_s(str(dn), old_pass, new_pass)
def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False):
"""
@@ -509,7 +513,9 @@ class ldap2(LDAPClient, CrudBackend):
# update group entry
try:
with self.error_handler():
- self.conn.modify_s(group_dn, modlist)
+ modlist = [(a, self.encode(b), self.encode(c))
+ for a, b, c in modlist]
+ self.conn.modify_s(str(group_dn), modlist)
except errors.DatabaseError:
raise errors.AlreadyGroupMember()
@@ -529,7 +535,9 @@ class ldap2(LDAPClient, CrudBackend):
# update group entry
try:
with self.error_handler():
- self.conn.modify_s(group_dn, modlist)
+ modlist = [(a, self.encode(b), self.encode(c))
+ for a, b, c in modlist]
+ self.conn.modify_s(str(group_dn), modlist)
except errors.MidairCollision:
raise errors.NotGroupMember()
@@ -583,7 +591,7 @@ class ldap2(LDAPClient, CrudBackend):
(_ldap.MOD_REPLACE, 'krblastpwdchange', None)]
with self.error_handler():
- self.conn.modify_s(dn, mod)
+ self.conn.modify_s(str(dn), mod)
# CrudBackend methods
--
2.1.0
>From 558b19dc21bb3a2bcb824aec0b19c0ee559226ea Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 14 Jan 2015 16:49:35 +0000
Subject: [PATCH 13/16] ldap: Use LDAPClient instead of IPASimpleLDAPObject in
LDAPEntry
---
ipapython/ipaldap.py | 11 +++++++----
ipatests/test_xmlrpc/test_baseldap_plugin.py | 8 +++++++-
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 042d2de..b8b0617 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -571,7 +571,7 @@ class LDAPEntry(collections.MutableMapping):
* 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
+ specified LDAPClient and DN and optionally initialize
attributes from the specified mapping object.
Keyword arguments can be used to override values of specific attributes.
@@ -582,7 +582,7 @@ class LDAPEntry(collections.MutableMapping):
assert _dn is None
_dn = _conn
_conn = _conn._conn
- assert isinstance(_conn, IPASimpleLDAPObject)
+ assert isinstance(_conn, LDAPClient)
if isinstance(_dn, LDAPEntry):
assert _obj is None
@@ -992,6 +992,9 @@ class LDAPClient(object):
def conn(self):
return self._conn
+ def get_single_value(self, attr):
+ return self.conn.get_single_value(attr)
+
def encode(self, val):
return self.conn.encode(val)
@@ -1023,7 +1026,7 @@ class LDAPClient(object):
continue
- ipa_entry = LDAPEntry(self.conn, DN(original_dn))
+ ipa_entry = LDAPEntry(self, DN(original_dn))
for attr, original_values in original_attrs.items():
ipa_entry.raw[attr] = original_values
@@ -1240,7 +1243,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(self.conn, _dn, _obj, **kwargs)
+ return LDAPEntry(self, _dn, _obj, **kwargs)
# generating filters for find_entry
# some examples:
diff --git a/ipatests/test_xmlrpc/test_baseldap_plugin.py b/ipatests/test_xmlrpc/test_baseldap_plugin.py
index d5b3388..3ffc041 100644
--- a/ipatests/test_xmlrpc/test_baseldap_plugin.py
+++ b/ipatests/test_xmlrpc/test_baseldap_plugin.py
@@ -187,7 +187,13 @@ def test_entry_to_dict():
self._schema = FakeSchema()
self._has_schema = True
- conn = FakeIPASimpleLDAPObject()
+ class FakeLDAPClient(ipaldap.LDAPClient):
+ def __init__(self):
+ super(FakeLDAPClient, self).__init__('ldap://test',
+ force_schema_updates=False)
+ self._conn = FakeIPASimpleLDAPObject()
+
+ conn = FakeLDAPClient()
rights = {'nothing': 'is'}
entry = ipaldap.LDAPEntry(
--
2.1.0
>From bd869739f49ff7313522da40793c9cd62e0bbf33 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 26 Jan 2015 16:33:50 +0000
Subject: [PATCH 14/16] ldap: Move schema handling from IPASimpleLDAPObject to
LDAPClient
---
ipalib/plugins/baseldap.py | 2 +-
ipapython/ipaldap.py | 541 +++++++++++++--------------
ipatests/test_xmlrpc/test_baseldap_plugin.py | 9 +-
3 files changed, 264 insertions(+), 288 deletions(-)
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index 4b1c701..85065c6 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -243,7 +243,7 @@ def entry_to_dict(entry, **options):
for attr in entry.iterkeys():
if attr.lower() == 'attributelevelrights':
value = entry[attr]
- elif entry.conn.get_type(attr) is str:
+ elif entry.conn.get_attribute_type(attr) is str:
value = entry.raw[attr]
else:
value = list(entry.raw[attr])
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index b8b0617..d135aa7 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -50,7 +50,6 @@ from ipapython.dnsutil import DNSName
SASL_GSSAPI = ldap.sasl.sasl({}, 'GSSAPI')
DEFAULT_TIMEOUT = 10
-DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12'
_debug_log_ldap = False
_missing = object()
@@ -190,277 +189,18 @@ schema_cache = SchemaCache()
class IPASimpleLDAPObject(object):
'''
- The purpose of this class is to provide a boundary between IPA and
- python-ldap. In IPA we use IPA defined types because they are
- richer and are designed to meet our needs. We also require that we
- consistently use those types without exception. On the other hand
- python-ldap uses different types. The goal is to be able to have
- IPA code call python-ldap methods using the types native to
- IPA. This class accomplishes that goal by exposing python-ldap
- methods which take IPA types, convert them to python-ldap types,
- call python-ldap, and then convert the results returned by
- python-ldap into IPA types.
-
IPA code should never call python-ldap directly, it should only
call python-ldap methods in this class.
'''
- # Note: the oid for dn syntax is: 1.3.6.1.4.1.1466.115.121.1.12
-
- _SYNTAX_MAPPING = {
- '1.3.6.1.4.1.1466.115.121.1.1' : str, # ACI item
- '1.3.6.1.4.1.1466.115.121.1.4' : str, # Audio
- '1.3.6.1.4.1.1466.115.121.1.5' : str, # Binary
- '1.3.6.1.4.1.1466.115.121.1.8' : str, # Certificate
- '1.3.6.1.4.1.1466.115.121.1.9' : str, # Certificate List
- '1.3.6.1.4.1.1466.115.121.1.10' : str, # Certificate Pair
- '1.3.6.1.4.1.1466.115.121.1.23' : str, # Fax
- '1.3.6.1.4.1.1466.115.121.1.28' : str, # JPEG
- '1.3.6.1.4.1.1466.115.121.1.40' : str, # OctetString (same as Binary)
- '1.3.6.1.4.1.1466.115.121.1.49' : str, # Supported Algorithm
- '1.3.6.1.4.1.1466.115.121.1.51' : str, # Teletext Terminal Identifier
-
- DN_SYNTAX_OID : DN, # DN, member, memberof
- '2.16.840.1.113730.3.8.3.3' : DN, # enrolledBy
- '2.16.840.1.113730.3.8.3.18' : DN, # managedBy
- '2.16.840.1.113730.3.8.3.5' : DN, # memberUser
- '2.16.840.1.113730.3.8.3.7' : DN, # memberHost
- '2.16.840.1.113730.3.8.3.20' : DN, # memberService
- '2.16.840.1.113730.3.8.11.4' : DN, # ipaNTFallbackPrimaryGroup
- '2.16.840.1.113730.3.8.11.21' : DN, # ipaAllowToImpersonate
- '2.16.840.1.113730.3.8.11.22' : DN, # ipaAllowedTarget
- '2.16.840.1.113730.3.8.7.1' : DN, # memberAllowCmd
- '2.16.840.1.113730.3.8.7.2' : DN, # memberDenyCmd
-
- '2.16.840.1.113719.1.301.4.14.1' : DN, # krbRealmReferences
- '2.16.840.1.113719.1.301.4.17.1' : DN, # krbKdcServers
- '2.16.840.1.113719.1.301.4.18.1' : DN, # krbPwdServers
- '2.16.840.1.113719.1.301.4.26.1' : DN, # krbPrincipalReferences
- '2.16.840.1.113719.1.301.4.29.1' : DN, # krbAdmServers
- '2.16.840.1.113719.1.301.4.36.1' : DN, # krbPwdPolicyReference
- '2.16.840.1.113719.1.301.4.40.1' : DN, # krbTicketPolicyReference
- '2.16.840.1.113719.1.301.4.41.1' : DN, # krbSubTrees
- '2.16.840.1.113719.1.301.4.52.1' : DN, # krbObjectReferences
- '2.16.840.1.113719.1.301.4.53.1' : DN, # krbPrincContainerRef
- '1.3.6.1.4.1.1466.115.121.1.24' : datetime.datetime,
- }
-
- # In most cases we lookup the syntax from the schema returned by
- # the server. However, sometimes attributes may not be defined in
- # the schema (e.g. extensibleObject which permits undefined
- # attributes), or the schema was incorrectly defined (i.e. giving
- # an attribute the syntax DirectoryString when in fact it's really
- # a DN). This (hopefully sparse) table allows us to trap these
- # anomalies and force them to be the syntax we know to be in use.
- #
- # FWIW, many entries under cn=config are undefined :-(
-
- _SYNTAX_OVERRIDE = CIDict({
- 'managedtemplate': DN,
- 'managedbase': DN,
- 'originscope': DN,
- 'idnsname': DNSName,
- 'idnssoamname': DNSName,
- 'idnssoarname': DNSName,
- 'dnszoneidnsname': DNSName,
- 'nsds5replicalastupdatestart': unicode,
- 'nsds5replicalastupdateend': unicode,
- 'nsds5replicalastinitstart': unicode,
- 'nsds5replicalastinitend': unicode,
- })
- _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):
+ def __init__(self, uri):
"""An internal LDAP connection object
:param uri: The LDAP URI to connect to
- :param force_schema_updates:
- If true, this object will always request a new schema from the
- server. If false, a cached schema will be reused if it exists.
-
- Generally, it should be true if the API context is 'installer' or
- 'updates', but it must be given explicitly since the API object
- is not always available
- :param no_schema: If true, schema is never requested from the server.
- :param decode_attrs:
- If true, attributes are decoded to Python types according to their
- syntax.
"""
self.log = log_mgr.get_logger(self)
self.uri = uri
self.conn = SimpleLDAPObject(uri)
- self._no_schema = no_schema
- self._has_schema = False
- self._schema = None
- self._force_schema_updates = force_schema_updates
- self._decode_attrs = decode_attrs
-
- def _get_schema(self):
- if self._no_schema:
- return None
- if not self._has_schema:
- try:
- self._schema = schema_cache.get_schema(
- self.uri, self.conn,
- force_update=self._force_schema_updates)
- except (errors.ExecutionError, IndexError):
- pass
- self._has_schema = True
- return self._schema
-
- schema = property(_get_schema, None, None, 'schema associated with this LDAP server')
-
-
- def flush_cached_schema(self):
- '''
- Force this instance to forget it's cached schema and reacquire
- it from the schema cache.
- '''
-
- # Currently this is called during bind operations to assure
- # we're working with valid schema for a specific
- # connection. This causes self._get_schema() to query the
- # schema cache for the server's schema passing along a flag
- # indicating if we're in a context that requires freshly
- # loading the schema vs. returning the last cached version of
- # the schema. If we're in a mode that permits use of
- # previously cached schema the flush and reacquire is a very
- # low cost operation.
- #
- # The schema is reacquired whenever this object is
- # instantiated or when binding occurs. The schema is not
- # reacquired for operations during a bound connection, it is
- # presumed schema cannot change during this interval. This
- # provides for maximum efficiency in contexts which do need
- # schema refreshing by only peforming the refresh inbetween
- # logical operations that have the potential to cause a schema
- # change.
-
- self._has_schema = False
- self._schema = None
-
- def get_type(self, attr):
- if isinstance(attr, unicode):
- attr = attr.encode('utf-8')
-
- # Is this a special case attribute?
- if attr in self._SYNTAX_OVERRIDE:
- return self._SYNTAX_OVERRIDE[attr]
-
- if self.schema is None:
- return unicode
-
- # Try to lookup the syntax in the schema returned by the server
- obj = self.schema.get_obj(ldap.schema.AttributeType, attr)
- if obj is None:
- return unicode
-
- return self._SYNTAX_MAPPING.get(obj.syntax, unicode)
-
- def has_dn_syntax(self, attr):
- """
- Check the schema to see if the attribute uses DN syntax.
-
- Returns True/False
- """
- return self.get_type(attr) is DN
-
- 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 attr in self._SINGLE_VALUE_OVERRIDE:
- return self._SINGLE_VALUE_OVERRIDE[attr]
-
- 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):
- """
- Encode attribute value to LDAP representation (str).
- """
- # Booleans are both an instance of bool and int, therefore
- # test for bool before int otherwise the int clause will be
- # entered for a boolean value instead of the boolean clause.
- if isinstance(val, bool):
- if val:
- return 'TRUE'
- else:
- return 'FALSE'
- elif isinstance(val, (unicode, float, int, long, Decimal, DN)):
- return value_to_utf8(val)
- elif isinstance(val, DNSName):
- return str(val)
- elif isinstance(val, str):
- return val
- elif isinstance(val, list):
- return [self.encode(m) for m in val]
- elif isinstance(val, tuple):
- return tuple(self.encode(m) for m in val)
- elif isinstance(val, dict):
- dct = dict((self.encode(k), self.encode(v)) for k, v in val.iteritems())
- return dct
- elif isinstance(val, datetime.datetime):
- return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
- elif val is None:
- return None
- else:
- raise TypeError("attempt to pass unsupported type to ldap, value=%s type=%s" %(val, type(val)))
-
- def decode(self, val, attr):
- """
- Decode attribute value from LDAP representation (str).
- """
- if isinstance(val, str):
- if not self._decode_attrs:
- return val
- target_type = self.get_type(attr)
- try:
- if target_type is str:
- return val
- elif target_type is unicode:
- return val.decode('utf-8')
- elif target_type is datetime.datetime:
- return datetime.datetime.strptime(val, LDAP_GENERALIZED_TIME_FORMAT)
- else:
- return target_type(val)
- except Exception, e:
- 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):
- return [self.decode(m, attr) for m in val]
- elif isinstance(val, tuple):
- return tuple(self.decode(m, attr) for m in val)
- elif isinstance(val, dict):
- dct = dict((unicode_from_utf8(k), self.decode(v, k)) for k, v in val.iteritems())
- return dct
- elif val is None:
- return None
- else:
- raise TypeError("attempt to pass unsupported type from ldap, value=%s type=%s" %(val, type(val)))
#---------- python-ldap emulations ----------
@@ -477,7 +217,6 @@ class IPASimpleLDAPObject(object):
return self.conn.add_s(dn, modlist)
def bind(self, who, cred, method=ldap.AUTH_SIMPLE):
- self.flush_cached_schema()
return self.conn.bind(who, cred, method)
def delete(self, dn):
@@ -512,7 +251,6 @@ class IPASimpleLDAPObject(object):
def sasl_interactive_bind_s(self, who, auth, serverctrls=None,
clientctrls=None, sasl_flags=ldap.SASL_QUIET):
- self.flush_cached_schema()
return self.conn.sasl_interactive_bind_s(who, auth, serverctrls,
clientctrls, sasl_flags)
@@ -545,14 +283,12 @@ class IPASimpleLDAPObject(object):
return self.conn.set_option(option, invalue)
def simple_bind_s(self, who=None, cred='', serverctrls=None, clientctrls=None):
- self.flush_cached_schema()
return self.conn.simple_bind_s(who, cred, serverctrls, clientctrls)
def start_tls_s(self):
return self.conn.start_tls_s()
def unbind_s(self):
- self.flush_cached_schema()
return self.conn.unbind_s()
@@ -880,7 +616,7 @@ class LDAPEntry(collections.MutableMapping):
# particularly for schema
adds = [value for value in new if value not in old]
dels = [value for value in old if value not in new]
- if adds and self.conn.get_single_value(name):
+ if adds and self.conn.get_attribute_single_value(name):
if len(adds) > 1:
raise errors.OnlyOneValueAllowed(attr=name)
modlist.append((ldap.MOD_REPLACE, name, adds))
@@ -961,8 +697,16 @@ class LDAPClient(object):
This class abstracts a LDAP connection, providing methods that work with
LADPEntries.
- This class is not intended to be used directly; instead, use one of its
- subclasses, IPAdmin or the ldap2 plugin.
+ The purpose of this class is to provide a boundary between IPA and
+ python-ldap. In IPA we use IPA defined types because they are
+ richer and are designed to meet our needs. We also require that we
+ consistently use those types without exception. On the other hand
+ python-ldap uses different types. The goal is to be able to have
+ IPA code call python-ldap methods using the types native to
+ IPA. This class accomplishes that goal by exposing python-ldap
+ methods which take IPA types, convert them to python-ldap types,
+ call python-ldap, and then convert the results returned by
+ python-ldap into IPA types.
"""
# rules for generating filters from entries
@@ -975,8 +719,93 @@ class LDAPClient(object):
SCOPE_ONELEVEL = ldap.SCOPE_ONELEVEL
SCOPE_SUBTREE = ldap.SCOPE_SUBTREE
+ _SYNTAX_MAPPING = {
+ '1.3.6.1.4.1.1466.115.121.1.1' : str, # ACI item
+ '1.3.6.1.4.1.1466.115.121.1.4' : str, # Audio
+ '1.3.6.1.4.1.1466.115.121.1.5' : str, # Binary
+ '1.3.6.1.4.1.1466.115.121.1.8' : str, # Certificate
+ '1.3.6.1.4.1.1466.115.121.1.9' : str, # Certificate List
+ '1.3.6.1.4.1.1466.115.121.1.10' : str, # Certificate Pair
+ '1.3.6.1.4.1.1466.115.121.1.12' : DN, # Distinguished Name
+ '1.3.6.1.4.1.1466.115.121.1.23' : str, # Fax
+ '1.3.6.1.4.1.1466.115.121.1.24' : datetime.datetime,
+ '1.3.6.1.4.1.1466.115.121.1.28' : str, # JPEG
+ '1.3.6.1.4.1.1466.115.121.1.40' : str, # OctetString (same as Binary)
+ '1.3.6.1.4.1.1466.115.121.1.49' : str, # Supported Algorithm
+ '1.3.6.1.4.1.1466.115.121.1.51' : str, # Teletext Terminal Identifier
+
+ '2.16.840.1.113730.3.8.3.3' : DN, # enrolledBy
+ '2.16.840.1.113730.3.8.3.18' : DN, # managedBy
+ '2.16.840.1.113730.3.8.3.5' : DN, # memberUser
+ '2.16.840.1.113730.3.8.3.7' : DN, # memberHost
+ '2.16.840.1.113730.3.8.3.20' : DN, # memberService
+ '2.16.840.1.113730.3.8.11.4' : DN, # ipaNTFallbackPrimaryGroup
+ '2.16.840.1.113730.3.8.11.21' : DN, # ipaAllowToImpersonate
+ '2.16.840.1.113730.3.8.11.22' : DN, # ipaAllowedTarget
+ '2.16.840.1.113730.3.8.7.1' : DN, # memberAllowCmd
+ '2.16.840.1.113730.3.8.7.2' : DN, # memberDenyCmd
+
+ '2.16.840.1.113719.1.301.4.14.1' : DN, # krbRealmReferences
+ '2.16.840.1.113719.1.301.4.17.1' : DN, # krbKdcServers
+ '2.16.840.1.113719.1.301.4.18.1' : DN, # krbPwdServers
+ '2.16.840.1.113719.1.301.4.26.1' : DN, # krbPrincipalReferences
+ '2.16.840.1.113719.1.301.4.29.1' : DN, # krbAdmServers
+ '2.16.840.1.113719.1.301.4.36.1' : DN, # krbPwdPolicyReference
+ '2.16.840.1.113719.1.301.4.40.1' : DN, # krbTicketPolicyReference
+ '2.16.840.1.113719.1.301.4.41.1' : DN, # krbSubTrees
+ '2.16.840.1.113719.1.301.4.52.1' : DN, # krbObjectReferences
+ '2.16.840.1.113719.1.301.4.53.1' : DN, # krbPrincContainerRef
+ }
+
+ # In most cases we lookup the syntax from the schema returned by
+ # the server. However, sometimes attributes may not be defined in
+ # the schema (e.g. extensibleObject which permits undefined
+ # attributes), or the schema was incorrectly defined (i.e. giving
+ # an attribute the syntax DirectoryString when in fact it's really
+ # a DN). This (hopefully sparse) table allows us to trap these
+ # anomalies and force them to be the syntax we know to be in use.
+ #
+ # FWIW, many entries under cn=config are undefined :-(
+
+ _SYNTAX_OVERRIDE = CIDict({
+ 'managedtemplate': DN,
+ 'managedbase': DN,
+ 'originscope': DN,
+ 'idnsname': DNSName,
+ 'idnssoamname': DNSName,
+ 'idnssoarname': DNSName,
+ 'dnszoneidnsname': DNSName,
+ 'nsds5replicalastupdatestart': unicode,
+ 'nsds5replicalastupdateend': unicode,
+ 'nsds5replicalastinitstart': unicode,
+ 'nsds5replicalastinitend': unicode,
+ })
+ _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, ldap_uri, start_tls=False, force_schema_updates=False,
no_schema=False, decode_attrs=True):
+ """Create LDAPClient object.
+
+ :param ldap_uri: The LDAP URI to connect to
+ :param start_tls: Use STARTTLS
+ :param force_schema_updates:
+ If true, this object will always request a new schema from the
+ server. If false, a cached schema will be reused if it exists.
+
+ Generally, it should be true if the API context is 'installer' or
+ 'updates', but it must be given explicitly since the API object
+ is not always available
+ :param no_schema: If true, schema is never requested from the server.
+ :param decode_attrs:
+ If true, attributes are decoded to Python types according to their
+ syntax.
+ """
self.ldap_uri = ldap_uri
self._start_tls = start_tls
self._force_schema_updates = force_schema_updates
@@ -985,6 +814,8 @@ class LDAPClient(object):
self.log = log_mgr.get_logger(self)
self._conn = None
+ self._has_schema = False
+ self._schema = None
self._connect()
@@ -992,14 +823,166 @@ class LDAPClient(object):
def conn(self):
return self._conn
- def get_single_value(self, attr):
- return self.conn.get_single_value(attr)
+ def _get_schema(self):
+ if self._no_schema:
+ return None
+
+ if not self._has_schema:
+ try:
+ schema = schema_cache.get_schema(
+ self.conn.uri, self.conn.conn,
+ force_update=self._force_schema_updates)
+ except (errors.ExecutionError, IndexError):
+ schema = None
+
+ # bypass ldap2's locking
+ object.__setattr__(self, '_schema', schema)
+ object.__setattr__(self, '_has_schema', True)
+
+ return self._schema
+
+ def _flush_schema(self):
+ '''
+ Force this instance to forget it's cached schema and reacquire
+ it from the schema cache.
+ '''
+
+ # Currently this is called during bind operations to assure
+ # we're working with valid schema for a specific
+ # connection. This causes self._get_schema() to query the
+ # schema cache for the server's schema passing along a flag
+ # indicating if we're in a context that requires freshly
+ # loading the schema vs. returning the last cached version of
+ # the schema. If we're in a mode that permits use of
+ # previously cached schema the flush and reacquire is a very
+ # low cost operation.
+ #
+ # The schema is reacquired whenever this object is
+ # instantiated or when binding occurs. The schema is not
+ # reacquired for operations during a bound connection, it is
+ # presumed schema cannot change during this interval. This
+ # provides for maximum efficiency in contexts which do need
+ # schema refreshing by only peforming the refresh inbetween
+ # logical operations that have the potential to cause a schema
+ # change.
+
+ # bypass ldap2's locking
+ object.__setattr__(self, '_has_schema', False)
+ object.__setattr__(self, '_schema', None)
+
+ def get_attribute_type(self, name_or_oid):
+ if not self._decode_attrs:
+ return str
+
+ if isinstance(name_or_oid, unicode):
+ name_or_oid = name_or_oid.encode('utf-8')
+
+ # Is this a special case attribute?
+ if name_or_oid in self._SYNTAX_OVERRIDE:
+ return self._SYNTAX_OVERRIDE[name_or_oid]
+
+ schema = self._get_schema()
+ if schema is not None:
+ # Try to lookup the syntax in the schema returned by the server
+ obj = schema.get_obj(ldap.schema.AttributeType, name_or_oid)
+ if obj is not None and obj.syntax in self._SYNTAX_MAPPING:
+ return self._SYNTAX_MAPPING[obj.syntax]
+
+ return unicode
+
+ def has_dn_syntax(self, name_or_oid):
+ """
+ Check the schema to see if the attribute uses DN syntax.
+
+ Returns True/False
+ """
+ return self.get_attribute_type(name_or_oid) is DN
+
+ def get_attribute_single_value(self, name_or_oid):
+ """
+ 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(name_or_oid, unicode):
+ name_or_oid = name_or_oid.encode('utf-8')
+
+ if name_or_oid in self._SINGLE_VALUE_OVERRIDE:
+ return self._SINGLE_VALUE_OVERRIDE[name_or_oid]
+
+ schema = self._get_schema()
+ if schema is not None:
+ obj = schema.get_obj(ldap.schema.AttributeType, name_or_oid)
+ if obj is not None:
+ return obj.single_value
+
+ return None
def encode(self, val):
- return self.conn.encode(val)
+ """
+ Encode attribute value to LDAP representation (str).
+ """
+ # Booleans are both an instance of bool and int, therefore
+ # test for bool before int otherwise the int clause will be
+ # entered for a boolean value instead of the boolean clause.
+ if isinstance(val, bool):
+ if val:
+ return 'TRUE'
+ else:
+ return 'FALSE'
+ elif isinstance(val, (unicode, float, int, long, Decimal, DN)):
+ return value_to_utf8(val)
+ elif isinstance(val, DNSName):
+ return str(val)
+ elif isinstance(val, str):
+ return val
+ elif isinstance(val, list):
+ return [self.encode(m) for m in val]
+ elif isinstance(val, tuple):
+ return tuple(self.encode(m) for m in val)
+ elif isinstance(val, dict):
+ dct = dict((self.encode(k), self.encode(v)) for k, v in val.iteritems())
+ return dct
+ elif isinstance(val, datetime.datetime):
+ return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
+ elif val is None:
+ return None
+ else:
+ raise TypeError("attempt to pass unsupported type to ldap, value=%s type=%s" %(val, type(val)))
def decode(self, val, attr):
- return self.conn.decode(val, attr)
+ """
+ Decode attribute value from LDAP representation (str).
+ """
+ if isinstance(val, str):
+ target_type = self.get_attribute_type(attr)
+ try:
+ if target_type is str:
+ return val
+ elif target_type is unicode:
+ return val.decode('utf-8')
+ elif target_type is datetime.datetime:
+ return datetime.datetime.strptime(val, LDAP_GENERALIZED_TIME_FORMAT)
+ else:
+ return target_type(val)
+ except Exception, e:
+ 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):
+ return [self.decode(m, attr) for m in val]
+ elif isinstance(val, tuple):
+ return tuple(self.decode(m, attr) for m in val)
+ elif isinstance(val, dict):
+ dct = dict((unicode_from_utf8(k), self.decode(v, k)) for k, v in val.iteritems())
+ return dct
+ elif val is None:
+ return None
+ else:
+ raise TypeError("attempt to pass unsupported type from ldap, value=%s type=%s" %(val, type(val)))
def _convert_result(self, result):
'''
@@ -1115,10 +1098,7 @@ class LDAPClient(object):
@property
def schema(self):
"""schema associated with this LDAP server"""
- return self.conn.schema
-
- def has_dn_syntax(self, attr):
- return self.conn.has_dn_syntax(attr)
+ return self._get_schema()
def get_allowed_attributes(self, objectclasses, raise_on_unknown=False):
if self.schema is None:
@@ -1157,10 +1137,7 @@ class LDAPClient(object):
with self.error_handler():
# bypass ldap2's locking
object.__setattr__(self, '_conn',
- IPASimpleLDAPObject(self.ldap_uri,
- self._force_schema_updates,
- self._no_schema,
- self._decode_attrs))
+ IPASimpleLDAPObject(self.ldap_uri))
if self._start_tls:
self._conn.start_tls_s()
@@ -1179,6 +1156,7 @@ class LDAPClient(object):
Perform simple bind operation.
"""
with self.error_handler():
+ self._flush_schema()
if bind_dn is None:
bind_dn = DN()
assert isinstance(bind_dn, DN)
@@ -1194,6 +1172,7 @@ class LDAPClient(object):
"""
with self.error_handler():
auth_tokens = ldap.sasl.external(user_name)
+ self._flush_schema()
self._conn.sasl_interactive_bind_s(
'', auth_tokens, server_controls, client_controls)
@@ -1203,6 +1182,7 @@ class LDAPClient(object):
"""
with self.error_handler():
auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
+ self._flush_schema()
self._conn.sasl_interactive_bind_s(
'', auth_tokens, server_controls, client_controls)
@@ -1211,6 +1191,7 @@ class LDAPClient(object):
Perform unbind operation.
"""
with self.error_handler():
+ self._flush_schema()
self.conn.unbind_s()
def make_dn_from_attr(self, attr, value, parent_dn=None):
diff --git a/ipatests/test_xmlrpc/test_baseldap_plugin.py b/ipatests/test_xmlrpc/test_baseldap_plugin.py
index 3ffc041..6b19e57 100644
--- a/ipatests/test_xmlrpc/test_baseldap_plugin.py
+++ b/ipatests/test_xmlrpc/test_baseldap_plugin.py
@@ -181,17 +181,12 @@ def test_entry_to_dict():
elif name == 'dnattr':
return FakeAttributeType(name, '1.3.6.1.4.1.1466.115.121.1.12')
- class FakeIPASimpleLDAPObject(ipaldap.IPASimpleLDAPObject):
- def __init__(self):
- super(FakeIPASimpleLDAPObject, self).__init__('ldap://test', False)
- self._schema = FakeSchema()
- self._has_schema = True
-
class FakeLDAPClient(ipaldap.LDAPClient):
def __init__(self):
super(FakeLDAPClient, self).__init__('ldap://test',
force_schema_updates=False)
- self._conn = FakeIPASimpleLDAPObject()
+ self._has_schema = True
+ self._schema = FakeSchema()
conn = FakeLDAPClient()
rights = {'nothing': 'is'}
--
2.1.0
>From 9076defa131433e33f3534e912ae8e558d91f093 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Tue, 20 Jan 2015 12:36:14 +0000
Subject: [PATCH 15/16] ldap: Use SimpleLDAPObject instead of
IPASimpleLDAPObject in LDAPClient
---
ipapython/ipaldap.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index d135aa7..939ce15 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -830,7 +830,7 @@ class LDAPClient(object):
if not self._has_schema:
try:
schema = schema_cache.get_schema(
- self.conn.uri, self.conn.conn,
+ self.ldap_uri, self.conn,
force_update=self._force_schema_updates)
except (errors.ExecutionError, IndexError):
schema = None
@@ -1137,7 +1137,7 @@ class LDAPClient(object):
with self.error_handler():
# bypass ldap2's locking
object.__setattr__(self, '_conn',
- IPASimpleLDAPObject(self.ldap_uri))
+ ldap.initialize(self.ldap_uri))
if self._start_tls:
self._conn.start_tls_s()
--
2.1.0
>From b9fae2c2a5b358f638982dea15479b90d3e49719 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 14 Jan 2015 17:09:58 +0000
Subject: [PATCH 16/16] ldap: Remove IPASimpleLDAPObject
---
ipapython/ipaldap.py | 105 ---------------------------------------------------
1 file changed, 105 deletions(-)
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 939ce15..730b6bd 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -187,111 +187,6 @@ class SchemaCache(object):
schema_cache = SchemaCache()
-class IPASimpleLDAPObject(object):
- '''
- IPA code should never call python-ldap directly, it should only
- call python-ldap methods in this class.
- '''
-
- def __init__(self, uri):
- """An internal LDAP connection object
-
- :param uri: The LDAP URI to connect to
- """
- self.log = log_mgr.get_logger(self)
- self.uri = uri
- self.conn = SimpleLDAPObject(uri)
-
- #---------- python-ldap emulations ----------
-
- def add(self, dn, modlist):
- return self.conn.add(dn, modlist)
-
- def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None):
- return self.conn.add_ext(dn, modlist, serverctrls, clientctrls)
-
- def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None):
- return self.conn.add_ext_s(dn, modlist, serverctrls, clientctrls)
-
- def add_s(self, dn, modlist):
- return self.conn.add_s(dn, modlist)
-
- def bind(self, who, cred, method=ldap.AUTH_SIMPLE):
- return self.conn.bind(who, cred, method)
-
- def delete(self, dn):
- return self.conn.delete(dn)
-
- def delete_s(self, dn):
- return self.conn.delete_s(dn)
-
- def get_option(self, option):
- return self.conn.get_option(option)
-
- def modify_s(self, dn, modlist):
- return self.conn.modify_s(dn, modlist)
-
- def modrdn_s(self, dn, newrdn, delold=1):
- return self.conn.modrdn_s(dn, newrdn, delold)
-
- def passwd_s(self, dn, oldpw, newpw, serverctrls=None, clientctrls=None):
- return self.conn.passwd_s(dn, oldpw, newpw, serverctrls, clientctrls)
-
- def rename_s(self, dn, newrdn, newsuperior=None, delold=1):
- # NOTICE: python-ldap of version 2.3.10 and lower does not support
- # serverctrls and clientctrls for rename_s operation. Thus, these
- # options are ommited from this command until really needed
- return self.conn.rename_s(dn, newrdn, newsuperior, delold)
-
- def result(self, msgid=ldap.RES_ANY, all=1, timeout=None):
- return self.conn.result(msgid, all, timeout)
-
- def result3(self, msgid=ldap.RES_ANY, all=1, timeout=None):
- return self.conn.result3(msgid, all, timeout)
-
- def sasl_interactive_bind_s(self, who, auth, serverctrls=None,
- clientctrls=None, sasl_flags=ldap.SASL_QUIET):
- return self.conn.sasl_interactive_bind_s(who, auth, serverctrls,
- clientctrls, sasl_flags)
-
- def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- return self.conn.search(base, scope, filterstr, attrlist, attrsonly)
-
- def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- if _debug_log_ldap:
- self.log.debug(
- "ldap.search_ext: dn: %s\nfilter: %s\nattrs_list: %s",
- base, filterstr, attrlist)
-
-
- return self.conn.search_ext(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit)
-
- def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- return self.conn.search_ext_s(base, scope, filterstr, attrlist,
- attrsonly, serverctrls, clientctrls,
- timeout, sizelimit)
-
- def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- return self.conn.search_s(base, scope, filterstr, attrlist,
- attrsonly)
-
- def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1):
- return self.conn.search_st(base, scope, filterstr, attrlist, attrsonly,
- timeout)
-
- def set_option(self, option, invalue):
- return self.conn.set_option(option, invalue)
-
- def simple_bind_s(self, who=None, cred='', serverctrls=None, clientctrls=None):
- return self.conn.simple_bind_s(who, cred, serverctrls, clientctrls)
-
- def start_tls_s(self):
- return self.conn.start_tls_s()
-
- def unbind_s(self):
- return self.conn.unbind_s()
-
-
class LDAPEntry(collections.MutableMapping):
__slots__ = ('_conn', '_dn', '_names', '_nice', '_raw', '_sync',
'_not_list', '_orig', '_raw_view', '_single_value_view')
--
2.1.0
--
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code