Provide config file fields for enable users in LDAP backend (bug1067516)

DocImpact

Change-Id: I1ee9a1e2505cdd8c9ee8acba5c0e89a4f25c7262
This commit is contained in:
Jose Castro Leon 2012-10-29 15:07:58 +01:00 committed by Dolph Mathews
parent 8dcafd81df
commit 001f708e7d
6 changed files with 196 additions and 16 deletions

View File

@ -921,15 +921,26 @@ for openstack would look like this::
dn: ou=Roles,cn=openstack,cn=org
objectClass: top
objectClass: organizationalUnit
ou: users
ou: roles
The corresponding entries in the Keystone configuration file are::
[ldap]
url = ldap://localhost
suffix = dc=openstack,dc=org
user = dc=Manager,dc=openstack,dc=org
password = badpassword
suffix = dc=openstack,dc=org
use_dumb_member = False
allow_subtree_delete = False
user_tree_dn = ou=Users,dc=openstack,dc=com
user_objectclass = inetOrgPerson
tenant_tree_dn = ou=Groups,dc=openstack,dc=com
tenant_objectclass = groupOfNames
role_tree_dn = ou=Roles,dc=example,dc=com
role_objectclass = organizationalRole
The default object classes and attributes are intentionally simplistic. They
reflect the common standard objects according to the LDAP RFCs. However,
@ -943,3 +954,77 @@ corresponding entries in the Keystone configuration file are::
[ldap]
user_id_attribute = uidNumber
user_name_attribute = cn
There is a set of allowed actions per object type that you can modify
depending on your specific deployment. For example, the users are managed by
another tool and you have only read access, in such case the configuration
is::
[ldap]
user_allow_create = False
user_allow_update = False
user_allow_delete = False
tenant_allow_create = True
tenant_allow_update = True
tenant_allow_delete = True
role_allow_create = True
role_allow_update = True
role_allow_delete = True
There are some configuration options for filtering users, tenants and roles,
if the backend is providing too much output, in such case the configuration
will look like::
[ldap]
user_filter = (memberof=CN=openstack-users,OU=workgroups,DC=openstack,DC=com)
tenant_filter =
role_filter =
In case that the directory server does not have an attribute enabled of type
boolean for the user, there is several configuration parameters that can be used
to extract the value from an integer attribute like in Active Directory::
[ldap]
user_enabled_attribute = userAccountControl
user_enabled_mask = 2
user_enabled_default = 512
In this case the attribute is an integer and the enabled attribute is listed
in bit 1, so the if the mask configured *user_enabled_mask* is different from 0,
it gets the value from the field *user_enabled_attribute* and it makes an ADD
operation with the value indicated on *user_enabled_mask* and if the value matches
the mask then the account is disabled.
It also saves the value without mask to the user identity in the attribute
*enabled_nomask*. This is needed in order to set it back in case that we need to
change it to enable/disable a user because it contains more information than the
status like password expiration. Last setting *user_enabled_mask* is needed in order
to create a default value on the integer attribute (512 = NORMAL ACCOUNT on AD)
In case of Active Directory the classes and attributes could not match the
specified classes in the LDAP module so you can configure them like::
[ldap]
user_objectclass = person
user_id_attribute = cn
user_name_attribute = cn
user_mail_attribute = mail
user_enabled_attribute = userAccountControl
user_enabled_mask = 2
user_enabled_default = 512
user_attribute_ignore = tenant_id,tenants
tenant_objectclass = groupOfNames
tenant_id_attribute = cn
tenant_member_attribute = member
tenant_name_attribute = ou
tenant_desc_attribute = description
tenant_enabled_attribute = extensionName
tenant_attribute_ignore =
role_objectclass = organizationalRole
role_id_attribute = cn
role_name_attribute = ou
role_member_attribute = roleOccupant
role_attribute_ignore =

View File

