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
This commit is contained in:
Yuriy Taraday 2011-08-23 16:28:17 +04:00
parent d349b349c5
commit 2f76856dff
6 changed files with 95 additions and 42 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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'])

View File

@ -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 = {

View File

@ -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 )
)

View File

@ -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 )
)