summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-02-21 02:41:33 +0000
committerGerrit Code Review <review@openstack.org>2013-02-21 02:41:33 +0000
commitc6b978cbb80adbe33f70fa021d60a73802601f20 (patch)
tree188e10406cb10f7f550ceac4ab36b4b3d57f2f42
parentd036db145d51f8b134ffa36165065a8986e4f8a1 (diff)
parent408a1d57d729461056507283c58d6c48403554b8 (diff)
Merge "enabled attribute emulation support"
-rw-r--r--etc/keystone.conf.sample4
-rw-r--r--keystone/common/ldap/core.py114
-rw-r--r--keystone/config.py4
-rw-r--r--keystone/identity/backends/ldap/core.py5
-rw-r--r--tests/test_backend_ldap.py66
5 files changed, 191 insertions, 2 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample
index 6e810fc6..4a481576 100644
--- a/etc/keystone.conf.sample
+++ b/etc/keystone.conf.sample
@@ -156,6 +156,8 @@
# user_allow_create = True
# user_allow_update = True
# user_allow_delete = True
+# user_enabled_emulation = False
+# user_enabled_emulation_dn =
# tenant_tree_dn = ou=Groups,dc=example,dc=com
# tenant_filter =
@@ -169,6 +171,8 @@
# tenant_allow_create = True
# tenant_allow_update = True
# tenant_allow_delete = True
+# tenant_enabled_emulation = False
+# tenant_enabled_emulation_dn =
# role_tree_dn = ou=Roles,dc=example,dc=com
# role_filter =
diff --git a/keystone/common/ldap/core.py b/keystone/common/ldap/core.py
index 2c1d0817..f9099df7 100644
--- a/keystone/common/ldap/core.py
+++ b/keystone/common/ldap/core.py
@@ -410,3 +410,117 @@ class LdapWrapper(object):
def delete_ext_s(self, dn, serverctrls):
LOG.debug(_("LDAP delete_ext: dn=%s, serverctrls=%s"), dn, serverctrls)
return self.conn.delete_ext_s(dn, serverctrls)
+
+
+class EnabledEmuMixIn(BaseLdap):
+ """Emulates boolean 'enabled' attribute if turned on.
+
+ Creates groupOfNames holding all enabled objects of this class, all missing
+ objects are considered disabled.
+
+ Options:
+
+ * $name_enabled_emulation - boolean, on/off
+ * $name_enabled_emulation_dn - DN of that groupOfNames, default is
+ cn=enabled_$name,$tree_dn
+
+ Where $name is self.options_name ('user' or 'tenant'), $tree_dn is
+ self.tree_dn.
+ """
+
+ def __init__(self, conf):
+ super(EnabledEmuMixIn, self).__init__(conf)
+ enabled_emulation = '%s_enabled_emulation' % self.options_name
+ self.enabled_emulation = getattr(conf.ldap, enabled_emulation)
+
+ enabled_emulation_dn = '%s_enabled_emulation_dn' % self.options_name
+ self.enabled_emulation_dn = getattr(conf.ldap, enabled_emulation_dn)
+ if not self.enabled_emulation_dn:
+ self.enabled_emulation_dn = ('cn=enabled_%ss,%s' %
+ (self.options_name, self.tree_dn))
+
+ def _get_enabled(self, object_id):
+ conn = self.get_connection()
+ dn = self._id_to_dn(object_id)
+ query = '(member=%s)' % dn
+ try:
+ enabled_value = conn.search_s(self.enabled_emulation_dn,
+ ldap.SCOPE_BASE,
+ query)
+ except ldap.NO_SUCH_OBJECT:
+ return False
+ else:
+ return bool(enabled_value)
+
+ def _add_enabled(self, object_id):
+ conn = self.get_connection()
+ modlist = [(ldap.MOD_ADD,
+ 'member',
+ [self._id_to_dn(object_id)])]
+ try:
+ conn.modify_s(self.enabled_emulation_dn, modlist)
+ except ldap.NO_SUCH_OBJECT:
+ attr_list = [('objectClass', ['groupOfNames']),
+ ('member',
+ [self._id_to_dn(object_id)])]
+ if self.use_dumb_member:
+ attr_list[1][1].append(self.dumb_member)
+ conn.add_s(self.enabled_emulation_dn, attr_list)
+
+ def _remove_enabled(self, object_id):
+ conn = self.get_connection()
+ modlist = [(ldap.MOD_DELETE,
+ 'member',
+ [self._id_to_dn(object_id)])]
+ try:
+ conn.modify_s(self.enabled_emulation_dn, modlist)
+ except (ldap.NO_SUCH_OBJECT, ldap.NO_SUCH_ATTRIBUTE):
+ pass
+
+ def create(self, values):
+ if self.enabled_emulation:
+ enabled_value = values.pop('enabled', True)
+ ref = super(EnabledEmuMixIn, self).create(values)
+ if 'enabled' not in self.attribute_ignore:
+ if enabled_value:
+ self._add_enabled(ref['id'])
+ ref['enabled'] = enabled_value
+ return ref
+ else:
+ return super(EnabledEmuMixIn, self).create(values)
+
+ def get(self, object_id, filter=None):
+ ref = super(EnabledEmuMixIn, self).get(object_id, filter)
+ if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
+ ref['enabled'] = self._get_enabled(object_id)
+ return ref
+
+ def get_all(self, filter=None):
+ if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
+ # had to copy BaseLdap.get_all here to filter by DN
+ tenant_list = [self._ldap_res_to_model(x)
+ for x in self._ldap_get_all(filter)
+ if x[0] != self.enabled_emulation_dn]
+ for tenant_ref in tenant_list:
+ tenant_ref['enabled'] = self._get_enabled(tenant_ref['id'])
+ return tenant_list
+ else:
+ return super(EnabledEmuMixIn, self).get_all(filter)
+
+ def update(self, object_id, values, old_obj=None):
+ if 'enabled' not in self.attribute_ignore and self.enabled_emulation:
+ data = values.copy()
+ enabled_value = data.pop('enabled', None)
+ super(EnabledEmuMixIn, self).update(object_id, data, old_obj)
+ if enabled_value is not None:
+ if enabled_value:
+ self._add_enabled(object_id)
+ else:
+ self._remove_enabled(object_id)
+ else:
+ super(EnabledEmuMixIn, self).update(object_id, values, old_obj)
+
+ def delete(self, object_id):
+ if self.enabled_emulation:
+ self._remove_enabled(object_id)
+ super(EnabledEmuMixIn, self).delete(object_id)
diff --git a/keystone/config.py b/keystone/config.py
index 1c18350d..2049091d 100644
--- a/keystone/config.py
+++ b/keystone/config.py
@@ -267,6 +267,8 @@ register_list('user_attribute_ignore', group='ldap',
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)
+register_bool('user_enabled_emulation', group='ldap', default=False)
+register_str('user_enabled_emulation_dn', group='ldap', default=None)
register_str('tenant_tree_dn', group='ldap', default=None)
register_str('tenant_filter', group='ldap', default=None)
@@ -281,6 +283,8 @@ 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)
+register_bool('tenant_enabled_emulation', group='ldap', default=False)
+register_str('tenant_enabled_emulation_dn', group='ldap', default=None)
register_str('role_tree_dn', group='ldap', default=None)
register_str('role_filter', group='ldap', default=None)
diff --git a/keystone/identity/backends/ldap/core.py b/keystone/identity/backends/ldap/core.py
index ec9df209..24c8a2b3 100644
--- a/keystone/identity/backends/ldap/core.py
+++ b/keystone/identity/backends/ldap/core.py
@@ -326,7 +326,7 @@ class ApiShimMixin(object):
# TODO(termie): turn this into a data object and move logic to driver
-class UserApi(common_ldap.BaseLdap, ApiShimMixin):
+class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap, ApiShimMixin):
DEFAULT_OU = 'ou=Users'
DEFAULT_STRUCTURAL_CLASSES = ['person']
DEFAULT_ID_ATTR = 'cn'
@@ -486,7 +486,8 @@ class UserApi(common_ldap.BaseLdap, ApiShimMixin):
# TODO(termie): turn this into a data object and move logic to driver
-class ProjectApi(common_ldap.BaseLdap, ApiShimMixin):
+class ProjectApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap,
+ ApiShimMixin):
DEFAULT_OU = 'ou=Groups'
DEFAULT_STRUCTURAL_CLASSES = []
DEFAULT_OBJECTCLASS = 'groupOfNames'
diff --git a/tests/test_backend_ldap.py b/tests/test_backend_ldap.py
index 63499bd1..d4e96884 100644
--- a/tests/test_backend_ldap.py
+++ b/tests/test_backend_ldap.py
@@ -480,3 +480,69 @@ class LDAPIdentity(test.TestCase, test_backend.IdentityTests):
def test_move_project_between_domains_with_clashing_names_fails(self):
raise nose.exc.SkipTest('Blocked by bug 1101276')
+
+
+class LDAPIdentityEnabledEmulation(LDAPIdentity):
+ def setUp(self):
+ super(LDAPIdentityEnabledEmulation, self).setUp()
+ self.config([test.etcdir('keystone.conf.sample'),
+ test.testsdir('test_overrides.conf'),
+ test.testsdir('backend_ldap.conf')])
+ CONF.ldap.user_enabled_emulation = True
+ CONF.ldap.tenant_enabled_emulation = True
+ clear_database()
+ self.identity_api = identity_ldap.Identity()
+ self.load_fixtures(default_fixtures)
+ for obj in [self.tenant_bar, self.tenant_baz, self.user_foo,
+ self.user_two, self.user_badguy]:
+ obj.setdefault('enabled', True)
+
+ def test_authenticate_no_metadata(self):
+ user = {
+ 'id': 'no_meta',
+ 'name': 'NO_META',
+ 'domain_id': test_backend.DEFAULT_DOMAIN_ID,
+ 'password': 'no_meta2',
+ 'enabled': True,
+ }
+ self.identity_api.create_user(user['id'], user)
+ self.identity_api.add_user_to_project(self.tenant_baz['id'],
+ user['id'])
+ user_ref, tenant_ref, metadata_ref = self.identity_api.authenticate(
+ user_id=user['id'],
+ tenant_id=self.tenant_baz['id'],
+ password=user['password'])
+ # NOTE(termie): the password field is left in user_foo to make
+ # it easier to authenticate in tests, but should
+ # not be returned by the api
+ user.pop('password')
+ self.assertEquals(metadata_ref, {"roles":
+ [CONF.member_role_id]})
+ self.assertDictEqual(user_ref, user)
+ self.assertDictEqual(tenant_ref, self.tenant_baz)
+
+ def test_user_crud(self):
+ user = {'domain_id': uuid.uuid4().hex, 'id': uuid.uuid4().hex,
+ 'name': uuid.uuid4().hex, 'password': 'passw0rd'}
+ self.identity_api.create_user(user['id'], user)
+ user['enabled'] = True
+ user_ref = self.identity_api.get_user(user['id'])
+ del user['password']
+ user_ref_dict = dict((x, user_ref[x]) for x in user_ref)
+ self.assertDictEqual(user_ref_dict, user)
+
+ user['password'] = uuid.uuid4().hex
+ self.identity_api.update_user(user['id'], user)
+ user_ref = self.identity_api.get_user(user['id'])
+ del user['password']
+ user_ref_dict = dict((x, user_ref[x]) for x in user_ref)
+ self.assertDictEqual(user_ref_dict, user)
+
+ self.identity_api.delete_user(user['id'])
+ self.assertRaises(exception.UserNotFound,
+ self.identity_api.get_user,
+ user['id'])
+
+ def test_user_enable_attribute_mask(self):
+ raise nose.exc.SkipTest(
+ "Enabled emulation conflicts with enabled mask")