@ -116,7 +116,10 @@
# user_name_attribute = sn
# user_mail_attribute = email
# user_pass_attribute = userPassword
# user_attribute_ignore = tenant_id,enabled,tenants
# user_enabled_attribute = enabled
# user_enabled_mask = 0
# user_enabled_default = True
# user_attribute_ignore = tenant_id,tenants
# user_allow_create = True
# user_allow_update = True
# user_allow_delete = True
@ -128,7 +131,8 @@
# tenant_member_attribute = member
# tenant_name_attribute = ou
# tenant_desc_attribute = desc
# tenant_attribute_ignore = enabled
# tenant_enabled_attribute = enabled
# tenant_attribute_ignore =
# tenant_allow_create = True
# tenant_allow_update = True
# tenant_allow_delete = True

View File

@ -186,8 +186,11 @@ register_str('user_id_attribute', group='ldap', default='cn')
register_str('user_name_attribute', group='ldap', default='sn')
register_str('user_mail_attribute', group='ldap', default='email')
register_str('user_pass_attribute', group='ldap', default='userPassword')
register_str('user_enabled_attribute', group='ldap', default='enabled')
register_int('user_enabled_mask', group='ldap', default=0)
register_str('user_enabled_default', group='ldap', default='True')
register_list('user_attribute_ignore', group='ldap',
default='tenant_id,enable,tenants')
default='tenant_id,tenants')
register_bool('user_allow_create', group='ldap', default=True)
register_bool('user_allow_update', group='ldap', default=True)
register_bool('user_allow_delete', group='ldap', default=True)
@ -199,7 +202,8 @@ register_str('tenant_id_attribute', group='ldap', default='cn')
register_str('tenant_member_attribute', group='ldap', default='member')
register_str('tenant_name_attribute', group='ldap', default='ou')
register_str('tenant_desc_attribute', group='ldap', default='desc')
register_list('tenant_attribute_ignore', group='ldap', default='enabled')
register_str('tenant_enabled_attribute', group='ldap', default='enabled')
register_list('tenant_attribute_ignore', group='ldap', default='')
register_bool('tenant_allow_create', group='ldap', default=True)
register_bool('tenant_allow_update', group='ldap', default=True)
register_bool('tenant_allow_delete', group='ldap', default=True)

View File

