diff options
author | Zuul <zuul@review.opendev.org> | 2020-04-10 00:18:35 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2020-04-10 00:18:35 +0000 |
commit | f5fd13f26f3e00478140c9d330a0a2689ac05a8c (patch) | |
tree | 6bbfcbb4e6b2cf53379fa7fc9fe32d6c8fb5ff0b /keystone | |
parent | 033e7aff870f2ccd4dec607e9c47efff630ece29 (diff) | |
parent | d8938514fe3f9df54467f557e13770533c614259 (diff) | |
download | keystone-f5fd13f26f3e00478140c9d330a0a2689ac05a8c.tar.gz |
Merge "Expiring Group Membership Driver - Add, List Groups"
Diffstat (limited to 'keystone')
-rw-r--r-- | keystone/identity/backends/sql.py | 54 | ||||
-rw-r--r-- | keystone/identity/core.py | 3 | ||||
-rw-r--r-- | keystone/identity/shadow_backends/sql.py | 29 | ||||
-rw-r--r-- | keystone/tests/unit/test_backend_ldap.py | 2 | ||||
-rw-r--r-- | keystone/tests/unit/test_backend_sql.py | 89 |
5 files changed, 171 insertions, 6 deletions
diff --git a/keystone/identity/backends/sql.py b/keystone/identity/backends/sql.py index eed349630..cbe98305f 100644 --- a/keystone/identity/backends/sql.py +++ b/keystone/identity/backends/sql.py @@ -160,6 +160,13 @@ class Identity(base.IdentityDriverBase): 'password_expires_at'] return query, hints + @staticmethod + def _apply_limits_to_list(collection, hints): + if not hints.limit: + return collection + + return collection[:hints.limit['limit']] + @driver_hints.truncated def list_users(self, hints): with sql.session_for_read() as session: @@ -281,14 +288,28 @@ class Identity(base.IdentityDriverBase): with sql.session_for_read() as session: self.get_group(group_id) self.get_user(user_id) + + # Note(knikolla): Check for normal group membership query = session.query(model.UserGroupMembership) query = query.filter_by(user_id=user_id) query = query.filter_by(group_id=group_id) - if not query.first(): - raise exception.NotFound(_("User '%(user_id)s' not found in" - " group '%(group_id)s'") % - {'user_id': user_id, - 'group_id': group_id}) + if query.first(): + return + + # Note(knikolla): Check for expiring group membership + query = session.query(model.ExpiringUserGroupMembership) + query = query.filter( + model.ExpiringUserGroupMembership.user_id == user_id) + query = query.filter( + model.ExpiringUserGroupMembership.group_id == group_id) + active = [q for q in query.all() if not q.expired] + if active: + return + + raise exception.NotFound(_("User '%(user_id)s' not found in" + " group '%(group_id)s'") % + {'user_id': user_id, + 'group_id': group_id}) def remove_user_from_group(self, user_id, group_id): # We don't check if user or group are still valid and let the remove @@ -310,12 +331,33 @@ class Identity(base.IdentityDriverBase): session.delete(membership_ref) def list_groups_for_user(self, user_id, hints): + def row_to_group_dict(row): + group = row.group.to_dict() + group['membership_expires_at'] = row.expires + return group + with sql.session_for_read() as session: self.get_user(user_id) query = session.query(model.Group).join(model.UserGroupMembership) query = query.filter(model.UserGroupMembership.user_id == user_id) query = sql.filter_limit_query(model.Group, query, hints) - return [g.to_dict() for g in query] + groups = [g.to_dict() for g in query] + + # Note(knikolla): We must use the ExpiringGroupMembership model + # so that we can access the expired property. + query = session.query(model.ExpiringUserGroupMembership) + query = query.filter( + model.ExpiringUserGroupMembership.user_id == user_id) + query = sql.filter_limit_query( + model.UserGroupMembership, query, hints) + expiring_groups = [row_to_group_dict(r) for r in query.all() + if not r.expired] + + # Note(knikolla): I would have loved to be able to merge the two + # queries together and use filter_limit_query on the union, but + # I haven't found a generic way to express expiration in a SQL + # query, therefore we have to apply the limits here again. + return self._apply_limits_to_list(groups + expiring_groups, hints) def list_users_in_group(self, group_id, hints): with sql.session_for_read() as session: diff --git a/keystone/identity/core.py b/keystone/identity/core.py index 2d0c266db..4aa489a12 100644 --- a/keystone/identity/core.py +++ b/keystone/identity/core.py @@ -1309,6 +1309,9 @@ class Manager(manager.Manager): # driver selection, so remove any such filter self._mark_domain_id_filter_satisfied(hints) ref_list = driver.list_groups_for_user(entity_id, hints) + for ref in ref_list: + if 'membership_expires_at' not in ref: + ref['membership_expires_at'] = None return self._set_domain_id_and_mapping( ref_list, domain_id, driver, mapping.EntityType.GROUP) diff --git a/keystone/identity/shadow_backends/sql.py b/keystone/identity/shadow_backends/sql.py index 5ac8b6469..3dbcfea39 100644 --- a/keystone/identity/shadow_backends/sql.py +++ b/keystone/identity/shadow_backends/sql.py @@ -195,3 +195,32 @@ class ShadowUsers(base.ShadowUsersDriverBase): fed_user_refs = sql.filter_limit_query(model.FederatedUser, query, hints) return [x.to_dict() for x in fed_user_refs] + + def add_user_to_group_expires(self, user_id, group_id): + def get_federated_user(): + with sql.session_for_read() as session: + query = session.query(model.FederatedUser) + query = query.filter_by(user_id=user_id) + user = query.first() + if not user: + # Note(knikolla): This shouldn't really ever happen, since + # this requires the user to already be logged in. + raise exception.UserNotFound() + return user + + with sql.session_for_write() as session: + user = get_federated_user() + query = session.query(model.ExpiringUserGroupMembership) + query = query.filter_by(user_id=user_id) + query = query.filter_by(group_id=group_id) + membership = query.first() + + if membership: + membership.last_verified = datetime.datetime.utcnow() + else: + session.add(model.ExpiringUserGroupMembership( + user_id=user_id, + group_id=group_id, + idp_id=user.idp_id, + last_verified=datetime.datetime.utcnow() + )) diff --git a/keystone/tests/unit/test_backend_ldap.py b/keystone/tests/unit/test_backend_ldap.py index 560c5da25..adb354764 100644 --- a/keystone/tests/unit/test_backend_ldap.py +++ b/keystone/tests/unit/test_backend_ldap.py @@ -984,6 +984,8 @@ class BaseLDAPIdentity(LDAPTestSetup, IdentityTests, AssignmentTests, # List groups for user. ref_list = PROVIDERS.identity_api.list_groups_for_user(public_user_id) + for ref in ref_list: + del(ref['membership_expires_at']) group['id'] = public_group_id self.assertThat(ref_list, matchers.Equals([group])) diff --git a/keystone/tests/unit/test_backend_sql.py b/keystone/tests/unit/test_backend_sql.py index a5e269d44..c0bff3aaa 100644 --- a/keystone/tests/unit/test_backend_sql.py +++ b/keystone/tests/unit/test_backend_sql.py @@ -16,6 +16,7 @@ import datetime from unittest import mock import uuid +import freezegun from oslo_db import exception as db_exception from oslo_db import options import sqlalchemy @@ -671,6 +672,94 @@ class SqlIdentity(SqlTests, negative_user['id']) self.assertEqual(0, len(group_refs)) + def test_add_user_to_group_expiring(self): + self._build_fed_resource() + domain = self._get_domain_fixture() + time = datetime.datetime.utcnow() + tick = datetime.timedelta(minutes=5) + + new_group = unit.new_group_ref(domain_id=domain['id']) + new_group = PROVIDERS.identity_api.create_group(new_group) + + fed_dict = unit.new_federated_user_ref() + fed_dict['idp_id'] = 'myidp' + fed_dict['protocol_id'] = 'mapped' + new_user = PROVIDERS.shadow_users_api.create_federated_user( + domain['id'], fed_dict + ) + + with freezegun.freeze_time(time - tick) as frozen_time: + PROVIDERS.shadow_users_api.add_user_to_group_expires( + new_user['id'], new_group['id']) + + self.config_fixture.config(group='federation', + default_authorization_ttl=0) + self.assertRaises(exception.NotFound, + PROVIDERS.identity_api.check_user_in_group, + new_user['id'], + new_group['id']) + + self.config_fixture.config(group='federation', + default_authorization_ttl=5) + PROVIDERS.identity_api.check_user_in_group(new_user['id'], + new_group['id']) + + # Expiration + frozen_time.tick(tick) + self.assertRaises(exception.NotFound, + PROVIDERS.identity_api.check_user_in_group, + new_user['id'], + new_group['id']) + + # Renewal + PROVIDERS.shadow_users_api.add_user_to_group_expires( + new_user['id'], new_group['id']) + PROVIDERS.identity_api.check_user_in_group(new_user['id'], + new_group['id']) + + def test_add_user_to_group_expiring_list(self): + self._build_fed_resource() + domain = self._get_domain_fixture() + self.config_fixture.config(group='federation', + default_authorization_ttl=5) + time = datetime.datetime.utcnow() + tick = datetime.timedelta(minutes=5) + + new_group = unit.new_group_ref(domain_id=domain['id']) + new_group = PROVIDERS.identity_api.create_group(new_group) + exp_new_group = unit.new_group_ref(domain_id=domain['id']) + exp_new_group = PROVIDERS.identity_api.create_group(exp_new_group) + + fed_dict = unit.new_federated_user_ref() + fed_dict['idp_id'] = 'myidp' + fed_dict['protocol_id'] = 'mapped' + new_user = PROVIDERS.shadow_users_api.create_federated_user( + domain['id'], fed_dict + ) + + PROVIDERS.identity_api.add_user_to_group(new_user['id'], + new_group['id']) + PROVIDERS.identity_api.check_user_in_group(new_user['id'], + new_group['id']) + + with freezegun.freeze_time(time - tick) as frozen_time: + PROVIDERS.shadow_users_api.add_user_to_group_expires( + new_user['id'], exp_new_group['id']) + PROVIDERS.identity_api.check_user_in_group(new_user['id'], + new_group['id']) + + groups = PROVIDERS.identity_api.list_groups_for_user( + new_user['id']) + self.assertEqual(len(groups), 2) + for group in groups: + if group.get('membership_expires_at'): + self.assertEqual(group['membership_expires_at'], time) + + frozen_time.tick(tick) + groups = PROVIDERS.identity_api.list_groups_for_user( + new_user['id']) + self.assertEqual(len(groups), 1) + def test_storing_null_domain_id_in_project_ref(self): """Test the special storage of domain_id=None in sql resource driver. |