summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJose Castro Leon <jose.castro.leon@cern.ch>2012-10-29 15:07:58 +0100
committerDolph Mathews <dolph.mathews@gmail.com>2012-11-13 10:37:17 -0600
commit001f708e7d9ffc69c80f823e7ab5f79325cc8a40 (patch)
tree9181d119b43770f27c8e7fff0293d18909a7f503
parent8dcafd81dfa0ccd958b614c12eee091a325ec5c4 (diff)
downloadkeystone-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.rst89
-rw-r--r--etc/keystone.conf.sample8
-rw-r--r--keystone/config.py8
-rw-r--r--keystone/identity/backends/ldap/core.py43
-rw-r--r--tests/test_backend.py32
-rw-r--r--tests/test_backend_ldap.py32
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)