@ -317,17 +317,13 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
DEFAULT_STRUCTURAL_CLASSES = ['person']
DEFAULT_ID_ATTR = 'cn'
DEFAULT_OBJECTCLASS = 'inetOrgPerson'
DEFAULT_ATTRIBUTE_IGNORE = ['tenant_id', 'enabled', 'tenants']
DEFAULT_ATTRIBUTE_IGNORE = ['tenant_id', 'tenants']
options_name = 'user'
attribute_mapping = {'password': 'userPassword',
'email': 'mail',
'name': 'sn'}
'name': 'sn',
'enabled': 'enabled'}
# NOTE(ayoung): The RFC based schemas don't have a way to indicate
# 'enabled' the closest is the nsAccount lock, which is on defined to
# be part of any objectclass.
# in the future, we need to provide a way for the end user to
# indicate the field to use and what it indicates
model = models.User
def __init__(self, conf):
@ -335,10 +331,30 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
self.attribute_mapping['name'] = conf.ldap.user_name_attribute
self.attribute_mapping['email'] = conf.ldap.user_mail_attribute
self.attribute_mapping['password'] = conf.ldap.user_pass_attribute
self.attribute_mapping['enabled'] = conf.ldap.user_enabled_attribute
self.enabled_mask = conf.ldap.user_enabled_mask
self.enabled_default = conf.ldap.user_enabled_default
self.attribute_ignore = (getattr(conf.ldap, 'user_attribute_ignore')
or self.DEFAULT_ATTRIBUTE_IGNORE)
self.api = ApiShim(conf)
def _ldap_res_to_model(self, res):
obj = super(UserApi, self)._ldap_res_to_model(res)
if self.enabled_mask != 0:
obj['enabled_nomask'] = obj['enabled']
obj['enabled'] = ((obj['enabled'] & self.enabled_mask) !=
self.enabled_mask)
return obj
def mask_enabled_attribute(self, values):
value = values['enabled']
values.setdefault('enabled_nomask', self.enabled_default)
if value != ((values['enabled_nomask'] & self.enabled_mask) !=
self.enabled_mask):
values['enabled_nomask'] ^= self.enabled_mask
values['enabled'] = values['enabled_nomask']
del values['enabled_nomask']
def get(self, id, filter=None):
"""Replaces exception.NotFound with exception.UserNotFound."""
try:
@ -358,6 +374,8 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
def create(self, values):
self.affirm_unique(values)
values = utils.hash_ldap_user_password(values)
if self.enabled_mask:
self.mask_enabled_attribute(values)
values = super(UserApi, self).create(values)
tenant_id = values.get('tenant_id')
if tenant_id is not None:
@ -385,6 +403,9 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
self.tenant_api.add_user(new_tenant, id)
values = utils.hash_ldap_user_password(values)
if self.enabled_mask:
values['enabled_nomask'] = old_obj['enabled_nomask']
self.mask_enabled_attribute(values)
super(UserApi, self).update(id, values, old_obj)
def delete(self, id):
@ -456,9 +477,12 @@ class TenantApi(common_ldap.BaseLdap, ApiShimMixin):
DEFAULT_OBJECTCLASS = 'groupOfNames'
DEFAULT_ID_ATTR = 'cn'
DEFAULT_MEMBER_ATTRIBUTE = 'member'
DEFAULT_ATTRIBUTE_IGNORE = ['enabled']
DEFAULT_ATTRIBUTE_IGNORE = []
options_name = 'tenant'
attribute_mapping = {'name': 'ou', 'description': 'desc', 'tenantId': 'cn'}
attribute_mapping = {'name': 'ou',
'description': 'desc',
'tenantId': 'cn',
'enabled': 'enabled'}
model = models.Tenant
def __init__(self, conf):
@ -466,6 +490,7 @@ class TenantApi(common_ldap.BaseLdap, ApiShimMixin):
self.api = ApiShim(conf)
self.attribute_mapping['name'] = conf.ldap.tenant_name_attribute
self.attribute_mapping['description'] = conf.ldap.tenant_desc_attribute
self.attribute_mapping['enabled'] = conf.ldap.tenant_enabled_attribute
self.member_attribute = (getattr(conf.ldap, 'tenant_member_attribute')
or self.DEFAULT_MEMBER_ATTRIBUTE)
self.attribute_ignore = (getattr(conf.ldap, 'tenant_attribute_ignore')

View File

@ -659,6 +659,38 @@ class IdentityTests(object):
self.identity_api.create_user('user_id', new_user)
self.assertDictEqual(original_user, new_user)
def test_update_user_enable(self):
user = {'id': 'fake1', 'name': 'fake1', 'enabled': 'True'}
self.identity_api.create_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], 'True')
user['enabled'] = 'False'
self.identity_api.update_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], user['enabled'])
user['enabled'] = 'True'
self.identity_api.update_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], user['enabled'])
def test_update_tenant_enable(self):
tenant = {'id': 'fake1', 'name': 'fake1', 'enabled': 'True'}
self.identity_api.create_tenant('fake1', tenant)
tenant_ref = self.identity_api.get_tenant('fake1')
self.assertEqual(tenant_ref['enabled'], 'True')
tenant['enabled'] = 'False'
self.identity_api.update_tenant('fake1', tenant)
tenant_ref = self.identity_api.get_tenant('fake1')
self.assertEqual(tenant_ref['enabled'], tenant['enabled'])
tenant['enabled'] = 'True'
self.identity_api.update_tenant('fake1', tenant)
tenant_ref = self.identity_api.get_tenant('fake1')
self.assertEqual(tenant_ref['enabled'], tenant['enabled'])
class TokenTests(object):
def test_token_crud(self):

