summaryrefslogtreecommitdiffstats
path: root/ipaserver/plugins
diff options
context:
space:
mode:
authorJohn Dennis <jdennis@redhat.com>2012-05-13 07:36:35 -0400
committerRob Crittenden <rcritten@redhat.com>2012-08-12 16:23:24 -0400
commit94d457e83c172320707fbf13f7a1587dad128ece (patch)
treee1e2d88ee436114f1f82f2ba4141c6318089765a /ipaserver/plugins
parentbe9614654ee8232323a19ec56e551c4f66e6cc72 (diff)
downloadfreeipa.git-94d457e83c172320707fbf13f7a1587dad128ece.tar.gz
freeipa.git-94d457e83c172320707fbf13f7a1587dad128ece.tar.xz
freeipa.git-94d457e83c172320707fbf13f7a1587dad128ece.zip
Use DN objects instead of strings
* Convert every string specifying a DN into a DN object * Every place a dn was manipulated in some fashion it was replaced by the use of DN operators * Add new DNParam parameter type for parameters which are DN's * DN objects are used 100% of the time throughout the entire data pipeline whenever something is logically a dn. * Many classes now enforce DN usage for their attributes which are dn's. This is implmented via ipautil.dn_attribute_property(). The only permitted types for a class attribute specified to be a DN are either None or a DN object. * Require that every place a dn is used it must be a DN object. This translates into lot of:: assert isinstance(dn, DN) sprinkled through out the code. Maintaining these asserts is valuable to preserve DN type enforcement. The asserts can be disabled in production. The goal of 100% DN usage 100% of the time has been realized, these asserts are meant to preserve that. The asserts also proved valuable in detecting functions which did not obey their function signatures, such as the baseldap pre and post callbacks. * Moved ipalib.dn to ipapython.dn because DN class is shared with all components, not just the server which uses ipalib. * All API's now accept DN's natively, no need to convert to str (or unicode). * Removed ipalib.encoder and encode/decode decorators. Type conversion is now explicitly performed in each IPASimpleLDAPObject method which emulates a ldap.SimpleLDAPObject method. * Entity & Entry classes now utilize DN's * Removed __getattr__ in Entity & Entity clases. There were two problems with it. It presented synthetic Python object attributes based on the current LDAP data it contained. There is no way to validate synthetic attributes using code checkers, you can't search the code to find LDAP attribute accesses (because synthetic attriutes look like Python attributes instead of LDAP data) and error handling is circumscribed. Secondly __getattr__ was hiding Python internal methods which broke class semantics. * Replace use of methods inherited from ldap.SimpleLDAPObject via IPAdmin class with IPAdmin methods. Directly using inherited methods was causing us to bypass IPA logic. Mostly this meant replacing the use of search_s() with getEntry() or getList(). Similarly direct access of the LDAP data in classes using IPAdmin were replaced with calls to getValue() or getValues(). * Objects returned by ldap2.find_entries() are now compatible with either the python-ldap access methodology or the Entity/Entry access methodology. * All ldap operations now funnel through the common IPASimpleLDAPObject giving us a single location where we interface to python-ldap and perform conversions. * The above 4 modifications means we've greatly reduced the proliferation of multiple inconsistent ways to perform LDAP operations. We are well on the way to having a single API in IPA for doing LDAP (a long range goal). * All certificate subject bases are now DN's * DN objects were enhanced thusly: - find, rfind, index, rindex, replace and insert methods were added - AVA, RDN and DN classes were refactored in immutable and mutable variants, the mutable variants are EditableAVA, EditableRDN and EditableDN. By default we use the immutable variants preserving important semantics. To edit a DN cast it to an EditableDN and cast it back to DN when done editing. These issues are fully described in other documentation. - first_key_match was removed - DN equalty comparison permits comparison to a basestring * Fixed ldapupdate to work with DN's. This work included: - Enhance test_updates.py to do more checking after applying update. Add test for update_from_dict(). Convert code to use unittest classes. - Consolidated duplicate code. - Moved code which should have been in the class into the class. - Fix the handling of the 'deleteentry' update action. It's no longer necessary to supply fake attributes to make it work. Detect case where subsequent update applies a change to entry previously marked for deletetion. General clean-up and simplification of the 'deleteentry' logic. - Rewrote a couple of functions to be clearer and more Pythonic. - Added documentation on the data structure being used. - Simplfy the use of update_from_dict() * Removed all usage of get_schema() which was being called prior to accessing the .schema attribute of an object. If a class is using internal lazy loading as an optimization it's not right to require users of the interface to be aware of internal optimization's. schema is now a property and when the schema property is accessed it calls a private internal method to perform the lazy loading. * Added SchemaCache class to cache the schema's from individual servers. This was done because of the observation we talk to different LDAP servers, each of which may have it's own schema. Previously we globally cached the schema from the first server we connected to and returned that schema in all contexts. The cache includes controls to invalidate it thus forcing a schema refresh. * Schema caching is now senstive to the run time context. During install and upgrade the schema can change leading to errors due to out-of-date cached schema. The schema cache is refreshed in these contexts. * We are aware of the LDAP syntax of all LDAP attributes. Every attribute returned from an LDAP operation is passed through a central table look-up based on it's LDAP syntax. The table key is the LDAP syntax it's value is a Python callable that returns a Python object matching the LDAP syntax. There are a handful of LDAP attributes whose syntax is historically incorrect (e.g. DistguishedNames that are defined as DirectoryStrings). The table driven conversion mechanism is augmented with a table of hard coded exceptions. Currently only the following conversions occur via the table: - dn's are converted to DN objects - binary objects are converted to Python str objects (IPA convention). - everything else is converted to unicode using UTF-8 decoding (IPA convention). However, now that the table driven conversion mechanism is in place it would be trivial to do things such as converting attributes which have LDAP integer syntax into a Python integer, etc. * Expected values in the unit tests which are a DN no longer need to use lambda expressions to promote the returned value to a DN for equality comparison. The return value is automatically promoted to a DN. The lambda expressions have been removed making the code much simpler and easier to read. * Add class level logging to a number of classes which did not support logging, less need for use of root_logger. * Remove ipaserver/conn.py, it was unused. * Consolidated duplicate code wherever it was found. * Fixed many places that used string concatenation to form a new string rather than string formatting operators. This is necessary because string formatting converts it's arguments to a string prior to building the result string. You can't concatenate a string and a non-string. * Simplify logic in rename_managed plugin. Use DN operators to edit dn's. * The live version of ipa-ldap-updater did not generate a log file. The offline version did, now both do. https://fedorahosted.org/freeipa/ticket/1670 https://fedorahosted.org/freeipa/ticket/1671 https://fedorahosted.org/freeipa/ticket/1672 https://fedorahosted.org/freeipa/ticket/1673 https://fedorahosted.org/freeipa/ticket/1674 https://fedorahosted.org/freeipa/ticket/1392 https://fedorahosted.org/freeipa/ticket/2872
Diffstat (limited to 'ipaserver/plugins')
-rw-r--r--ipaserver/plugins/dogtag.py14
-rw-r--r--ipaserver/plugins/ldap2.py1123
-rw-r--r--ipaserver/plugins/selfsign.py11
3 files changed, 772 insertions, 376 deletions
diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py
index 8accb56d..baa41ad3 100644
--- a/ipaserver/plugins/dogtag.py
+++ b/ipaserver/plugins/dogtag.py
@@ -238,6 +238,8 @@ digits and nothing else follows.
from lxml import etree
import datetime
+from ipapython.dn import DN
+from ldap.filter import escape_filter_chars
# These are general status return values used when
# CMSServlet.outputError() is invoked.
@@ -1239,8 +1241,8 @@ class ra(rabase.rabase):
Check if a specified host is a master for a specified service.
"""
- base_dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (host, api.env.basedn)
- filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service
+ base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
+ filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service)
try:
ldap2 = self.api.Backend.ldap2
ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
@@ -1258,14 +1260,16 @@ class ra(rabase.rabase):
Select any host which is a master for a specified service.
"""
- base_dn = 'cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn
- filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service
+ base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
+ filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service)
try:
ldap2 = self.api.Backend.ldap2
ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
if len(ent):
entry = random.choice(ent)
- return ldap.explode_dn(dn=entry[0],notypes=True)[1]
+ dn = entry[0]
+ assert isinstance(dn, DN)
+ return dn[1].value
except Exception, e:
pass
return None
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 6a3d2164..a0b91fd5 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -1,5 +1,6 @@
# Authors:
# Pavel Zuna <pzuna@redhat.com>
+# John Dennis <jdennis@redhat.com>
#
# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
@@ -35,6 +36,8 @@ import tempfile
import time
import re
import pwd
+import sys
+from decimal import Decimal
import krbV
from ipapython.ipa_log_manager import *
@@ -42,6 +45,12 @@ import ldap as _ldap
from ldap.ldapobject import SimpleLDAPObject
import ldap.filter as _ldap_filter
import ldap.sasl as _ldap_sasl
+from ipapython.dn import DN, RDN
+from ipapython.ipautil import CIDict
+from collections import namedtuple
+from ipalib.errors import NetworkError, DatabaseError
+
+
try:
from ldap.controls.simple import GetEffectiveRightsControl #pylint: disable=F0401,E0611
except ImportError:
@@ -56,17 +65,26 @@ except ImportError:
def __init__(self, criticality, authzId=None):
LDAPControl.__init__(self, '1.3.6.1.4.1.42.2.27.9.5.2', criticality, authzId)
# for backward compatibility
-from ldap.functions import explode_dn
-from ipalib.dn import DN
from ipalib import _
import krbV
from ipalib import api, errors
from ipalib.crud import CrudBackend
-from ipalib.encoder import Encoder, encode_args, decode_retval
from ipalib.request import context
+_debug_log_ldap = False
+
+# 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
+LDAPEntry = namedtuple('LDAPEntry', ['dn', 'data'])
+
+
# Group Member types
MEMBERS_ALL = 0
MEMBERS_DIRECT = 1
@@ -75,264 +93,541 @@ MEMBERS_INDIRECT = 2
# SASL authentication mechanism
SASL_AUTH = _ldap_sasl.sasl({}, 'GSSAPI')
-class IPASimpleLDAPObject(SimpleLDAPObject):
+DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12'
+
+def unicode_from_utf8(val):
'''
- This is a thin layer over SimpleLDAPObject which allows us to utilize
- IPA specific types with the python-ldap API without the IPA caller needing
- to perform the type translation, consider this a convenience layer for the
- IPA programmer.
+ val is a UTF-8 encoded string, return a unicode object.
+ '''
+ return val.decode('utf-8')
- This subclass performs the following translations:
+def value_to_utf8(val):
+ '''
+ Coerce the val parameter to a UTF-8 encoded string representation
+ of the val.
+ '''
+
+ # If val is not a string we need to convert it to a string
+ # (specifically a unicode string). Naively we might think we need to
+ # call str(val) to convert to a string. This is incorrect because if
+ # val is already a unicode object then str() will call
+ # encode(default_encoding) returning a str object encoded with
+ # default_encoding. But we don't want to apply the default_encoding!
+ # Rather we want to guarantee the val object has been converted to a
+ # unicode string because from a unicode string we want to explicitly
+ # encode to a str using our desired encoding (utf-8 in this case).
+ #
+ # Note: calling unicode on a unicode object simply returns the exact
+ # same object (with it's ref count incremented). This means calling
+ # unicode on a unicode object is effectively a no-op, thus it's not
+ # inefficient.
- * DN objects may be passed into any ldap function expecting a dn. The DN
- object will be converted to a string before being passed to the python-ldap
- function. This allows us to maintain DN objects as DN objects in the rest
- of the code (useful for DN manipulation and DN information) and not have
- to worry about conversion to a string prior to passing it ldap.
+ return unicode(val).encode('utf-8')
+class _ServerSchema(object):
+ '''
+ Properties of a schema retrieved from an LDAP server.
'''
- def __init__(self, *args, **kwds):
- SimpleLDAPObject.__init__(self, *args, **kwds)
- def add(self, dn, modlist):
- return SimpleLDAPObject.add(self, str(dn), modlist)
+ def __init__(self, server, schema):
+ self.server = server
+ self.schema = schema
+ self.retrieve_timestamp = time.time()
- def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.add_ext(self, str(dn), modlist, serverctrls, clientctrls)
+class SchemaCache(object):
+ '''
+ Cache the schema's from individual LDAP servers.
+ '''
- def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.add_ext_s(self, str(dn), modlist, serverctrls, clientctrls)
+ def __init__(self):
+ log_mgr.get_logger(self, True)
+ self.servers = {}
- def add_s(self, dn, modlist):
- return SimpleLDAPObject.add_s(self, str(dn), modlist)
+ def get_schema(self, url, conn=None, force_update=False):
+ '''
+ Return schema belonging to a specific LDAP server.
- def compare(self, dn, attr, value):
- return SimpleLDAPObject.compare(self, str(dn), attr, value)
+ For performance reasons the schema is retrieved once and
+ cached unless force_update is True. force_update flushes the
+ existing schema for the server from the cache and reacquires
+ it.
+ '''
- def compare_ext(self, dn, attr, value, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.compare_ext(self, str(dn), attr, value, serverctrls, clientctrls)
+ if force_update:
+ self.flush(url)
- def compare_ext_s(self, dn, attr, value, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.compare_ext_s(self, str(dn), attr, value, serverctrls, clientctrls)
+ server_schema = self.servers.get(url)
+ if server_schema is None:
+ schema = self._retrieve_schema_from_server(url, conn)
+ server_schema = _ServerSchema(url, schema)
+ self.servers[url] = server_schema
+ return server_schema.schema
- def compare_s(self, dn, attr, value):
- return SimpleLDAPObject.compare_s(self, str(dn), attr, value)
+ def flush(self, url):
+ self.debug('flushing %s from SchemaCache', url)
+ try:
+ del self.servers[url]
+ except KeyError:
+ pass
- def delete(self, dn):
- return SimpleLDAPObject.delete(self, str(dn))
+ def _retrieve_schema_from_server(self, url, conn=None):
+ """
+ Retrieve the LDAP schema from the provided url and determine if
+ User-Private Groups (upg) are configured.
- def delete_ext(self, dn, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.delete_ext(self, str(dn), serverctrls, clientctrls)
+ Bind using kerberos credentials. If in the context of the
+ in-tree "lite" server then use the current ccache. If in the context of
+ Apache then create a new ccache and bind using the Apache HTTP service
+ principal.
- def delete_ext_s(self, dn, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.delete_ext_s(self, str(dn), serverctrls, clientctrls)
+ If a connection is provided then it the credentials bound to it are
+ used. The connection is not closed when the request is done.
+ """
+ tmpdir = None
+ has_conn = conn is not None
- def delete_s(self, dn):
- return SimpleLDAPObject.delete_s(self, str(dn))
+ self.debug('retrieving schema for SchemaCache url=%s conn=%s', url, conn)
- def modify(self, dn, modlist):
- return SimpleLDAPObject.modify(self, str(dn), modlist)
+ try:
+ if api.env.context == 'server' and conn is None:
+ # FIXME: is this really what we want to do?
+ # This seems like this logic is in the wrong place and may conflict with other state.
+ try:
+ # Create a new credentials cache for this Apache process
+ tmpdir = tempfile.mkdtemp(prefix = "tmp-")
+ ccache_file = 'FILE:%s/ccache' % tmpdir
+ krbcontext = krbV.default_context()
+ principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm))
+ keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext)
+ principal = krbV.Principal(name=principal, context=krbcontext)
+ prev_ccache = os.environ.get('KRB5CCNAME')
+ os.environ['KRB5CCNAME'] = ccache_file
+ ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal)
+ ccache.init(principal)
+ ccache.init_creds_keytab(keytab=keytab, principal=principal)
+ except krbV.Krb5Error, e:
+ raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e)))
+ finally:
+ if prev_ccache is not None:
+ os.environ['KRB5CCNAME'] = prev_ccache
+
+
+ if conn is None:
+ conn = IPASimpleLDAPObject(url)
+ if url.startswith('ldapi://'):
+ conn.set_option(_ldap.OPT_HOST_NAME, api.env.host)
+ conn.sasl_interactive_bind_s(None, SASL_AUTH)
+
+ schema_entry = conn.search_s('cn=schema', _ldap.SCOPE_BASE,
+ attrlist=['attributetypes', 'objectclasses'])[0]
+ if not has_conn:
+ conn.unbind_s()
+ except _ldap.SERVER_DOWN:
+ raise NetworkError(uri=url,
+ error=u'LDAP Server Down, unable to retrieve LDAP schema')
+ except _ldap.LDAPError, e:
+ desc = e.args[0]['desc'].strip()
+ info = e.args[0].get('info', '').strip()
+ raise DatabaseError(desc = u'uri=%s' % url,
+ info = u'Unable to retrieve LDAP schema: %s: %s' % (desc, info))
+ except IndexError:
+ # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema'
+ # TODO: DS uses 'cn=schema', support for other server?
+ # raise a more appropriate exception
+ raise
+ finally:
+ if tmpdir:
+ shutil.rmtree(tmpdir)
+
+ return _ldap.schema.SubSchema(schema_entry[1])
+
+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.
+ '''
- def modify_ext(self, dn, modlist, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.modify_ext(self, str(dn), modlist, serverctrls, clientctrls)
+ # Note: the oid for dn syntax is: 1.3.6.1.4.1.1466.115.121.1.12
- def modify_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.modify_ext_s(self, str(dn), modlist, serverctrls, clientctrls)
+ _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
+ }
- def modify_s(self, dn, modlist):
- return SimpleLDAPObject.modify_s(self, str(dn), modlist)
+ # 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 :-(
+
+ _SCHEMA_OVERRIDE = CIDict({
+ 'managedtemplate': DN_SYNTAX_OID, # DN
+ 'managedbase': DN_SYNTAX_OID, # DN
+ 'originscope': DN_SYNTAX_OID, # DN
+ })
+
+ def __init__(self, uri):
+ log_mgr.get_logger(self, True)
+ self.uri = uri
+ self.conn = SimpleLDAPObject(uri)
+ self._schema = None
+
+ def _get_schema(self):
+ if self._schema is None:
+ # The schema may be updated during install or during
+ # updates, make sure we have a current version of the
+ # schema, not an out of date cached version.
+ force_update = api.env.context in ('installer', 'updates')
+ self._schema = schema_cache.get_schema(self.uri, self.conn, force_update=force_update)
+ 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._schema = None
+
+ def get_syntax(self, attr):
+ # Is this a special case attribute?
+ syntax = self._SCHEMA_OVERRIDE.get(attr)
+ if syntax is not None:
+ return syntax
+
+ # Try to lookup the syntax in the schema returned by the server
+ obj = self.schema.get_obj(_ldap.schema.AttributeType, attr)
+ if obj is not None:
+ return obj.syntax
+ else:
+ return None
- def modrdn(self, dn, newrdn, delold=1):
- return SimpleLDAPObject.modrdn(self, str(dn), str(newrdn), delold)
+ def has_dn_syntax(self, attr):
+ """
+ Check the schema to see if the attribute uses DN syntax.
- def modrdn_s(self, dn, newrdn, delold=1):
- return SimpleLDAPObject.modrdn_s(self, str(dn), str(newrdn), delold)
+ Returns True/False
+ """
+ syntax = self.get_syntax(attr)
+ return syntax == DN_SYNTAX_OID
+
+
+ def encode(self, val):
+ '''
+ '''
+ # 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, 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 val is None:
+ return None
+ else:
+ raise TypeError("attempt to pass unsupported type to ldap, value=%s type=%s" %(val, type(val)))
- def read_subschemasubentry_s(self, subschemasubentry_dn, attrs=None):
- return SimpleLDAPObject.read_subschemasubentry_s(self, str(subschemasubentry_dn), attrs)
+ def convert_value_list(self, attr, target_type, values):
+ '''
+ '''
- def rename(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.rename(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls)
+ ipa_values = []
- def rename_s(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None):
- return SimpleLDAPObject.rename_s(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls)
+ for original_value in values:
+ if isinstance(target_type, type) and isinstance(original_value, target_type):
+ ipa_value = original_value
+ else:
+ try:
+ ipa_value = target_type(original_value)
+ except Exception, e:
+ msg = 'unable to convert the attribute "%s" value "%s" to type %s' % (attr, original_value, target_type)
+ self.error(msg)
+ raise ValueError(msg)
- def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- return SimpleLDAPObject.search(self, str(base), scope, filterstr, attrlist, attrsonly)
+ ipa_values.append(ipa_value)
- def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0,
- serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- return SimpleLDAPObject.search_ext(self, str(base), scope, filterstr, attrlist, attrsonly,
- serverctrls, clientctrls, timeout, sizelimit)
+ return ipa_values
- def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0,
- serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0):
- return SimpleLDAPObject.search_ext_s(self, str(base), scope, filterstr, attrlist, attrsonly,
- serverctrls, clientctrls, timeout, sizelimit)
+ 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.
- def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0):
- return SimpleLDAPObject.search_s(self, str(base), scope, filterstr, attrlist, attrsonly)
+ We convert the dn to a DN object.
- def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1):
- return SimpleLDAPObject.search_st(self, str(base), scope, filterstr, attrlist, attrsonly, timeout)
+ We convert every value associated with an attribute according
+ to it's syntax into the desired Python type.
- def search_subschemasubentry_s(self, dn=''):
- return SimpleLDAPObject.search_subschemasubentry_s(self, str(dn))
+ returns a IPA result tuple of the same form as a python-ldap
+ result tuple except everything inside of the result tuple has
+ been converted to it's preferred IPA python type.
+ '''
-# universal LDAPError handler
-def _handle_errors(e, **kw):
- """
- Centralize error handling in one place.
+ ipa_result = []
+ for dn_tuple in result:
+ original_dn = dn_tuple[0]
+ original_attrs = dn_tuple[1]
- e is the error to be raised
- **kw is an exception-specific list of options
- """
- if not isinstance(e, _ldap.TIMEOUT):
- desc = e.args[0]['desc'].strip()
- info = e.args[0].get('info', '').strip()
- else:
- desc = ''
- info = ''
-
- try:
- # re-raise the error so we can handle it
- raise e
- except _ldap.NO_SUCH_OBJECT:
- # args = kw.get('args', '')
- # raise errors.NotFound(msg=notfound(args))
- raise errors.NotFound(reason='no such entry')
- except _ldap.ALREADY_EXISTS:
- raise errors.DuplicateEntry()
- except _ldap.CONSTRAINT_VIOLATION:
- # This error gets thrown by the uniqueness plugin
- if info.startswith('Another entry with the same attribute value already exists'):
- raise errors.DuplicateEntry()
- else:
- raise errors.DatabaseError(desc=desc, info=info)
- except _ldap.INSUFFICIENT_ACCESS:
- raise errors.ACIError(info=info)
- except _ldap.INVALID_CREDENTIALS:
- raise errors.ACIError(info="%s %s" % (info, desc))
- except _ldap.NO_SUCH_ATTRIBUTE:
- # this is raised when a 'delete' attribute isn't found.
- # it indicates the previous attribute was removed by another
- # update, making the oldentry stale.
- raise errors.MidairCollision()
- except _ldap.INVALID_SYNTAX:
- raise errors.InvalidSyntax(attr=info)
- except _ldap.OBJECT_CLASS_VIOLATION:
- raise errors.ObjectclassViolation(info=info)
- except _ldap.ADMINLIMIT_EXCEEDED:
- raise errors.LimitsExceeded()
- except _ldap.SIZELIMIT_EXCEEDED:
- raise errors.LimitsExceeded()
- except _ldap.TIMELIMIT_EXCEEDED:
- raise errors.LimitsExceeded()
- except _ldap.NOT_ALLOWED_ON_RDN:
- raise errors.NotAllowedOnRDN(attr=info)
- except _ldap.FILTER_ERROR:
- raise errors.BadSearchFilter(info=info)
- except _ldap.SUCCESS:
- pass
- except _ldap.LDAPError, e:
- if 'NOT_ALLOWED_TO_DELEGATE' in info:
- raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE")
- root_logger.info('Unhandled LDAPError: %s' % str(e))
- raise errors.DatabaseError(desc=desc, info=info)
-
-
-def get_schema(url, conn=None):
- """
- Perform global initialization when the module is loaded.
+ ipa_dn = DN(original_dn)
+ ipa_attrs = dict()
- Retrieve the LDAP schema from the provided url and determine if
- User-Private Groups (upg) are configured.
+ for attr, original_values in original_attrs.items():
+ target_type = self._SYNTAX_MAPPING.get(self.get_syntax(attr), unicode_from_utf8)
+ ipa_attrs[attr.lower()] = self.convert_value_list(attr, target_type, original_values)
- Bind using kerberos credentials. If in the context of the
- in-tree "lite" server then use the current ccache. If in the context of
- Apache then create a new ccache and bind using the Apache HTTP service
- principal.
+ ipa_result.append(LDAPEntry(ipa_dn, ipa_attrs))
- If a connection is provided then it the credentials bound to it are
- used. The connection is not closed when the request is done.
- """
- tmpdir = None
- has_conn = conn is not None
+ if _debug_log_ldap:
+ self.debug('ldap.result: %s', ipa_result)
+ return ipa_result
- if ((not api.env.in_server or api.env.context not in ['lite', 'server'])
- and conn is None):
- # The schema is only needed on the server side
- return None
+ #---------- python-ldap emulations ----------
- try:
- if api.env.context == 'server' and conn is None:
- try:
- # Create a new credentials cache for this Apache process
- tmpdir = tempfile.mkdtemp(prefix = "tmp-")
- ccache_file = 'FILE:%s/ccache' % tmpdir
- krbcontext = krbV.default_context()
- principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm))
- keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext)
- principal = krbV.Principal(name=principal, context=krbcontext)
- os.environ['KRB5CCNAME'] = ccache_file
- ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal)
- ccache.init(principal)
- ccache.init_creds_keytab(keytab=keytab, principal=principal)
- except krbV.Krb5Error, e:
- raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e)))
-
- if conn is None:
- conn = IPASimpleLDAPObject(url)
- if url.startswith('ldapi://'):
- conn.set_option(_ldap.OPT_HOST_NAME, api.env.host)
- conn.sasl_interactive_bind_s('', SASL_AUTH)
-
- schema_entry = conn.search_s(
- 'cn=schema', _ldap.SCOPE_BASE,
- attrlist=['attributetypes', 'objectclasses']
- )[0]
- if not has_conn:
- conn.unbind_s()
- except _ldap.SERVER_DOWN:
- return None
- except _ldap.LDAPError, e:
- desc = e.args[0]['desc'].strip()
- info = e.args[0].get('info', '').strip()
- raise StandardError('Unable to retrieve LDAP schema: %s: %s' % (desc, info))
- except IndexError:
- # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema'
- # TODO: DS uses 'cn=schema', support for other server?
- # raise a more appropriate exception
- raise
- finally:
- if tmpdir:
- shutil.rmtree(tmpdir)
-
- return _ldap.schema.SubSchema(schema_entry[1])
-
-# Global schema
-_schema = None
-
-class ldap2(CrudBackend, Encoder):
+ 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, serverctrls=None, clientctrls=None):
+ assert isinstance(dn, DN)
+ dn = str(dn)
+ assert isinstance(newrdn, (DN, RDN))
+ newrdn = str(newrdn)
+ return self.conn.rename_s(dn, newrdn, newsuperior, delold, serverctrls, clientctrls)
+
+ 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
+
+ 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)
+
+ 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.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):
+ 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
+
+ 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
+
+ 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
+
+ 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):
+ return self.conn.start_tls_s()
+
+ def unbind(self):
+ self.flush_cached_schema()
+ return self.conn.unbind()
+
+ def unbind_s(self):
+ self.flush_cached_schema()
+ return self.conn.unbind_s()
+
+class ldap2(CrudBackend):
"""
LDAP Backend Take 2.
"""
- # attribute syntax to python type mapping, 'SYNTAX OID': type
- # everything not in this dict is considered human readable unicode
- _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
- }
-
# attributes in this list cannot be deleted by update_entry
# only MOD_REPLACE operations are generated for them
_FORCE_REPLACE_ON_UPDATE_ATTRS = []
@@ -349,27 +644,19 @@ class ldap2(CrudBackend, Encoder):
def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None,
schema=None):
- global _schema
+ log_mgr.get_logger(self, True)
CrudBackend.__init__(self, shared_instance=shared_instance)
- Encoder.__init__(self)
- self.encoder_settings.encode_dict_keys = True
- self.encoder_settings.decode_dict_keys = True
- self.encoder_settings.decode_dict_vals_postprocess = False
- self.encoder_settings.decode_dict_vals_table = self._SYNTAX_MAPPING
- self.encoder_settings.decode_dict_vals_table_keygen = self.get_syntax
- self.encoder_settings.decode_postprocessor = lambda x: string.lower(x)
try:
self.ldap_uri = ldap_uri or api.env.ldap_uri
except AttributeError:
self.ldap_uri = 'ldap://example.com'
try:
if base_dn is not None:
- self.base_dn = base_dn
+ self.base_dn = DN(base_dn)
else:
- self.base_dn = api.env.basedn
+ self.base_dn = DN(api.env.basedn)
except AttributeError:
- self.base_dn = ''
- self.schema = schema or _schema
+ self.base_dn = DN()
def __del__(self):
if self.isconnected():
@@ -378,18 +665,83 @@ class ldap2(CrudBackend, Encoder):
def __str__(self):
return self.ldap_uri
+ def _get_schema(self):
+ return self.conn.schema
+ schema = property(_get_schema, None, None, 'schema associated with this LDAP server')
+
+ # universal LDAPError handler
+ def handle_errors(self, e):
+ """
+ Centralize error handling in one place.
+
+ e is the error to be raised
+ """
+ if not isinstance(e, _ldap.TIMEOUT):
+ desc = e.args[0]['desc'].strip()
+ info = e.args[0].get('info', '').strip()
+ else:
+ desc = ''
+ info = ''
+
+ try:
+ # re-raise the error so we can handle it
+ raise e
+ except _ldap.NO_SUCH_OBJECT:
+ raise errors.NotFound(reason='no such entry')
+ except _ldap.ALREADY_EXISTS:
+ raise errors.DuplicateEntry()
+ except _ldap.CONSTRAINT_VIOLATION:
+ # This error gets thrown by the uniqueness plugin
+ if info.startswith('Another entry with the same attribute value already exists'):
+ raise errors.DuplicateEntry()
+ else:
+ raise errors.DatabaseError(desc=desc, info=info)
+ except _ldap.INSUFFICIENT_ACCESS:
+ raise errors.ACIError(info=info)
+ except _ldap.INVALID_CREDENTIALS:
+ raise errors.ACIError(info="%s %s" % (info, desc))
+ except _ldap.NO_SUCH_ATTRIBUTE:
+ # this is raised when a 'delete' attribute isn't found.
+ # it indicates the previous attribute was removed by another
+ # update, making the oldentry stale.
+ raise errors.MidairCollision()
+ except _ldap.INVALID_SYNTAX:
+ raise errors.InvalidSyntax(attr=info)
+ except _ldap.OBJECT_CLASS_VIOLATION:
+ raise errors.ObjectclassViolation(info=info)
+ except _ldap.ADMINLIMIT_EXCEEDED:
+ raise errors.LimitsExceeded()
+ except _ldap.SIZELIMIT_EXCEEDED:
+ raise errors.LimitsExceeded()
+ except _ldap.TIMELIMIT_EXCEEDED:
+ raise errors.LimitsExceeded()
+ except _ldap.NOT_ALLOWED_ON_RDN:
+ raise errors.NotAllowedOnRDN(attr=info)
+ except _ldap.FILTER_ERROR:
+ raise errors.BadSearchFilter(info=info)
+ except _ldap.SUCCESS:
+ pass
+ except _ldap.LDAPError, e:
+ if 'NOT_ALLOWED_TO_DELEGATE' in info:
+ raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE")
+ self.info('Unhandled LDAPError: %s' % str(e))
+ raise errors.DatabaseError(desc=desc, info=info)
+
def get_syntax(self, attr, value):
- if not self.schema:
- self.get_schema()
+ if self.schema is None:
+ return None
obj = self.schema.get_obj(_ldap.schema.AttributeType, attr)
if obj is not None:
return obj.syntax
else:
return None
+ def has_dn_syntax(self, attr):
+ return self.conn.has_dn_syntax(attr)
+
def get_allowed_attributes(self, objectclasses, raise_on_unknown=False):
- if not self.schema:
- self.get_schema()
+ if self.schema is None:
+ return None
allowed_attributes = []
for oc in objectclasses:
obj = self.schema.get_obj(_ldap.schema.ObjectClass, oc)
@@ -408,13 +760,12 @@ class ldap2(CrudBackend, Encoder):
If there is a problem loading the schema or the attribute is
not in the schema return None
"""
- if not self.schema:
- self.get_schema()
+ if self.schema is None:
+ return None
obj = self.schema.get_obj(_ldap.schema.AttributeType, attr)
return obj and obj.single_value
- @encode_args(2, 3, 'bind_dn', 'bind_pw')
- def create_connection(self, ccache=None, bind_dn='', bind_pw='',
+ def create_connection(self, ccache=None, bind_dn=None, bind_pw='',
tls_cacertfile=None, tls_certfile=None, tls_keyfile=None,
debug_level=0, autobind=False):
"""
@@ -433,7 +784,9 @@ class ldap2(CrudBackend, Encoder):
Extends backend.Connectible.create_connection.
"""
- global _schema
+ if bind_dn is None:
+ bind_dn = DN()
+ assert isinstance(bind_dn, DN)
if tls_cacertfile is not None:
_ldap.set_option(_ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile)
if tls_certfile is not None:
@@ -445,7 +798,7 @@ class ldap2(CrudBackend, Encoder):
_ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level)
try:
- conn = _ldap.initialize(self.ldap_uri)
+ conn = IPASimpleLDAPObject(self.ldap_uri)
if self.ldap_uri.startswith('ldapi://') and ccache:
conn.set_option(_ldap.OPT_HOST_NAME, api.env.host)
minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN)
@@ -459,7 +812,7 @@ class ldap2(CrudBackend, Encoder):
conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf)
if ccache is not None:
os.environ['KRB5CCNAME'] = ccache
- conn.sasl_interactive_bind_s('', SASL_AUTH)
+ conn.sasl_interactive_bind_s(None, SASL_AUTH)
principal = krbV.CCache(name=ccache,
context=krbV.default_context()).principal().name
setattr(context, 'principal', principal)
@@ -468,15 +821,13 @@ class ldap2(CrudBackend, Encoder):
if autobind:
pent = pwd.getpwuid(os.geteuid())
auth_tokens = _ldap.sasl.external(pent.pw_name)
- conn.sasl_interactive_bind_s("", auth_tokens)
+ conn.sasl_interactive_bind_s(None, auth_tokens)
else:
conn.simple_bind_s(bind_dn, bind_pw)
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
- if _schema:
- object.__setattr__(self, 'schema', _schema)
return conn
def destroy_connection(self):
@@ -489,55 +840,41 @@ class ldap2(CrudBackend, Encoder):
def normalize_dn(self, dn):
"""
- Normalize distinguished name.
+ Normalize distinguished name by assuring it ends with
+ the base_dn.
Note: You don't have to normalize DN's before passing them to
ldap2 methods. It's done internally for you.
"""
- rdns = explode_dn(dn)
- if rdns:
- dn = ','.join(rdns)
- if not dn.endswith(self.base_dn):
- dn = '%s,%s' % (dn, self.base_dn)
- return dn
- return self.base_dn
-
- def get_container_rdn(self, name):
- """Get relative distinguished name of cotainer."""
- env_container = 'container_%s' % name
- if env_container in self.api.env:
- return self.api.env[env_container]
- return ''
- def make_rdn_from_attr(self, attr, value):
- """Make relative distinguished name from attribute."""
- if isinstance(value, (list, tuple)):
- value = value[0]
- attr = _ldap.dn.escape_dn_chars(attr)
- value = _ldap.dn.escape_dn_chars(value)
- return '%s=%s' % (attr, value)
+ assert isinstance(dn, DN)
- def make_dn_from_rdn(self, rdn, parent_dn=''):
- """
- Make distinguished name from relative distinguished name.
+ if not dn.endswith(self.base_dn):
+ # DN's are mutable, don't use in-place addtion (+=) which would
+ # modify the dn passed in with unintended side-effects. Addition
+ # returns a new DN object which is the concatenation of the two.
+ dn = dn + self.base_dn
- Keyword arguments:
- parent_dn -- DN of the parent entry (default '')
- """
- parent_dn = self.normalize_dn(parent_dn)
- return '%s,%s' % (rdn, parent_dn)
+ return dn
- def make_dn_from_attr(self, attr, value, parent_dn=''):
+ def make_dn_from_attr(self, attr, value, parent_dn=None):
"""
Make distinguished name from attribute.
Keyword arguments:
parent_dn -- DN of the parent entry (default '')
"""
- rdn = self.make_rdn_from_attr(attr, value)
- return self.make_dn_from_rdn(rdn, parent_dn)
+ if parent_dn is None:
+ parent_dn = DN()
+ assert isinstance(parent_dn, DN)
+ parent_dn = self.normalize_dn(parent_dn)
+
+ if isinstance(value, (list, tuple)):
+ value = value[0]
+
+ return DN((attr, value), parent_dn)
- def make_dn(self, entry_attrs, primary_key='cn', parent_dn=''):
+ def make_dn(self, entry_attrs, primary_key='cn', parent_dn=None):
"""
Make distinguished name from entry attributes.
@@ -545,23 +882,32 @@ class ldap2(CrudBackend, Encoder):
primary_key -- attribute from which to make RDN (default 'cn')
parent_dn -- DN of the parent entry (default '')
"""
+
assert primary_key in entry_attrs
- rdn = self.make_rdn_from_attr(primary_key, entry_attrs[primary_key])
- return self.make_dn_from_rdn(rdn, parent_dn)
- @encode_args(1, 2)
+ if parent_dn is None:
+ parent_dn = DN()
+
+ parent_dn = self.normalize_dn(parent_dn)
+ return DN((primary_key, entry_attrs[primary_key]), parent_dn)
+
def add_entry(self, dn, entry_attrs, normalize=True):
"""Create a new entry."""
+
+ assert isinstance(dn, DN)
+
if normalize:
dn = self.normalize_dn(dn)
- # remove all None values, python-ldap hates'em
+ # remove all None or [] values, python-ldap hates'em
entry_attrs = dict(
- (k, v) for (k, v) in entry_attrs.iteritems() if v
+ # FIXME, shouldn't these values be an error?
+ (k, v) for (k, v) in entry_attrs.iteritems()
+ if v is not None and v != []
)
try:
self.conn.add_s(dn, list(entry_attrs.iteritems()))
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
# generating filters for find_entry
# some examples:
@@ -580,7 +926,9 @@ class ldap2(CrudBackend, Encoder):
Keyword arguments:
rules -- see ldap2.make_filter
"""
+
assert isinstance(filters, (list, tuple))
+
filters = [f for f in filters if f]
if filters and rules == self.MATCH_NONE: # unary operator
return '(%s%s)' % (self.MATCH_NONE,
@@ -598,7 +946,6 @@ class ldap2(CrudBackend, Encoder):
flt = '%s)' % flt
return flt
- @encode_args(1, 2)
def make_filter_from_attr(self, attr, value, rules='|', exact=True,
leading_wildcard=True, trailing_wildcard=True):
"""
@@ -620,7 +967,7 @@ class ldap2(CrudBackend, Encoder):
trailing_wildcard=trailing_wildcard) for v in value ]
return self.combine_filters(flts, rules)
elif value is not None:
- value = _ldap_filter.escape_filter_chars(value)
+ value = _ldap_filter.escape_filter_chars(value_to_utf8(value))
if not exact:
template = '%s'
if leading_wildcard:
@@ -671,9 +1018,7 @@ class ldap2(CrudBackend, Encoder):
)
return self.combine_filters(flts, rules)
- @encode_args(1, 2, 3)
- @decode_retval()
- def find_entries(self, filter=None, attrs_list=None, base_dn='',
+ def find_entries(self, filter=None, attrs_list=None, base_dn=None,
scope=_ldap.SCOPE_SUBTREE, time_limit=None, size_limit=None,
normalize=True, search_refs=False):
"""
@@ -691,6 +1036,9 @@ class ldap2(CrudBackend, Encoder):
normalize -- normalize the DN (default True)
search_refs -- allow search references to be returned (default skips these entries)
"""
+ if base_dn is None:
+ base_dn = DN()
+ assert isinstance(base_dn, DN)
if normalize:
base_dn = self.normalize_dn(base_dn)
if not filter:
@@ -731,7 +1079,7 @@ class ldap2(CrudBackend, Encoder):
_ldap.SIZELIMIT_EXCEEDED), e:
truncated = True
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
if not res and not truncated:
raise errors.NotFound(reason='no such entry')
@@ -756,7 +1104,8 @@ class ldap2(CrudBackend, Encoder):
del r[1]['memberOf']
else:
continue
- (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit, size_limit=size_limit, normalize=normalize)
+ (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit,
+ size_limit=size_limit, normalize=normalize)
if len(direct) > 0:
r[1]['memberof'] = direct
if len(indirect) > 0:
@@ -764,8 +1113,7 @@ class ldap2(CrudBackend, Encoder):
return (res, truncated)
- def find_entry_by_attr(self, attr, value, object_class, attrs_list=None,
- base_dn=''):
+ def find_entry_by_attr(self, attr, value, object_class, attrs_list=None, base_dn=None):
"""
Find entry (dn, entry_attrs) by attribute and object class.
@@ -773,6 +1121,11 @@ class ldap2(CrudBackend, Encoder):
attrs_list - list of attributes to return, all if None (default None)
base_dn - dn of the entry at which to start the search (default '')
"""
+
+ if base_dn is None:
+ base_dn = DN()
+ assert isinstance(base_dn, DN)
+
search_kw = {attr: value, 'objectClass': object_class}
filter = self.make_filter(search_kw, rules=self.MATCH_ALL)
(entries, truncated) = self.find_entries(filter, attrs_list, base_dn)
@@ -793,6 +1146,9 @@ class ldap2(CrudBackend, Encoder):
Keyword arguments:
attrs_list - list of attributes to return, all if None (default None)
"""
+
+ assert isinstance(dn, DN)
+
(entry, truncated) = self.find_entries(
None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit,
size_limit=size_limit, normalize=normalize
@@ -805,7 +1161,12 @@ class ldap2(CrudBackend, Encoder):
config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]}
def get_ipa_config(self, attrs_list=None):
"""Returns the IPA configuration entry (dn, entry_attrs)."""
- cdn = "%s,%s" % (api.Object.config.get_dn(), api.env.basedn)
+
+ odn = api.Object.config.get_dn()
+ assert isinstance(odn, DN)
+ assert isinstance(api.env.basedn, DN)
+ cdn = DN(odn, api.env.basedn)
+
try:
config_entry = getattr(context, 'config_entry')
return (cdn, copy.deepcopy(config_entry))
@@ -828,35 +1189,17 @@ class ldap2(CrudBackend, Encoder):
setattr(context, 'config_entry', copy.deepcopy(config_entry))
return (cdn, config_entry)
- def get_schema(self, deepcopy=False):
- """Returns either a reference to current schema or its deep copy"""
- global _schema
- if not _schema:
- _schema = get_schema(self.ldap_uri, self.conn)
- if not _schema:
- raise errors.DatabaseError(desc='Unable to retrieve LDAP schema', info='Unable to proceed with request')
- # explicitly use setattr here so the schema can be set after
- # the object is finalized.
- object.__setattr__(self, 'schema', _schema)
-
- if (deepcopy):
- return copy.deepcopy(self.schema)
- else:
- return self.schema
-
def has_upg(self):
"""Returns True/False whether User-Private Groups are enabled.
This is determined based on whether the UPG Template exists.
"""
- upg_dn = str(DN('cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc', api.env.basedn))
+ upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'),
+ ('cn', 'etc'), api.env.basedn)
try:
- upg_entry = self.conn.search_s(
- upg_dn,
- _ldap.SCOPE_BASE,
- attrlist=['*']
- )[0]
+ upg_entry = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE,
+ attrlist=['*'])[0]
disable_attr = '(objectclass=disable)'
if 'originfilter' in upg_entry[1]:
org_filter = upg_entry[1]['originfilter']
@@ -866,27 +1209,32 @@ class ldap2(CrudBackend, Encoder):
except _ldap.NO_SUCH_OBJECT, e:
return False
- @encode_args(1, 2)
def get_effective_rights(self, dn, entry_attrs):
"""Returns the rights the currently bound user has for the given DN.
Returns 2 attributes, the attributeLevelRights for the given list of
attributes and the entryLevelRights for the entry itself.
"""
+
+ assert isinstance(dn, DN)
+
principal = getattr(context, 'principal')
(binddn, attrs) = self.find_entry_by_attr("krbprincipalname", principal, "krbPrincipalAux")
- sctrl = [GetEffectiveRightsControl(True, "dn: " + binddn.encode('UTF-8'))]
+ assert isinstance(binddn, DN)
+ sctrl = [GetEffectiveRightsControl(True, "dn: " + str(binddn))]
self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl)
(dn, attrs) = self.get_entry(dn, entry_attrs)
# remove the control so subsequent operations don't include GER
self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, [])
return (dn, attrs)
- @encode_args(1, 2)
def can_write(self, dn, attr):
"""Returns True/False if the currently bound user has write permissions
on the attribute. This only operates on a single attribute at a time.
"""
+
+ assert isinstance(dn, DN)
+
(dn, attrs) = self.get_effective_rights(dn, [attr])
if 'attributelevelrights' in attrs:
attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8')
@@ -896,11 +1244,12 @@ class ldap2(CrudBackend, Encoder):
return False
- @encode_args(1, 2)
def can_read(self, dn, attr):
"""Returns True/False if the currently bound user has read permissions
on the attribute. This only operates on a single attribute at a time.
"""
+ assert isinstance(dn, DN)
+
(dn, attrs) = self.get_effective_rights(dn, [attr])
if 'attributelevelrights' in attrs:
attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8')
@@ -919,11 +1268,13 @@ class ldap2(CrudBackend, Encoder):
# v - View the entry
#
- @encode_args(1)
def can_delete(self, dn):
"""Returns True/False if the currently bound user has delete permissions
on the entry.
"""
+
+ assert isinstance(dn, DN)
+
(dn, attrs) = self.get_effective_rights(dn, ["*"])
if 'entrylevelrights' in attrs:
entry_rights = attrs['entrylevelrights'][0].decode('UTF-8')
@@ -932,11 +1283,11 @@ class ldap2(CrudBackend, Encoder):
return False
- @encode_args(1)
def can_add(self, dn):
"""Returns True/False if the currently bound user has add permissions
on the entry.
"""
+ assert isinstance(dn, DN)
(dn, attrs) = self.get_effective_rights(dn, ["*"])
if 'entrylevelrights' in attrs:
entry_rights = attrs['entrylevelrights'][0].decode('UTF-8')
@@ -945,7 +1296,6 @@ class ldap2(CrudBackend, Encoder):
return False
- @encode_args(1, 2)
def update_entry_rdn(self, dn, new_rdn, del_old=True):
"""
Update entry's relative distinguished name.
@@ -953,22 +1303,25 @@ class ldap2(CrudBackend, Encoder):
Keyword arguments:
del_old -- delete old RDN value (default True)
"""
+
+ assert isinstance(dn, DN)
+ assert isinstance(new_rdn, RDN)
+
dn = self.normalize_dn(dn)
- if dn.startswith(new_rdn + ","):
+ if dn[0] == new_rdn:
raise errors.EmptyModlist()
try:
self.conn.rename_s(dn, new_rdn, delold=int(del_old))
time.sleep(.3) # Give memberOf plugin a chance to work
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
def _generate_modlist(self, dn, entry_attrs, normalize):
+ assert isinstance(dn, DN)
+
# get original entry
(dn, entry_attrs_old) = self.get_entry(dn, entry_attrs.keys(), normalize=normalize)
- # get_entry returns a decoded entry, encode it back
- # we could call search_s directly, but this saves a lot of code at
- # the expense of a little bit of performace
- entry_attrs_old = self.encode(entry_attrs_old)
+
# generate modlist
# for multi value attributes: no MOD_REPLACE to handle simultaneous
# updates better
@@ -1009,13 +1362,14 @@ class ldap2(CrudBackend, Encoder):
return modlist
- @encode_args(1, 2)
def update_entry(self, dn, entry_attrs, normalize=True):
"""
Update entry's attributes.
An attribute value set to None deletes all current values.
"""
+
+ assert isinstance(dn, DN)
if normalize:
dn = self.normalize_dn(dn)
@@ -1028,37 +1382,40 @@ class ldap2(CrudBackend, Encoder):
try:
self.conn.modify_s(dn, modlist)
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
- @encode_args(1)
def delete_entry(self, dn, normalize=True):
"""Delete entry."""
+
+ assert isinstance(dn, DN)
if normalize:
dn = self.normalize_dn(dn)
+
try:
self.conn.delete_s(dn)
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
- @encode_args(1, 2, 3)
def modify_password(self, dn, new_pass, old_pass=''):
"""Set user password."""
+
+ assert isinstance(dn, DN)
dn = self.normalize_dn(dn)
# The python-ldap passwd command doesn't verify the old password
# so we'll do a simple bind to validate it.
if old_pass != '':
try:
- conn = _ldap.initialize(self.ldap_uri)
+ conn = IPASimpleLDAPObject(self.ldap_uri)
conn.simple_bind_s(dn, old_pass)
conn.unbind()
except _ldap.LDAPError, e:
- _handle_errors(e, **{})
+ self.handle_errors(e)
try:
self.conn.passwd_s(dn, old_pass, new_pass)
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False):
"""
@@ -1068,12 +1425,18 @@ class ldap2(CrudBackend, Encoder):
Adding a group as a member of itself is not allowed unless allow_same
is True.
"""
+
+ assert isinstance(dn, DN)
+ assert isinstance(group_dn, DN)
+
+ self.debug("add_entry_to_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr)
# check if the entry exists
(dn, entry_attrs) = self.get_entry(dn, ['objectclass'])
# get group entry
(group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr])
+ self.debug("add_entry_to_group: group_entry_attrs=%s", group_entry_attrs)
# check if we're not trying to add group into itself
if dn == group_dn and not allow_same:
raise errors.SameGroupError()
@@ -1091,16 +1454,23 @@ class ldap2(CrudBackend, Encoder):
def remove_entry_from_group(self, dn, group_dn, member_attr='member'):
"""Remove entry from group."""
+
+ assert isinstance(dn, DN)
+ assert isinstance(group_dn, DN)
+
+ self.debug("remove_entry_from_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr)
# get group entry
(group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr])
+ self.debug("remove_entry_from_group: group_entry_attrs=%s", group_entry_attrs)
# remove dn from group entry's `member_attr` attribute
- members = [DN(m) for m in group_entry_attrs.get(member_attr, [])]
+ members = group_entry_attrs.get(member_attr, [])
+ assert all([isinstance(x, DN) for x in members])
try:
- members.remove(DN(dn))
+ members.remove(dn)
except ValueError:
raise errors.NotGroupMember()
- group_entry_attrs[member_attr] = [str(m) for m in members]
+ group_entry_attrs[member_attr] = members
# update group entry
self.update_entry(group_dn, group_entry_attrs)
@@ -1118,10 +1488,14 @@ class ldap2(CrudBackend, Encoder):
Returns a list of DNs.
"""
+
+ assert isinstance(group_dn, DN)
+
if membertype not in [MEMBERS_ALL, MEMBERS_DIRECT, MEMBERS_INDIRECT]:
return None
- search_group_dn = _ldap_filter.escape_filter_chars(group_dn)
+ self.debug("get_members: group_dn=%s members=%s membertype=%s", group_dn, members, membertype)
+ search_group_dn = _ldap_filter.escape_filter_chars(str(group_dn))
searchfilter = "(memberof=%s)" % search_group_dn
attr_list.append("member")
@@ -1130,18 +1504,23 @@ class ldap2(CrudBackend, Encoder):
results = []
if membertype == MEMBERS_ALL or membertype == MEMBERS_INDIRECT:
- checkmembers = copy.deepcopy(members)
- for member in checkmembers:
+ user_container_dn = DN(api.env.container_user, api.env.basedn) # FIXME, initialize once
+ host_container_dn = DN(api.env.container_host, api.env.basedn)
+ checkmembers = set(DN(x) for x in members)
+ checked = set()
+ while checkmembers:
+ member_dn = checkmembers.pop()
+ checked.add(member_dn)
+
# No need to check entry types that are not nested for
# additional members
- dn = DN(member)
- if dn.endswith(DN(api.env.container_user, api.env.basedn)) or \
- dn.endswith(DN(api.env.container_host, api.env.basedn)):
- results.append([member, {}])
+ if member_dn.endswith(user_container_dn) or \
+ member_dn.endswith(host_container_dn):
+ results.append([member_dn, {}])
continue
try:
(result, truncated) = self.find_entries(searchfilter,
- attr_list, member, time_limit=time_limit,
+ attr_list, member_dn, time_limit=time_limit,
size_limit=size_limit, scope=_ldap.SCOPE_BASE,
normalize=normalize)
if truncated:
@@ -1150,8 +1529,8 @@ class ldap2(CrudBackend, Encoder):
for m in result[0][1].get('member', []):
# This member may contain other members, add it to our
# candidate list
- if m not in checkmembers:
- checkmembers.append(m)
+ if m not in checked:
+ checkmembers.add(m)
except errors.NotFound:
pass
@@ -1164,21 +1543,18 @@ class ldap2(CrudBackend, Encoder):
(dn, group) = self.get_entry(group_dn, ['dn', 'member'],
size_limit=size_limit, time_limit=time_limit)
- real_members = group.get('member')
- if isinstance(real_members, basestring):
- real_members = [real_members]
- if real_members is None:
- real_members = []
+ real_members = group.get('member', [])
entries = []
for e in results:
- if unicode(e[0]) not in real_members and unicode(e[0]) not in entries:
+ if e[0] not in real_members and e[0] not in entries:
if membertype == MEMBERS_INDIRECT:
entries.append(e[0])
else:
if membertype == MEMBERS_DIRECT:
entries.append(e[0])
+ self.debug("get_members: result=%s", entries)
return entries
def get_memberof(self, entry_dn, memberof, time_limit=None, size_limit=None, normalize=True):
@@ -1192,12 +1568,15 @@ class ldap2(CrudBackend, Encoder):
Returns two memberof lists: (direct, indirect)
"""
+ assert isinstance(entry_dn, DN)
+
+ self.debug("get_memberof: entry_dn=%s memberof=%s", entry_dn, memberof)
if not type(memberof) in (list, tuple):
return ([], [])
if len(memberof) == 0:
return ([], [])
- search_entry_dn = _ldap_filter.escape_filter_chars(entry_dn)
+ search_entry_dn = _ldap_filter.escape_filter_chars(str(entry_dn))
attr_list = ["dn", "memberof"]
searchfilter = "(|(member=%s)(memberhost=%s)(memberuser=%s))" % (
search_entry_dn, search_entry_dn, search_entry_dn)
@@ -1207,6 +1586,7 @@ class ldap2(CrudBackend, Encoder):
results = []
for group in memberof:
+ assert isinstance(group, DN)
try:
(result, truncated) = self.find_entries(searchfilter, attr_list,
group, time_limit=time_limit,size_limit=size_limit,
@@ -1219,20 +1599,24 @@ class ldap2(CrudBackend, Encoder):
# If there is an exception here, it is likely due to a failure in
# referential integrity. All members should have corresponding
# memberOf entries.
- indirect = [ m.lower() for m in memberof ]
+ indirect = list(memberof)
for r in results:
direct.append(r[0])
try:
- indirect.remove(r[0].lower())
+ indirect.remove(r[0])
except ValueError, e:
- root_logger.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn)
+ self.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn)
raise e
+ self.debug("get_memberof: result direct=%s indirect=%s", direct, indirect)
return (direct, indirect)
def set_entry_active(self, dn, active):
"""Mark entry active/inactive."""
+
+ assert isinstance(dn, DN)
assert isinstance(active, bool)
+
# get the entry in question
(dn, entry_attrs) = self.get_entry(dn, ['nsaccountlock'])
@@ -1255,15 +1639,20 @@ class ldap2(CrudBackend, Encoder):
def activate_entry(self, dn):
"""Mark entry active."""
+
+ assert isinstance(dn, DN)
self.set_entry_active(dn, True)
def deactivate_entry(self, dn):
"""Mark entry inactive."""
+
+ assert isinstance(dn, DN)
self.set_entry_active(dn, False)
def remove_principal_key(self, dn):
"""Remove a kerberos principal key."""
+ assert isinstance(dn, DN)
dn = self.normalize_dn(dn)
# We need to do this directly using the LDAP library because we
@@ -1275,11 +1664,14 @@ class ldap2(CrudBackend, Encoder):
try:
self.conn.modify_s(dn, mod)
except _ldap.LDAPError, e:
- _handle_errors(e)
+ self.handle_errors(e)
# CrudBackend methods
def _get_normalized_entry_for_crud(self, dn, attrs_list=None):
+
+ assert isinstance(dn, DN)
+
(dn, entry_attrs) = self.get_entry(dn, attrs_list)
entry_attrs['dn'] = dn
return entry_attrs
@@ -1292,6 +1684,7 @@ class ldap2(CrudBackend, Encoder):
"""
assert 'dn' in kw
dn = kw['dn']
+ assert isinstance(dn, DN)
del kw['dn']
self.add_entry(dn, kw)
return self._get_normalized_entry_for_crud(dn)
@@ -1337,7 +1730,8 @@ class ldap2(CrudBackend, Encoder):
# get keyword arguments
filter = kw.pop('filter', None)
attrs_list = kw.pop('attrs_list', None)
- base_dn = kw.pop('base_dn', '')
+ base_dn = kw.pop('base_dn', DN())
+ assert isinstance(base_dn, DN)
scope = kw.pop('scope', self.SCOPE_SUBTREE)
# generate filter
@@ -1363,4 +1757,3 @@ class ldap2(CrudBackend, Encoder):
return (len(output), output)
api.register(ldap2)
-
diff --git a/ipaserver/plugins/selfsign.py b/ipaserver/plugins/selfsign.py
index bbf8fa78..09ed04f4 100644
--- a/ipaserver/plugins/selfsign.py
+++ b/ipaserver/plugins/selfsign.py
@@ -39,6 +39,7 @@ from ipalib import Backend
from ipalib import errors
from ipalib import x509
from ipalib import pkcs10
+from ipapython.dn import DN, EditableDN, RDN
from ipapython.certdb import get_ca_nickname
import subprocess
import os
@@ -86,16 +87,14 @@ class ra(rabase.rabase):
"""
try:
config = api.Command['config_show']()['result']
- subject_base = config.get('ipacertificatesubjectbase')[0]
+ subject_base = EditableDN(config.get('ipacertificatesubjectbase')[0])
hostname = get_csr_hostname(csr)
- base = re.split(',\s*(?=\w+=)', subject_base)
- base.insert(0,'CN=%s' % hostname)
- subject_base = ",".join(base)
+ subject_base.insert(0, RDN(('CN', hostname)))
request = pkcs10.load_certificate_request(csr)
# python-nss normalizes the request subject
- request_subject = str(pkcs10.get_subject(request))
+ request_subject = DN(pkcs10.get_subject(request))
- if str(subject_base).lower() != request_subject.lower():
+ if subject_base != request_subject:
raise errors.CertificateOperationError(error=_('Request subject "%(request_subject)s" does not match the form "%(subject_base)s"') % \
{'request_subject' : request_subject, 'subject_base' : subject_base})
except errors.CertificateOperationError, e: