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.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