'), item)
continue
if attr_map not in self.attribute_mapping:
LOG.warn(_('Invalid additional attribute mapping: "%(item)s". '
'Value "%(attr_map)s" must use one of %(keys)s.'),
{'item': item, 'attr_map': attr_map,
'keys': ', '.join(self.attribute_mapping.keys())})
continue
mapping[ldap_attr] = attr_map
return mapping
**** CubicPower OpenStack Study ****
def get_connection(self, user=None, password=None):
handler = get_handler(self.LDAP_URL)
conn = handler(self.LDAP_URL,
self.page_size,
alias_dereferencing=self.alias_dereferencing,
use_tls=self.use_tls,
tls_cacertfile=self.tls_cacertfile,
tls_cacertdir=self.tls_cacertdir,
tls_req_cert=self.tls_req_cert,
chase_referrals=self.chase_referrals)
if user is None:
user = self.LDAP_USER
if password is None:
password = self.LDAP_PASSWORD
# not all LDAP servers require authentication, so we don't bind
# if we don't have any user/pass
if user and password:
conn.simple_bind_s(user, password)
return conn
**** CubicPower OpenStack Study ****
def _id_to_dn_string(self, object_id):
return '%s=%s,%s' % (self.id_attr,
ldap.dn.escape_dn_chars(str(object_id)),
self.tree_dn)
**** CubicPower OpenStack Study ****
def _id_to_dn(self, object_id):
if self.LDAP_SCOPE == ldap.SCOPE_ONELEVEL:
return self._id_to_dn_string(object_id)
conn = self.get_connection()
try:
search_result = conn.search_s(
self.tree_dn, self.LDAP_SCOPE,
'(&(%(id_attr)s=%(id)s)(objectclass=%(objclass)s))' %
{'id_attr': self.id_attr,
'id': ldap.filter.escape_filter_chars(str(object_id)),
'objclass': self.object_class})
finally:
conn.unbind_s()
if search_result:
dn, attrs = search_result[0]
return dn
else:
return self._id_to_dn_string(object_id)
@staticmethod
**** CubicPower OpenStack Study ****
def _dn_to_id(dn):
return ldap.dn.str2dn(dn)[0][0][1]
**** CubicPower OpenStack Study ****
def _ldap_res_to_model(self, res):
obj = self.model(id=self._dn_to_id(res[0]))
for k in obj.known_keys:
if k in self.attribute_ignore:
continue
try:
v = res[1][self.attribute_mapping.get(k, k)]
except KeyError:
pass
else:
try:
obj[k] = v[0]
except IndexError:
obj[k] = None
return obj
**** CubicPower OpenStack Study ****
def check_allow_create(self):
if not self.allow_create:
action = _('LDAP %s create') % self.options_name
raise exception.ForbiddenAction(action=action)
**** CubicPower OpenStack Study ****
def check_allow_update(self):
if not self.allow_update:
action = _('LDAP %s update') % self.options_name
raise exception.ForbiddenAction(action=action)
**** CubicPower OpenStack Study ****
def check_allow_delete(self):
if not self.allow_delete:
action = _('LDAP %s delete') % self.options_name
raise exception.ForbiddenAction(action=action)
**** CubicPower OpenStack Study ****
def affirm_unique(self, values):
if values.get('name') is not None:
try:
self.get_by_name(values['name'])
except exception.NotFound:
pass
else:
raise exception.Conflict(type=self.options_name,
details=_('Duplicate name, %s.') %
values['name'])
if values.get('id') is not None:
try:
self.get(values['id'])
except exception.NotFound:
pass
else:
raise exception.Conflict(type=self.options_name,
details=_('Duplicate ID, %s.') %
values['id'])
**** CubicPower OpenStack Study ****
def create(self, values):
self.affirm_unique(values)
conn = self.get_connection()
object_classes = self.structural_classes + [self.object_class]
attrs = [('objectClass', object_classes)]
for k, v in six.iteritems(values):
if k == 'id' or k in self.attribute_ignore:
continue
if v is not None:
attr_type = self.attribute_mapping.get(k, k)
if attr_type is not None:
attrs.append((attr_type, [v]))
extra_attrs = [attr for attr, name
in six.iteritems(self.extra_attr_mapping)
if name == k]
for attr in extra_attrs:
attrs.append((attr, [v]))
if 'groupOfNames' in object_classes and self.use_dumb_member:
attrs.append(('member', [self.dumb_member]))
try:
conn.add_s(self._id_to_dn(values['id']), attrs)
finally:
conn.unbind_s()
return values
**** CubicPower OpenStack Study ****
def _ldap_get(self, object_id, ldap_filter=None):
conn = self.get_connection()
query = ('(&(%(id_attr)s=%(id)s)'
'%(filter)s'
'(objectClass=%(object_class)s))'
% {'id_attr': self.id_attr,
'id': ldap.filter.escape_filter_chars(str(object_id)),
'filter': (ldap_filter or self.ldap_filter or ''),
'object_class': self.object_class})
try:
attrs = list(set((self.attribute_mapping.values() +
self.extra_attr_mapping.keys())))
res = conn.search_s(self.tree_dn, self.LDAP_SCOPE, query, attrs)
except ldap.NO_SUCH_OBJECT:
return None
finally:
conn.unbind_s()
try:
return res[0]
except IndexError:
return None
**** CubicPower OpenStack Study ****
def _ldap_get_all(self, ldap_filter=None):
conn = self.get_connection()
query = '(&%s(objectClass=%s))' % (ldap_filter or
self.ldap_filter or
'', self.object_class)
try:
return conn.search_s(self.tree_dn,
self.LDAP_SCOPE,
query,
self.attribute_mapping.values())
except ldap.NO_SUCH_OBJECT:
return []
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
def get(self, object_id, ldap_filter=None):
res = self._ldap_get(object_id, ldap_filter)
if res is None:
raise self._not_found(object_id)
else:
return self._ldap_res_to_model(res)
**** CubicPower OpenStack Study ****
def get_by_name(self, name, ldap_filter=None):
query = ('(%s=%s)' % (self.attribute_mapping['name'],
ldap.filter.escape_filter_chars(name)))
res = self.get_all(query)
try:
return res[0]
except IndexError:
raise self._not_found(name)
**** CubicPower OpenStack Study ****
def get_all(self, ldap_filter=None):
return [self._ldap_res_to_model(x)
for x in self._ldap_get_all(ldap_filter)]
**** CubicPower OpenStack Study ****
def update(self, object_id, values, old_obj=None):
if old_obj is None:
old_obj = self.get(object_id)
modlist = []
for k, v in six.iteritems(values):
if k == 'id' or k in self.attribute_ignore:
continue
# attribute value has not changed
if k in old_obj and old_obj[k] == v:
continue
if k in self.immutable_attrs:
msg = (_("Cannot change %(option_name)s %(attr)s") %
{'option_name': self.options_name, 'attr': k})
raise exception.ValidationError(msg)
if v is None:
if old_obj.get(k) is not None:
modlist.append((ldap.MOD_DELETE,
self.attribute_mapping.get(k, k),
None))
continue
current_value = old_obj.get(k)
if current_value is None:
op = ldap.MOD_ADD
modlist.append((op, self.attribute_mapping.get(k, k), [v]))
elif current_value != v:
op = ldap.MOD_REPLACE
modlist.append((op, self.attribute_mapping.get(k, k), [v]))
if modlist:
conn = self.get_connection()
try:
conn.modify_s(self._id_to_dn(object_id), modlist)
except ldap.NO_SUCH_OBJECT:
raise self._not_found(object_id)
finally:
conn.unbind_s()
return self.get(object_id)
**** CubicPower OpenStack Study ****
def delete(self, object_id):
conn = self.get_connection()
try:
conn.delete_s(self._id_to_dn(object_id))
except ldap.NO_SUCH_OBJECT:
raise self._not_found(object_id)
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
def deleteTree(self, object_id):
conn = self.get_connection()
tree_delete_control = ldap.controls.LDAPControl(CONTROL_TREEDELETE,
0,
None)
try:
conn.delete_ext_s(self._id_to_dn(object_id),
serverctrls=[tree_delete_control])
except ldap.NO_SUCH_OBJECT:
raise self._not_found(object_id)
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
class LdapWrapper(object):
**** CubicPower OpenStack Study ****
def __init__(self, url, page_size, alias_dereferencing=None,
use_tls=False, tls_cacertfile=None, tls_cacertdir=None,
tls_req_cert='demand', chase_referrals=None):
LOG.debug(_("LDAP init: url=%s"), url)
LOG.debug(_('LDAP init: use_tls=%(use_tls)s\n'
'tls_cacertfile=%(tls_cacertfile)s\n'
'tls_cacertdir=%(tls_cacertdir)s\n'
'tls_req_cert=%(tls_req_cert)s\n'
'tls_avail=%(tls_avail)s\n'),
{'use_tls': use_tls,
'tls_cacertfile': tls_cacertfile,
'tls_cacertdir': tls_cacertdir,
'tls_req_cert': tls_req_cert,
'tls_avail': ldap.TLS_AVAIL
})
#NOTE(topol)
#for extra debugging uncomment the following line
#ldap.set_option(ldap.OPT_DEBUG_LEVEL, 4095)
using_ldaps = url.lower().startswith("ldaps")
if use_tls and using_ldaps:
raise AssertionError(_('Invalid TLS / LDAPS combination'))
if use_tls:
if not ldap.TLS_AVAIL:
raise ValueError(_('Invalid LDAP TLS_AVAIL option: %s. TLS '
'not available') % ldap.TLS_AVAIL)
if tls_cacertfile:
#NOTE(topol)
#python ldap TLS does not verify CACERTFILE or CACERTDIR
#so we add some extra simple sanity check verification
#Also, setting these values globally (i.e. on the ldap object)
#works but these values are ignored when setting them on the
#connection
if not os.path.isfile(tls_cacertfile):
raise IOError(_("tls_cacertfile %s not found "
"or is not a file") %
tls_cacertfile)
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile)
elif tls_cacertdir:
#NOTE(topol)
#python ldap TLS does not verify CACERTFILE or CACERTDIR
#so we add some extra simple sanity check verification
#Also, setting these values globally (i.e. on the ldap object)
#works but these values are ignored when setting them on the
#connection
if not os.path.isdir(tls_cacertdir):
raise IOError(_("tls_cacertdir %s not found "
"or is not a directory") %
tls_cacertdir)
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, tls_cacertdir)
if tls_req_cert in LDAP_TLS_CERTS.values():
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_cert)
else:
LOG.debug(_("LDAP TLS: invalid TLS_REQUIRE_CERT Option=%s"),
tls_req_cert)
self.conn = ldap.initialize(url)
self.conn.protocol_version = ldap.VERSION3
if alias_dereferencing is not None:
self.conn.set_option(ldap.OPT_DEREF, alias_dereferencing)
self.page_size = page_size
if use_tls:
self.conn.start_tls_s()
if chase_referrals is not None:
self.conn.set_option(ldap.OPT_REFERRALS, int(chase_referrals))
**** CubicPower OpenStack Study ****
def simple_bind_s(self, user, password):
LOG.debug(_("LDAP bind: dn=%s"), user)
return self.conn.simple_bind_s(user, password)
**** CubicPower OpenStack Study ****
def unbind_s(self):
LOG.debug("LDAP unbind")
return self.conn.unbind_s()
**** CubicPower OpenStack Study ****
def add_s(self, dn, attrs):
ldap_attrs = [(kind, [py2ldap(x) for x in safe_iter(values)])
for kind, values in attrs]
sane_attrs = [(kind, values
if kind != 'userPassword'
else ['****'])
for kind, values in ldap_attrs]
LOG.debug(_('LDAP add: dn=%(dn)s, attrs=%(attrs)s'), {
'dn': dn, 'attrs': sane_attrs})
return self.conn.add_s(dn, ldap_attrs)
**** CubicPower OpenStack Study ****
def search_s(self, dn, scope, query, attrlist=None):
# NOTE(morganfainberg): Remove "None" singletons from this list, which
# allows us to set mapped attributes to "None" as defaults in config.
# Without this filtering, the ldap query would raise a TypeError since
# attrlist is expected to be an iterable of strings.
if attrlist is not None:
attrlist = [attr for attr in attrlist if attr is not None]
LOG.debug(_(
'LDAP search: dn=%(dn)s, scope=%(scope)s, query=%(query)s, '
'attrs=%(attrlist)s'), {
'dn': dn,
'scope': scope,
'query': query,
'attrlist': attrlist})
if self.page_size:
res = self.paged_search_s(dn, scope, query, attrlist)
else:
res = self.conn.search_s(dn, scope, query, attrlist)
o = []
at_least_one_referral = False
for dn, attrs in res:
if dn is None:
# this is a Referral object, rather than an Entry object
at_least_one_referral = True
continue
o.append((dn, dict((kind, [ldap2py(x) for x in values])
for kind, values in six.iteritems(attrs))))
if at_least_one_referral:
LOG.debug(_('Referrals were returned and ignored. Enable referral '
'chasing in keystone.conf via [ldap] chase_referrals'))
return o
**** CubicPower OpenStack Study ****
def paged_search_s(self, dn, scope, query, attrlist=None):
res = []
lc = ldap.controls.SimplePagedResultsControl(
controlType=ldap.LDAP_CONTROL_PAGE_OID,
criticality=True,
controlValue=(self.page_size, ''))
msgid = self.conn.search_ext(dn,
scope,
query,
attrlist,
serverctrls=[lc])
# Endless loop request pages on ldap server until it has no data
while True:
# Request to the ldap server a page with 'page_size' entries
rtype, rdata, rmsgid, serverctrls = self.conn.result3(msgid)
# Receive the data
res.extend(rdata)
pctrls = [c for c in serverctrls
if c.controlType == ldap.LDAP_CONTROL_PAGE_OID]
if pctrls:
# LDAP server supports pagination
est, cookie = pctrls[0].controlValue
if cookie:
# There is more data still on the server
# so we request another page
lc.controlValue = (self.page_size, cookie)
msgid = self.conn.search_ext(dn,
scope,
query,
attrlist,
serverctrls=[lc])
else:
# Exit condition no more data on server
break
else:
LOG.warning(_('LDAP Server does not support paging. '
'Disable paging in keystone.conf to '
'avoid this message.'))
self._disable_paging()
break
return res
**** CubicPower OpenStack Study ****
def modify_s(self, dn, modlist):
ldap_modlist = [
(op, kind, (None if values is None
else [py2ldap(x) for x in safe_iter(values)]))
for op, kind, values in modlist]
sane_modlist = [(op, kind, (values if kind != 'userPassword'
else ['****']))
for op, kind, values in ldap_modlist]
LOG.debug(_('LDAP modify: dn=%(dn)s, modlist=%(modlist)s'), {
'dn': dn, 'modlist': sane_modlist})
return self.conn.modify_s(dn, ldap_modlist)
**** CubicPower OpenStack Study ****
def delete_s(self, dn):
LOG.debug(_("LDAP delete: dn=%s"), dn)
return self.conn.delete_s(dn)
**** CubicPower OpenStack Study ****
def delete_ext_s(self, dn, serverctrls):
LOG.debug(
_('LDAP delete_ext: dn=%(dn)s, serverctrls=%(serverctrls)s'), {
'dn': dn, 'serverctrls': serverctrls})
return self.conn.delete_ext_s(dn, serverctrls)
**** CubicPower OpenStack Study ****
def _disable_paging(self):
# Disable the pagination from now on
self.page_size = 0
**** CubicPower OpenStack Study ****
class EnabledEmuMixIn(BaseLdap):
"""Emulates boolean 'enabled' attribute if turned on.
Creates groupOfNames holding all enabled objects of this class, all missing
objects are considered disabled.
Options:
* $name_enabled_emulation - boolean, on/off
* $name_enabled_emulation_dn - DN of that groupOfNames,
**** CubicPower OpenStack Study ****
def __init__(self, conf):
super(EnabledEmuMixIn, self).__init__(conf)
enabled_emulation = '%s_enabled_emulation' % self.options_name
self.enabled_emulation = getattr(conf.ldap, enabled_emulation)
enabled_emulation_dn = '%s_enabled_emulation_dn' % self.options_name
self.enabled_emulation_dn = getattr(conf.ldap, enabled_emulation_dn)
if not self.enabled_emulation_dn:
self.enabled_emulation_dn = ('cn=enabled_%ss,%s' %
(self.options_name, self.tree_dn))
**** CubicPower OpenStack Study ****
def _get_enabled(self, object_id):
conn = self.get_connection()
dn = self._id_to_dn(object_id)
query = '(member=%s)' % dn
try:
enabled_value = conn.search_s(self.enabled_emulation_dn,
ldap.SCOPE_BASE,
query, ['cn'])
except ldap.NO_SUCH_OBJECT:
return False
else:
return bool(enabled_value)
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
def _add_enabled(self, object_id):
if not self._get_enabled(object_id):
conn = self.get_connection()
modlist = [(ldap.MOD_ADD,
'member',
[self._id_to_dn(object_id)])]
try:
conn.modify_s(self.enabled_emulation_dn, modlist)
except ldap.NO_SUCH_OBJECT:
attr_list = [('objectClass', ['groupOfNames']),
('member',
[self._id_to_dn(object_id)])]
if self.use_dumb_member:
attr_list[1][1].append(self.dumb_member)
conn.add_s(self.enabled_emulation_dn, attr_list)
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
def _remove_enabled(self, object_id):
conn = self.get_connection()
modlist = [(ldap.MOD_DELETE,
'member',
[self._id_to_dn(object_id)])]
try:
conn.modify_s(self.enabled_emulation_dn, modlist)
except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE):
pass
finally:
conn.unbind_s()
**** CubicPower OpenStack Study ****
def create(self, values):
if self.enabled_emulation:
enabled_value = values.pop('enabled', True)
ref = super(EnabledEmuMixIn, self).create(values)
if 'enabled' not in self.attribute_ignore:
if enabled_value:
self._add_enabled(ref['id'])
ref['enabled'] = enabled_value
return ref
else:
return super(EnabledEmuMixIn, self).create(values)
**** CubicPower OpenStack Study ****
def get(self, object_id, ldap_filter=None):
ref = super(EnabledEmuMixIn, self).get(object_id, ldap_filter)
if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
ref['enabled'] = self._get_enabled(object_id)
return ref
**** CubicPower OpenStack Study ****
def get_all(self, ldap_filter=None):
if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
# had to copy BaseLdap.get_all here to ldap_filter by DN
tenant_list = [self._ldap_res_to_model(x)
for x in self._ldap_get_all(ldap_filter)
if x[0] != self.enabled_emulation_dn]
for tenant_ref in tenant_list:
tenant_ref['enabled'] = self._get_enabled(tenant_ref['id'])
return tenant_list
else:
return super(EnabledEmuMixIn, self).get_all(ldap_filter)
**** CubicPower OpenStack Study ****
def update(self, object_id, values, old_obj=None):
if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
data = values.copy()
enabled_value = data.pop('enabled', None)
ref = super(EnabledEmuMixIn, self).update(object_id, data, old_obj)
if enabled_value is not None:
if enabled_value:
self._add_enabled(object_id)
else:
self._remove_enabled(object_id)
return ref
else:
return super(EnabledEmuMixIn, self).update(
object_id, values, old_obj)
**** CubicPower OpenStack Study ****
def delete(self, object_id):
if self.enabled_emulation:
self._remove_enabled(object_id)
super(EnabledEmuMixIn, self).delete(object_id)