diff options
author | Jose Castro Leon <jose.castro.leon@cern.ch> | 2012-10-29 15:07:58 +0100 |
---|---|---|
committer | Dolph Mathews <dolph.mathews@gmail.com> | 2012-11-13 10:37:17 -0600 |
commit | 001f708e7d9ffc69c80f823e7ab5f79325cc8a40 (patch) | |
tree | 9181d119b43770f27c8e7fff0293d18909a7f503 | |
parent | 8dcafd81dfa0ccd958b614c12eee091a325ec5c4 (diff) | |
download | keystone-001f708e7d9ffc69c80f823e7ab5f79325cc8a40.tar.gz keystone-001f708e7d9ffc69c80f823e7ab5f79325cc8a40.tar.xz keystone-001f708e7d9ffc69c80f823e7ab5f79325cc8a40.zip |
Provide config file fields for enable users in LDAP backend (bug1067516)
DocImpact
Change-Id: I1ee9a1e2505cdd8c9ee8acba5c0e89a4f25c7262
-rw-r--r-- | doc/source/configuration.rst | 89 | ||||
-rw-r--r-- | etc/keystone.conf.sample | 8 | ||||
-rw-r--r-- | keystone/config.py | 8 | ||||
-rw-r--r-- | keystone/identity/backends/ldap/core.py | 43 | ||||
-rw-r--r-- | tests/test_backend.py | 32 | ||||
-rw-r--r-- | tests/test_backend_ldap.py | 32 |
6 files changed, 196 insertions, 16 deletions
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 8dc8b032..21b60b85 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -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 = diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample index 43b23e25..c6765ab7 100644 --- a/etc/keystone.conf.sample +++ b/etc/keystone.conf.sample @@ -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 diff --git a/keystone/config.py b/keystone/config.py index bfded4ec..f1bfbd87 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -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) diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py index 72adf318..bdff6726 100644 --- a/keystone/identity/backends/ldap/core.py +++ b/keystone/identity/backends/ldap/core.py @@ -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') diff --git a/tests/test_backend.py b/tests/test_backend.py index c8b317d2..82e608e1 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -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): diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py index cbb94e44..2d9d35aa 100644 --- a/tests/test_backend_ldap.py +++ b/tests/test_backend_ldap.py @@ -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) |