From 2f76856dffeb5224fb1612bb03a74a373a1a528b Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Tue, 23 Aug 2011 16:28:17 +0400 Subject: [PATCH] Made it possible to integrate with external LDAP. All object classes are made auxiliary, add ability to specify what strutural classes to use and what attribute type should be treated as object identifier. Change-Id: I4b2cc8cc6f2a1d93e82005543ce99be4bae23b5b --- keystone/backends/ldap/api/base.py | 30 ++++++++++++++++++++---- keystone/backends/ldap/api/role.py | 30 +++++++++++++++--------- keystone/backends/ldap/api/tenant.py | 13 ++++++++--- keystone/backends/ldap/api/user.py | 4 +++- keystone/backends/ldap/keystone.ldif | 32 +++++++++++++++----------- keystone/backends/ldap/keystone.schema | 28 ++++++++++++++-------- 6 files changed, 95 insertions(+), 42 deletions(-) diff --git a/keystone/backends/ldap/api/base.py b/keystone/backends/ldap/api/base.py index 80d21479b7..bec7dc2f97 100644 --- a/keystone/backends/ldap/api/base.py +++ b/keystone/backends/ldap/api/base.py @@ -1,3 +1,4 @@ +import ast import ldap from itertools import izip, count @@ -15,6 +16,9 @@ def add_redirects(loc, cls, methods): class BaseLdapAPI(object): DEFAULT_TREE_DN = None + DEFAULT_STRUCTURAL_CLASSES = None + DEFAULT_ID_ATTR = 'cn' + DUMB_MEMBER_DN = 'cn=dumb,dc=nonexistent' options_name = None object_class = 'top' model = None @@ -23,13 +27,28 @@ class BaseLdapAPI(object): def __init__(self, api, options): self.api = api - self.tree_dn = options.get(self.options_name, self.DEFAULT_TREE_DN) + if self.options_name is not None: + self.tree_dn = options.get('%s_tree_dn' % (self.options_name,), + self.DEFAULT_TREE_DN) + try: + lst = options['%s_structural_classes' % (self.options_name,)] + except KeyError: + self.structural_classes = self.DEFAULT_STRUCTURAL_CLASSES + else: + self.structural_classes = ast.literal_eval(lst) + self.id_attr = options.get('%s_id_attr' % (self.options_name,), + self.DEFAULT_ID_ATTR) + self.use_dumb_member = options.get('use_dumb_member', True) def _id_to_dn(self, id): - return 'cn=%s,%s' % (ldap.dn.escape_dn_chars(str(id)), self.tree_dn) + return '%s=%s,%s' % (self.id_attr, ldap.dn.escape_dn_chars(str(id)), + self.tree_dn) + + def _dn_to_id(self, dn): + return ldap.dn.str2dn(dn)[0][0][1] def _ldap_res_to_model(self, res): - obj = self.model(id=ldap.dn.str2dn(res[0])[0][0][1]) + obj = self.model(id=self._dn_to_id(res[0])) for k in obj: if k in self.attribute_ignore: continue @@ -43,13 +62,16 @@ class BaseLdapAPI(object): def create(self, values): conn = self.api.get_connection() - attrs = [('objectClass', [self.object_class])] + object_classes = self.structural_classes + [self.object_class] + attrs = [('objectClass', object_classes)] for k, v in values.iteritems(): if k == 'id' or k in self.attribute_ignore: continue if v is not None: attr_type = self.attribute_mapping.get(k, k) attrs.append((attr_type, [v])) + if 'groupOfNames' in object_classes and self.use_dumb_member: + attrs.append(('member', [self.DUMB_MEMBER_DN])) conn.add_s(self._id_to_dn(values['id']), attrs) return self.model(values) diff --git a/keystone/backends/ldap/api/role.py b/keystone/backends/ldap/api/role.py index e79d70d000..a1a0a02318 100644 --- a/keystone/backends/ldap/api/role.py +++ b/keystone/backends/ldap/api/role.py @@ -9,7 +9,8 @@ from .base import BaseLdapAPI class RoleAPI(BaseLdapAPI, BaseTenantAPI): DEFAULT_TREE_DN = 'ou=Groups,dc=example,dc=com' - options_name = 'role_tree_dn' + DEFAULT_STRUCTURAL_CLASSES = ['groupOfNames'] + options_name = 'role' object_class = 'keystoneRole' model = models.Role attribute_mapping = {'desc': 'description', 'service_id': 'serviceId'} @@ -63,10 +64,12 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): else: tenant_dn = None attrs = [ - ('objectClass', 'keystoneTenantRole'), - ('member', user_dn), + ('objectClass', ['keystoneTenantRole', 'groupOfNames']), + ('member', [user_dn]), ('keystoneRole', self._id_to_dn(role_id)), ] + if self.use_dumb_member: + attrs[1][1].append(self.DUMB_MEMBER_DN) conn.add_s(role_dn, attrs) return models.UserRoleAssociation( id=self._create_ref(role_id, tenant_id, user_id), @@ -98,8 +101,10 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): except KeyError: continue for user_dn in user_dns: - user_id = ldap.dn.str2dn(user_dn)[0][0][1] - role_id = ldap.dn.str2dn(role_dn)[0][0][1] + if self.use_dumb_member and user_dn == self.DUMB_MEMBER_DN: + continue + user_id = self.api.user._dn_to_id(user_dn) + role_id = self._dn_to_id(role_dn) res.append(models.UserRoleAssociation( id=self._create_ref(role_id, tenant_id, user_id), user_id=user_id, @@ -127,7 +132,7 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): return [] res = [] for role_dn, _ in roles: - role_id = ldap.dn.str2dn(role_dn)[0][0][1] + role_id = self._dn_to_id(role_dn) res.append(models.UserRoleAssociation( id=self._create_ref(role_id, tenant_id, user_id), user_id=user_id, @@ -142,7 +147,7 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): return [] res = [] for role_dn, _ in roles: - role_id = ldap.dn.str2dn(role_dn)[0][0][1] + role_id = self._dn_to_id(role_dn) tenant_id = ldap.dn.str2dn(role_dn)[1][0][1] res.append(models.UserRoleAssociation( id=self._create_ref(role_id, tenant_id, user_id), @@ -156,8 +161,9 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): user_dn = self.api.user._id_to_dn(user_id) role_dn = self._subrole_id_to_dn(role_id, tenant_id) query = '(&(objectClass=keystoneTenantRole)(member=%s))' % (user_dn,) + conn = self.api.get_connection() try: - res = search_s(role_dn, ldap.SCOPE_BASE, query) + res = conn.search_s(role_dn, ldap.SCOPE_BASE, query) except ldap.NO_SUCH_OBJECT: return None if len(res) == 0: @@ -201,12 +207,14 @@ class RoleAPI(BaseLdapAPI, BaseTenantAPI): except KeyError: continue for user_dn in user_dns: - user_id = ldap.dn.str2dn(user_dn)[0][0][1] + if self.use_dumb_member and user_dn == self.DUMB_MEMBER_DN: + continue + user_id = self.api.user._dn_to_id(user_dn) tenant_id = None if tenant_dns != None: for tenant_dn in tenant_dns: - tenant_id = ldap.dn.str2dn(tenant_dn)[0][0][1] - role_id = ldap.dn.str2dn(role_dn)[0][0][1] + tenant_id = self.api.tenant._dn_to_id(tenant_dn) + role_id = self._dn_to_id(role_dn) res.append(models.UserRoleAssociation( id=self._create_ref(role_id, tenant_id, user_id), user_id=user_id, diff --git a/keystone/backends/ldap/api/tenant.py b/keystone/backends/ldap/api/tenant.py index 210e6a4836..5c8d64eece 100644 --- a/keystone/backends/ldap/api/tenant.py +++ b/keystone/backends/ldap/api/tenant.py @@ -9,7 +9,8 @@ from .base import BaseLdapAPI, add_redirects class TenantAPI(BaseLdapAPI, BaseTenantAPI): DEFAULT_TREE_DN = 'ou=Groups,dc=example,dc=com' - options_name = 'tenant_tree_dn' + DEFAULT_STRUCTURAL_CLASSES = ['groupOfNames'] + options_name = 'tenant' object_class = 'keystoneTenant' model = models.Tenant attribute_mapping = {'desc': 'description', 'enabled': 'keystoneEnabled'} @@ -28,7 +29,11 @@ class TenantAPI(BaseLdapAPI, BaseTenantAPI): def is_empty(self, id): tenant = self._ldap_get(id) - empty = len(tenant[1].get('member', [])) == 0 + members = tenant[1].get('member', []) + if self.use_dumb_member: + empty = members == [self.DUMB_MEMBER_DN] + else: + empty = len(members) == 0 return empty and len(self.api.role.get_role_assignments(id)) == 0 def get_role_assignments(self, tenant_id): @@ -48,7 +53,9 @@ class TenantAPI(BaseLdapAPI, BaseTenantAPI): tenant = self._ldap_get(tenant_id) res = [] for user_dn in tenant[1].get('member', []): - res.append(self.api.user.get(ldap.dn.str2dn(user_dn)[0][0][1])) + if self.use_dumb_member and user_dn == self.DUMB_MEMBER_DN: + continue + res.append(self.api.user.get(self.api.user._dn_to_id(user_dn))) return res add_redirects(locals(), SQLTenantAPI, ['get_all_endpoints']) diff --git a/keystone/backends/ldap/api/user.py b/keystone/backends/ldap/api/user.py index 2eab11944d..32199c671d 100644 --- a/keystone/backends/ldap/api/user.py +++ b/keystone/backends/ldap/api/user.py @@ -11,7 +11,9 @@ from .base import BaseLdapAPI, add_redirects class UserAPI(BaseLdapAPI, BaseUserAPI): DEFAULT_TREE_DN = 'ou=Users,dc=example,dc=com' - options_name = 'user_tree_dn' + DEFAULT_STRUCTURAL_CLASSES = ['keystoneUidObject'] + DEFAULT_ID_ATTR = 'uid' + options_name = 'user' object_class = 'keystoneUser' model = models.User attribute_mapping = { diff --git a/keystone/backends/ldap/keystone.ldif b/keystone/backends/ldap/keystone.ldif index cfa506259d..b4a0539d9f 100644 --- a/keystone/backends/ldap/keystone.ldif +++ b/keystone/backends/ldap/keystone.ldif @@ -30,33 +30,39 @@ olcAttributeTypes: ( ) olcObjectClasses: ( 1.3.6.1.3.1.666.667.4.1 - NAME 'keystoneUser' + NAME 'keystoneUidObject' SUP top STRUCTURAL - MUST ( cn $ keystoneEnabled ) - MAY ( mail $ userPassword ) + MUST ( uid ) ) olcObjectClasses: ( 1.3.6.1.3.1.666.667.4.2 - NAME 'keystoneRole' + NAME 'keystoneUser' SUP top - STRUCTURAL - MUST ( cn ) - MAY ( member $ description $ serviceId ) + AUXILIARY + MUST ( keystoneEnabled ) + MAY ( mail $ userPassword ) ) olcObjectClasses: ( 1.3.6.1.3.1.666.667.4.3 - NAME 'keystoneTenant' + NAME 'keystoneRole' SUP top - STRUCTURAL - MUST ( cn $ keystoneEnabled ) - MAY ( member $ description ) + AUXILIARY + MAY ( member $ description $ serviceId ) ) olcObjectClasses: ( 1.3.6.1.3.1.666.667.4.4 + NAME 'keystoneTenant' + SUP top + AUXILIARY + MUST ( keystoneEnabled ) + MAY ( member $ description ) + ) +olcObjectClasses: ( + 1.3.6.1.3.1.666.667.4.5 NAME 'keystoneTenantRole' SUP top - STRUCTURAL - MUST ( cn $ keystoneRole) + AUXILIARY + MUST ( keystoneRole ) MAY ( member ) ) diff --git a/keystone/backends/ldap/keystone.schema b/keystone/backends/ldap/keystone.schema index 518e0bc044..7aa7370a7f 100644 --- a/keystone/backends/ldap/keystone.schema +++ b/keystone/backends/ldap/keystone.schema @@ -35,36 +35,44 @@ attributetype ( objectClass ( keystoneOCs:1 - NAME 'keystoneUser' + NAME 'keystoneUidObject' SUP top STRUCTURAL - MUST ( cn $ keystoneEnabled ) - MAY ( mail $ userPassword ) + MUST ( uid ) ) objectClass ( keystoneOCs:2 + NAME 'keystoneUser' + SUP top + AUXILIARY + MUST ( keystoneEnabled ) + MAY ( mail $ userPassword ) + ) + +objectClass ( + keystoneOCs:3 NAME 'keystoneRole' SUP top - STRUCTURAL + AUXILIARY MUST ( cn ) MAY ( member $ description $ serviceId ) ) objectClass ( - keystoneOCs:3 + keystoneOCs:4 NAME 'keystoneTenant' SUP top - STRUCTURAL - MUST ( cn $ keystoneEnabled ) + AUXILIARY + MUST ( keystoneEnabled ) MAY ( member $ description ) ) objectClass ( - keystoneOCs:4 + keystoneOCs:5 NAME 'keystoneTenantRole' SUP top - STRUCTURAL - MUST ( cn $ keystoneRole) + AUXILIARY + MUST ( keystoneRole ) MAY ( member ) )