From 708fe4dfe5671344aa3e5aba766e9a598b19f9f1 Mon Sep 17 00:00:00 2001 From: Pavel Zuna Date: Fri, 22 May 2009 12:11:20 +0200 Subject: Make ldap2 always return attribute names as lowercase. Add Encoder to ldap2 base classes and use encode_args/decode_retval where applicable. --- ipaserver/plugins/ldap2.py | 160 ++++++++++++--------------------------------- 1 file changed, 43 insertions(+), 117 deletions(-) (limited to 'ipaserver') diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 1762dc6b..480b817f 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -44,9 +44,9 @@ import ldap.sasl as _ldap_sasl from ldap.controls import LDAPControl from ldap.ldapobject import SimpleLDAPObject -from ipalib import api -from ipalib import errors +from ipalib import api, errors from ipalib.crud import CrudBackend +from ipalib.encoder import Encoder, encode_args, decode_retval # attribute syntax to python type mapping, 'SYNTAX OID': type # everything not in this dict is considered human readable unicode @@ -66,13 +66,9 @@ _syntax_mapping = { '1.3.6.1.4.1.1466.115.121.1.51': str, # Teletext Terminal Identifier } -# used to identify the Uniqueness plugin error message -_uniqueness_plugin_error = 'Another entry with the same attribute value already exists' - # SASL authentication mechanism _sasl_auth = _ldap_sasl.sasl({}, 'GSSAPI') - # universal LDAPError handler def _handle_errors(e, **kw): """ @@ -146,12 +142,15 @@ def _load_schema(host, port): return _ldap.schema.SubSchema(schema_entry[1]) - # cache schema when importing module _schema = _load_schema(api.env.ldap_host, api.env.ldap_port) +def _get_syntax(attr, value): + api.Backend.ldap2._schema.get_obj(_ldap.schema.AttributeType, attr) + + # ldap backend class -class ldap2(CrudBackend): +class ldap2(CrudBackend, Encoder): # rules for generating filters from entries MATCH_ANY = '|' # (|(filter1)(filter2)) @@ -164,10 +163,17 @@ class ldap2(CrudBackend): SCOPE_SUBTREE = _ldap.SCOPE_SUBTREE def __init__(self): + 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 = _syntax_mapping + self.encoder_settings.decode_dict_vals_table_keygen = _get_syntax + self.encoder_settings.decode_postprocessor = lambda x: string.lower(x) self._host = api.env.ldap_host self._port = api.env.ldap_port self._schema = _schema - super(ldap2, self).__init__() + CrudBackend.__init__(self) def __del__(self): self.disconnect() @@ -176,48 +182,7 @@ class ldap2(CrudBackend): using_cacert = bool(_ldap.get_option(_ldap.OPT_X_TLS_CACERTFILE)) return _get_url(self._host, self._port, using_cacert) - # encoding values from unicode to utf-8 strings for the ldap bindings - - def _encode_value(self, value): - if isinstance(value, unicode): - return value.encode('utf-8') - if value is None: - return None - if not isinstance(value, (bool, int, float, long, str)): - raise TypeError('scalar value expected, got %s' % (value,)) - return str(value) - - def _encode_values(self, values): - if isinstance(values, (list, tuple)): - return map(self._encode_value, values) - return self._encode_value(values) - - def _encode_entry_attrs(self, entry_attrs): - for (k, v) in entry_attrs.iteritems(): - entry_attrs[k] = self._encode_values(v) - - # decoding values from the ldap bindings to the appropriate type - - def _decode_value(self, value): - return value.decode('utf-8') - - def _decode_values(self, values): - if isinstance(values, (list, tuple)): - return map(self._decode_value, values) - return self._decode_value(values) - - def _decode_entry_attrs(self, entry_attrs): - for (k, v) in entry_attrs.iteritems(): - attr = self._schema.get_obj(_ldap.schema.AttributeType, k) - if attr: - attr_type = _syntax_mapping.get(attr.syntax, unicode) - if attr_type is unicode: - entry_attrs[k] = self._decode_values(v) - elif isinstance(v, (list, tuple)): - entry_attrs[k] = map(attr_type, v) - else: - entry_attrs[k] = attr_type(v) - + @encode_args(3, 4, 'bind_dn', 'bind_pw') def create_connection(self, host=None, port=None, ccache=None, bind_dn='', bind_pw='', debug_level=255, tls_cacertfile=None, tls_certfile=None, tls_keyfile=None): @@ -259,8 +224,6 @@ class ldap2(CrudBackend): conn.sasl_interactive_bind_s('', _sasl_auth) else: # no kerberos ccache, use simple bind - bind_dn = self._encode_value(bind_dn) - bind_pw = self._encode_value(bind_pw) conn.simple_bind_s(bind_dn, bind_pw) return conn @@ -268,16 +231,6 @@ class ldap2(CrudBackend): """Disconnect from LDAP server.""" self.conn.unbind_s() - # DN manipulation - # DN's could be generated for example like this: - # def execute(...): - # ldap = self.backend.ldap2 - # entry_attrs = self.args_options_2_entry(*args, *options) - # parent = ldap.get_container_rdn('accounts') - # dn = ldap.make_dn(entry_attrs, self.obj.primary_key, parent) - # # add entry with generated dn - # ldap.add_entry(dn, entry_attrs) - def normalize_dn(self, dn): """ Normalize distinguished name. @@ -287,9 +240,9 @@ class ldap2(CrudBackend): """ rdns = _ldap.dn.explode_dn(dn.lower()) if rdns: - dn = u','.join(rdns) + dn = ','.join(rdns) if not dn.endswith(self.api.env.basedn): - dn = u'%s,%s' % (dn, self.api.env.basedn) + dn = '%s,%s' % (dn, self.api.env.basedn) return dn return self.api.env.basedn @@ -306,7 +259,7 @@ class ldap2(CrudBackend): value = value[0] attr = _ldap.dn.escape_dn_chars(attr) value = _ldap.dn.escape_dn_chars(value) - return u'%s=%s' % (attr, value) + return '%s=%s' % (attr, value) def make_dn_from_rdn(self, rdn, parent_dn=''): """ @@ -316,7 +269,7 @@ class ldap2(CrudBackend): parent_dn -- DN of the parent entry (default '') """ parent_dn = self.normalize_dn(parent_dn) - return u'%s,%s' % (rdn, parent_dn) + return '%s,%s' % (rdn, parent_dn) def make_dn_from_attr(self, attr, value, parent_dn=''): """ @@ -340,17 +293,12 @@ class ldap2(CrudBackend): 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) def add_entry(self, dn, entry_attrs): """Create a new entry.""" - # encode/normalize arguments dn = self.normalize_dn(dn) - dn = self._encode_value(dn) - entry_attrs_copy = dict(entry_attrs) - self._encode_entry_attrs(entry_attrs_copy) - - # pass arguments to python-ldap try: - self.conn.add_s(dn, list(entry_attrs_copy.iteritems())) + self.conn.add_s(dn, list(entry_attrs.iteritems())) except _ldap.LDAPError, e: _handle_errors(e, **{}) @@ -387,6 +335,7 @@ class ldap2(CrudBackend): flt = '%s)' % flt return flt + @encode_args(1, 2) def make_filter_from_attr(self, attr, value, rules='|', exact=True): """ Make filter for ldap2.find_entries from attribute. @@ -402,9 +351,7 @@ class ldap2(CrudBackend): flts.append(self.make_filter_from_attr(attr, v, rules, exact)) return self.combine_filters(flts, rules) elif value is not None: - value = self._encode_value(value) value = _ldap_filter.escape_filter_chars(value) - attr = self._encode_value(attr) if exact: return '(%s=%s)' % (attr, value) return '(%s=*%s*)' % (attr, value) @@ -440,6 +387,8 @@ class ldap2(CrudBackend): ) return self.combine_filters(flts, rules) + @encode_args(1, 2, 3) + @decode_retval() def find_entries(self, filter, attrs_list=None, base_dn='', scope=_ldap.SCOPE_SUBTREE, time_limit=1, size_limit=3000): """ @@ -453,15 +402,9 @@ class ldap2(CrudBackend): time_limit -- time limit in seconds (default 1) size_limit -- size (number of entries returned) limit (default 3000) """ - # encode/normalize arguments base_dn = self.normalize_dn(base_dn) - if filter: - filter = self._encode_value(filter) - else: + if not filter: filter = '(objectClass=*)' - if attrs_list is not None: - attrs_list = self._encode_values(attrs_list) - base_dn = self._encode_value(base_dn) # pass arguments to python-ldap try: @@ -475,12 +418,6 @@ class ldap2(CrudBackend): if not res: raise errors.NotFound() - # decode results - for i in xrange(len(res)): - dn = self._decode_value(res[i][0]) - self._decode_entry_attrs(res[i][1]) - res[i] = (dn, res[i][1]) - return res def get_entry(self, dn, attrs_list=None): @@ -501,6 +438,7 @@ class ldap2(CrudBackend): """Returns a copy of the current LDAP schema.""" return copy.deepcopy(self._schema) + @encode_args(1, 2) def update_entry_rdn(self, dn, new_rdn, del_old=True): """ Update entry's relative distinguished name. @@ -508,12 +446,7 @@ class ldap2(CrudBackend): Keyword arguments: del_old -- delete old RDN value (default True) """ - # encode/normalize arguments dn = self.normalize_dn(dn) - dn = self._encode_value(dn) - new_rdn = self._encode_value(new_rdn) - - # pass arguments to python-ldap try: self.conn.rename_s(dn, new_rdn, delold=int(del_old)) except _ldap.LDAPError, e: @@ -525,7 +458,7 @@ class ldap2(CrudBackend): # 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 - self._encode_entry_attrs(entry_attrs_old) + entry_attrs_old = self.encode(entry_attrs_old) # make a copy of the original entry's attribute dict with all # attribute names converted to lowercase @@ -552,20 +485,17 @@ class ldap2(CrudBackend): return modlist + @encode_args(1, 2) def update_entry(self, dn, entry_attrs): """ Update entry's attributes. An attribute value set to None deletes all current values. """ - # encode/normalize arguments dn = self.normalize_dn(dn) - dn = self._encode_value(dn) - entry_attrs_copy = dict(entry_attrs) - self._encode_entry_attrs(entry_attrs_copy) # generate modlist - modlist = self._generate_modlist(dn, entry_attrs_copy) + modlist = self._generate_modlist(dn, entry_attrs) if not modlist: raise errors.EmptyModlist() @@ -575,32 +505,25 @@ class ldap2(CrudBackend): except _ldap.LDAPError, e: _handle_errors(e, **{}) + @encode_args(1) def delete_entry(self, dn): """Delete entry.""" - # encode/normalize arguments dn = self.normalize_dn(dn) - dn = self._encode_value(dn) - - # pass arguments to python-ldap try: self.conn.delete_s(dn) except _ldap.LDAPError, e: _handle_errors(e, **{}) + @encode_args(1, 2, 3) def modify_password(self, dn, old_pass, new_pass): """Set user password.""" - # encode/normalize arguments dn = self.normalize_dn(dn) - dn = self._encode_value(dn) - old_pass = self._encode_value(old_pass) - new_pass = self._encode_value(new_pass) - - # pass arguments to python-ldap try: self.passwd_s(dn, odl_pass, new_pass) except _ldap.LDAPError, e: _handle_errors(e, **{}) + @encode_args(1, 2, 3) def add_entry_to_group(self, dn, group_dn, member_attr='member'): """Add entry to group.""" # encode/normalize arguments @@ -610,7 +533,7 @@ class ldap2(CrudBackend): if dn == group_dn: raise errors.SameGroupError() # check if the entry exists - (dn, entry_attrs) = self.get_entry(dn, ['objectClass']) + (dn, entry_attrs) = self.get_entry(dn, ['objectclass']) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn) @@ -626,6 +549,7 @@ class ldap2(CrudBackend): except errors.EmptyModlist: raise errors.AlreadyGroupMember() + @encode_args(1, 2, 3) def remove_entry_from_group(self, dn, group_dn, member_attr='member'): """Remove entry from group.""" # encode/normalize arguments @@ -645,14 +569,16 @@ class ldap2(CrudBackend): # update group entry self.update_entry(group_dn, group_entry_attrs) + @encode_args(1) def set_entry_active(self, dn, active): """Mark entry active/inactive.""" assert isinstance(active, bool) + dn = self.normalize_dn(dn) # get the entry in question - (dn, entry_attrs) = self.get_entry(dn, ['nsAccountLock', 'memberOf']) + (dn, entry_attrs) = self.get_entry(dn, ['nsaccountlock', 'memberof']) # check nsAccountLock attribute - account_lock_attr = entry_attrs.get('nsAccountLock', ['false']) + account_lock_attr = entry_attrs.get('nsaccountlock', ['false']) account_lock_attr = account_lock_attr[0].lower() if active: if account_lock_attr == 'false': @@ -663,12 +589,12 @@ class ldap2(CrudBackend): # check if nsAccountLock attribute is in the entry itself is_member = False - member_of_attr = entry_attrs.get('memberOf', []) + member_of_attr = entry_attrs.get('memberof', []) for m in member_of_attr: if m.find('cn=activated') >= 0 or m.find('cn=inactivated') >=0: is_member = True break - if not is_member and entry_attrs.has_key('nsAccountLock'): + if not is_member and entry_attrs.has_key('nsaccountlock'): raise errors.HasNSAccountLock() activated_filter = '(cn=activated)' @@ -688,10 +614,10 @@ class ldap2(CrudBackend): # add the entry to the activated/inactivated group if necessary if active: - (dn, entry_attrs) = self.get_entry(dn, ['nsAccountLock']) + (dn, entry_attrs) = self.get_entry(dn, ['nsaccountlock']) # check if we still need to add entry to the activated group - account_lock_attr = entry_attrs.get('nsAccountLock', ['false']) + account_lock_attr = entry_attrs.get('nsaccountlock', ['false']) account_lock_attr = account_lock_attr[0].lower() if account_lock_attr == 'false': return # we don't -- cgit