Make ldap2 always return attribute names as lowercase. Add Encoder to ldap2 base classes and use encode_args/decode_retval where applicable.

This commit is contained in:
Pavel Zuna
2009-05-22 12:11:20 +02:00
committed by Jason Gerard DeRose
parent 9468c9fca8
commit 708fe4dfe5

View File

@@ -44,9 +44,9 @@ import ldap.sasl as _ldap_sasl
from ldap.controls import LDAPControl from ldap.controls import LDAPControl
from ldap.ldapobject import SimpleLDAPObject from ldap.ldapobject import SimpleLDAPObject
from ipalib import api from ipalib import api, errors
from ipalib import errors
from ipalib.crud import CrudBackend from ipalib.crud import CrudBackend
from ipalib.encoder import Encoder, encode_args, decode_retval
# attribute syntax to python type mapping, 'SYNTAX OID': type # attribute syntax to python type mapping, 'SYNTAX OID': type
# everything not in this dict is considered human readable unicode # 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 '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 authentication mechanism
_sasl_auth = _ldap_sasl.sasl({}, 'GSSAPI') _sasl_auth = _ldap_sasl.sasl({}, 'GSSAPI')
# universal LDAPError handler # universal LDAPError handler
def _handle_errors(e, **kw): def _handle_errors(e, **kw):
""" """
@@ -146,12 +142,15 @@ def _load_schema(host, port):
return _ldap.schema.SubSchema(schema_entry[1]) return _ldap.schema.SubSchema(schema_entry[1])
# cache schema when importing module # cache schema when importing module
_schema = _load_schema(api.env.ldap_host, api.env.ldap_port) _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 # ldap backend class
class ldap2(CrudBackend): class ldap2(CrudBackend, Encoder):
# rules for generating filters from entries # rules for generating filters from entries
MATCH_ANY = '|' # (|(filter1)(filter2)) MATCH_ANY = '|' # (|(filter1)(filter2))
@@ -164,10 +163,17 @@ class ldap2(CrudBackend):
SCOPE_SUBTREE = _ldap.SCOPE_SUBTREE SCOPE_SUBTREE = _ldap.SCOPE_SUBTREE
def __init__(self): 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._host = api.env.ldap_host
self._port = api.env.ldap_port self._port = api.env.ldap_port
self._schema = _schema self._schema = _schema
super(ldap2, self).__init__() CrudBackend.__init__(self)
def __del__(self): def __del__(self):
self.disconnect() self.disconnect()
@@ -176,48 +182,7 @@ class ldap2(CrudBackend):
using_cacert = bool(_ldap.get_option(_ldap.OPT_X_TLS_CACERTFILE)) using_cacert = bool(_ldap.get_option(_ldap.OPT_X_TLS_CACERTFILE))
return _get_url(self._host, self._port, using_cacert) return _get_url(self._host, self._port, using_cacert)
# encoding values from unicode to utf-8 strings for the ldap bindings @encode_args(3, 4, 'bind_dn', 'bind_pw')
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)
def create_connection(self, host=None, port=None, ccache=None, def create_connection(self, host=None, port=None, ccache=None,
bind_dn='', bind_pw='', debug_level=255, bind_dn='', bind_pw='', debug_level=255,
tls_cacertfile=None, tls_certfile=None, tls_keyfile=None): tls_cacertfile=None, tls_certfile=None, tls_keyfile=None):
@@ -259,8 +224,6 @@ class ldap2(CrudBackend):
conn.sasl_interactive_bind_s('', _sasl_auth) conn.sasl_interactive_bind_s('', _sasl_auth)
else: else:
# no kerberos ccache, use simple bind # 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) conn.simple_bind_s(bind_dn, bind_pw)
return conn return conn
@@ -268,16 +231,6 @@ class ldap2(CrudBackend):
"""Disconnect from LDAP server.""" """Disconnect from LDAP server."""
self.conn.unbind_s() 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): def normalize_dn(self, dn):
""" """
Normalize distinguished name. Normalize distinguished name.
@@ -287,9 +240,9 @@ class ldap2(CrudBackend):
""" """
rdns = _ldap.dn.explode_dn(dn.lower()) rdns = _ldap.dn.explode_dn(dn.lower())
if rdns: if rdns:
dn = u','.join(rdns) dn = ','.join(rdns)
if not dn.endswith(self.api.env.basedn): 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 dn
return self.api.env.basedn return self.api.env.basedn
@@ -306,7 +259,7 @@ class ldap2(CrudBackend):
value = value[0] value = value[0]
attr = _ldap.dn.escape_dn_chars(attr) attr = _ldap.dn.escape_dn_chars(attr)
value = _ldap.dn.escape_dn_chars(value) 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=''): 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 -- DN of the parent entry (default '')
""" """
parent_dn = self.normalize_dn(parent_dn) 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=''): 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]) rdn = self.make_rdn_from_attr(primary_key, entry_attrs[primary_key])
return self.make_dn_from_rdn(rdn, parent_dn) return self.make_dn_from_rdn(rdn, parent_dn)
@encode_args(1, 2)
def add_entry(self, dn, entry_attrs): def add_entry(self, dn, entry_attrs):
"""Create a new entry.""" """Create a new entry."""
# encode/normalize arguments
dn = self.normalize_dn(dn) 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: try:
self.conn.add_s(dn, list(entry_attrs_copy.iteritems())) self.conn.add_s(dn, list(entry_attrs.iteritems()))
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
_handle_errors(e, **{}) _handle_errors(e, **{})
@@ -387,6 +335,7 @@ class ldap2(CrudBackend):
flt = '%s)' % flt flt = '%s)' % flt
return flt return flt
@encode_args(1, 2)
def make_filter_from_attr(self, attr, value, rules='|', exact=True): def make_filter_from_attr(self, attr, value, rules='|', exact=True):
""" """
Make filter for ldap2.find_entries from attribute. 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)) flts.append(self.make_filter_from_attr(attr, v, rules, exact))
return self.combine_filters(flts, rules) return self.combine_filters(flts, rules)
elif value is not None: elif value is not None:
value = self._encode_value(value)
value = _ldap_filter.escape_filter_chars(value) value = _ldap_filter.escape_filter_chars(value)
attr = self._encode_value(attr)
if exact: if exact:
return '(%s=%s)' % (attr, value) return '(%s=%s)' % (attr, value)
return '(%s=*%s*)' % (attr, value) return '(%s=*%s*)' % (attr, value)
@@ -440,6 +387,8 @@ class ldap2(CrudBackend):
) )
return self.combine_filters(flts, rules) return self.combine_filters(flts, rules)
@encode_args(1, 2, 3)
@decode_retval()
def find_entries(self, filter, attrs_list=None, base_dn='', def find_entries(self, filter, attrs_list=None, base_dn='',
scope=_ldap.SCOPE_SUBTREE, time_limit=1, size_limit=3000): 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) time_limit -- time limit in seconds (default 1)
size_limit -- size (number of entries returned) limit (default 3000) size_limit -- size (number of entries returned) limit (default 3000)
""" """
# encode/normalize arguments
base_dn = self.normalize_dn(base_dn) base_dn = self.normalize_dn(base_dn)
if filter: if not filter:
filter = self._encode_value(filter)
else:
filter = '(objectClass=*)' 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 # pass arguments to python-ldap
try: try:
@@ -475,12 +418,6 @@ class ldap2(CrudBackend):
if not res: if not res:
raise errors.NotFound() 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 return res
def get_entry(self, dn, attrs_list=None): def get_entry(self, dn, attrs_list=None):
@@ -501,6 +438,7 @@ class ldap2(CrudBackend):
"""Returns a copy of the current LDAP schema.""" """Returns a copy of the current LDAP schema."""
return copy.deepcopy(self._schema) return copy.deepcopy(self._schema)
@encode_args(1, 2)
def update_entry_rdn(self, dn, new_rdn, del_old=True): def update_entry_rdn(self, dn, new_rdn, del_old=True):
""" """
Update entry's relative distinguished name. Update entry's relative distinguished name.
@@ -508,12 +446,7 @@ class ldap2(CrudBackend):
Keyword arguments: Keyword arguments:
del_old -- delete old RDN value (default True) del_old -- delete old RDN value (default True)
""" """
# encode/normalize arguments
dn = self.normalize_dn(dn) dn = self.normalize_dn(dn)
dn = self._encode_value(dn)
new_rdn = self._encode_value(new_rdn)
# pass arguments to python-ldap
try: try:
self.conn.rename_s(dn, new_rdn, delold=int(del_old)) self.conn.rename_s(dn, new_rdn, delold=int(del_old))
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
@@ -525,7 +458,7 @@ class ldap2(CrudBackend):
# get_entry returns a decoded entry, encode it back # get_entry returns a decoded entry, encode it back
# we could call search_s directly, but this saves a lot of code at # we could call search_s directly, but this saves a lot of code at
# the expense of a little bit of performace # 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 # make a copy of the original entry's attribute dict with all
# attribute names converted to lowercase # attribute names converted to lowercase
@@ -552,20 +485,17 @@ class ldap2(CrudBackend):
return modlist return modlist
@encode_args(1, 2)
def update_entry(self, dn, entry_attrs): def update_entry(self, dn, entry_attrs):
""" """
Update entry's attributes. Update entry's attributes.
An attribute value set to None deletes all current values. An attribute value set to None deletes all current values.
""" """
# encode/normalize arguments
dn = self.normalize_dn(dn) 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 # generate modlist
modlist = self._generate_modlist(dn, entry_attrs_copy) modlist = self._generate_modlist(dn, entry_attrs)
if not modlist: if not modlist:
raise errors.EmptyModlist() raise errors.EmptyModlist()
@@ -575,32 +505,25 @@ class ldap2(CrudBackend):
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
_handle_errors(e, **{}) _handle_errors(e, **{})
@encode_args(1)
def delete_entry(self, dn): def delete_entry(self, dn):
"""Delete entry.""" """Delete entry."""
# encode/normalize arguments
dn = self.normalize_dn(dn) dn = self.normalize_dn(dn)
dn = self._encode_value(dn)
# pass arguments to python-ldap
try: try:
self.conn.delete_s(dn) self.conn.delete_s(dn)
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
_handle_errors(e, **{}) _handle_errors(e, **{})
@encode_args(1, 2, 3)
def modify_password(self, dn, old_pass, new_pass): def modify_password(self, dn, old_pass, new_pass):
"""Set user password.""" """Set user password."""
# encode/normalize arguments
dn = self.normalize_dn(dn) 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: try:
self.passwd_s(dn, odl_pass, new_pass) self.passwd_s(dn, odl_pass, new_pass)
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
_handle_errors(e, **{}) _handle_errors(e, **{})
@encode_args(1, 2, 3)
def add_entry_to_group(self, dn, group_dn, member_attr='member'): def add_entry_to_group(self, dn, group_dn, member_attr='member'):
"""Add entry to group.""" """Add entry to group."""
# encode/normalize arguments # encode/normalize arguments
@@ -610,7 +533,7 @@ class ldap2(CrudBackend):
if dn == group_dn: if dn == group_dn:
raise errors.SameGroupError() raise errors.SameGroupError()
# check if the entry exists # check if the entry exists
(dn, entry_attrs) = self.get_entry(dn, ['objectClass']) (dn, entry_attrs) = self.get_entry(dn, ['objectclass'])
# get group entry # get group entry
(group_dn, group_entry_attrs) = self.get_entry(group_dn) (group_dn, group_entry_attrs) = self.get_entry(group_dn)
@@ -626,6 +549,7 @@ class ldap2(CrudBackend):
except errors.EmptyModlist: except errors.EmptyModlist:
raise errors.AlreadyGroupMember() raise errors.AlreadyGroupMember()
@encode_args(1, 2, 3)
def remove_entry_from_group(self, dn, group_dn, member_attr='member'): def remove_entry_from_group(self, dn, group_dn, member_attr='member'):
"""Remove entry from group.""" """Remove entry from group."""
# encode/normalize arguments # encode/normalize arguments
@@ -645,14 +569,16 @@ class ldap2(CrudBackend):
# update group entry # update group entry
self.update_entry(group_dn, group_entry_attrs) self.update_entry(group_dn, group_entry_attrs)
@encode_args(1)
def set_entry_active(self, dn, active): def set_entry_active(self, dn, active):
"""Mark entry active/inactive.""" """Mark entry active/inactive."""
assert isinstance(active, bool) assert isinstance(active, bool)
dn = self.normalize_dn(dn)
# get the entry in question # 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 # 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() account_lock_attr = account_lock_attr[0].lower()
if active: if active:
if account_lock_attr == 'false': if account_lock_attr == 'false':
@@ -663,12 +589,12 @@ class ldap2(CrudBackend):
# check if nsAccountLock attribute is in the entry itself # check if nsAccountLock attribute is in the entry itself
is_member = False is_member = False
member_of_attr = entry_attrs.get('memberOf', []) member_of_attr = entry_attrs.get('memberof', [])
for m in member_of_attr: for m in member_of_attr:
if m.find('cn=activated') >= 0 or m.find('cn=inactivated') >=0: if m.find('cn=activated') >= 0 or m.find('cn=inactivated') >=0:
is_member = True is_member = True
break break
if not is_member and entry_attrs.has_key('nsAccountLock'): if not is_member and entry_attrs.has_key('nsaccountlock'):
raise errors.HasNSAccountLock() raise errors.HasNSAccountLock()
activated_filter = '(cn=activated)' activated_filter = '(cn=activated)'
@@ -688,10 +614,10 @@ class ldap2(CrudBackend):
# add the entry to the activated/inactivated group if necessary # add the entry to the activated/inactivated group if necessary
if active: 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 # 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() account_lock_attr = account_lock_attr[0].lower()
if account_lock_attr == 'false': if account_lock_attr == 'false':
return # we don't return # we don't