View File

@ -122,7 +122,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
test.testsdir('backend_ldap.conf')])
self.identity_api = identity_ldap.Identity()
tenant = {'id': 'fake1', 'name': 'fake1'}
tenant = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
self.identity_api.create_tenant('fake1', tenant)
tenant_ref = self.identity_api.get_tenant('fake1')
self.assertEqual(tenant_ref['id'], 'fake1')
@ -262,6 +262,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
test.testsdir('backend_ldap.conf')])
CONF.ldap.user_name_attribute = 'sn'
CONF.ldap.user_mail_attribute = 'email'
CONF.ldap.user_enabled_attribute = 'enabled'
clear_database()
self.identity_api = identity_ldap.Identity()
self.load_fixtures(default_fixtures)
@ -269,6 +270,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
self.assertEqual(user_ref['id'], self.user_attr['id'])
self.assertEqual(user_ref['name'], self.user_attr['name'])
self.assertEqual(user_ref['email'], self.user_attr['email'])
self.assertEqual(user_ref['enabled'], self.user_attr['enabled'])
CONF.ldap.user_name_attribute = 'email'
CONF.ldap.user_mail_attribute = 'sn'
@ -277,6 +279,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
self.assertEqual(user_ref['id'], self.user_attr['id'])
self.assertEqual(user_ref['name'], self.user_attr['email'])
self.assertEqual(user_ref['email'], self.user_attr['name'])
self.assertEqual(user_ref['enabled'], self.user_attr['enabled'])
def test_user_attribute_ignore(self):
self.config([test.etcdir('keystone.conf.sample'),
@ -302,6 +305,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
test.testsdir('backend_ldap.conf')])
CONF.ldap.tenant_name_attribute = 'ou'
CONF.ldap.tenant_desc_attribute = 'desc'
CONF.ldap.tenant_enabled_attribute = 'enabled'
clear_database()
self.identity_api = identity_ldap.Identity()
self.load_fixtures(default_fixtures)
@ -310,6 +314,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
self.assertEqual(tenant_ref['name'], self.tenant_attr['name'])
self.assertEqual(tenant_ref['description'],
self.tenant_attr['description'])
self.assertEqual(tenant_ref['enabled'], self.tenant_attr['enabled'])
CONF.ldap.tenant_name_attribute = 'desc'
CONF.ldap.tenant_desc_attribute = 'ou'
@ -318,6 +323,7 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
self.assertEqual(tenant_ref['id'], self.tenant_attr['id'])
self.assertEqual(tenant_ref['name'], self.tenant_attr['description'])
self.assertEqual(tenant_ref['description'], self.tenant_attr['name'])
self.assertEqual(tenant_ref['enabled'], self.tenant_attr['enabled'])
def test_tenant_attribute_ignore(self):
self.config([test.etcdir('keystone.conf.sample'),
@ -364,3 +370,27 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
role_ref = self.identity_api.get_role(self.role_attr['id'])
self.assertEqual(role_ref['id'], self.role_attr['id'])
self.assertNotIn('name', role_ref)
def test_user_enable_attribute_mask(self):
self.config([test.etcdir('keystone.conf.sample'),
test.testsdir('test_overrides.conf'),
test.testsdir('backend_ldap.conf')])
CONF.ldap.user_enabled_attribute = 'enabled'
CONF.ldap.user_enabled_mask = 2
CONF.ldap.user_enabled_default = 512
clear_database()
self.identity_api = identity_ldap.Identity()
user = {'id': 'fake1', 'name': 'fake1', 'enabled': True}
self.identity_api.create_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], True)
user['enabled'] = False
self.identity_api.update_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], False)
user['enabled'] = True
self.identity_api.update_user('fake1', user)
user_ref = self.identity_api.get_user('fake1')
self.assertEqual(user_ref['enabled'], True)