diff options
author | Jan Cholasta <jcholast@redhat.com> | 2015-01-26 16:33:50 +0000 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2015-04-16 06:58:31 +0000 |
commit | db88498c956a608ab09168ecd70ec7d4d755d0b2 (patch) | |
tree | c5ddc0993a782ba8e28179e5037d009c3cae6d2b | |
parent | cbb112dd57940cdb128dd5535f0ff3a826009641 (diff) | |
download | freeipa-db88498c956a608ab09168ecd70ec7d4d755d0b2.tar.gz freeipa-db88498c956a608ab09168ecd70ec7d4d755d0b2.tar.xz freeipa-db88498c956a608ab09168ecd70ec7d4d755d0b2.zip |
ldap: Move schema handling from IPASimpleLDAPObject to LDAPClient
Reviewed-By: Petr Viktorin <pviktori@redhat.com>
-rw-r--r-- | ipalib/plugins/baseldap.py | 2 | ||||
-rw-r--r-- | ipapython/ipaldap.py | 541 | ||||
-rw-r--r-- | 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 0b1a48107..865a8ccd8 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 98a43b79f..a562dd44b 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 3ffc041f9..6b19e57c1 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'} |