summaryrefslogtreecommitdiff
path: root/keystone
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-04-10 00:18:35 +0000
committerGerrit Code Review <review@openstack.org>2020-04-10 00:18:35 +0000
commitf5fd13f26f3e00478140c9d330a0a2689ac05a8c (patch)
tree6bbfcbb4e6b2cf53379fa7fc9fe32d6c8fb5ff0b /keystone
parent033e7aff870f2ccd4dec607e9c47efff630ece29 (diff)
parentd8938514fe3f9df54467f557e13770533c614259 (diff)
downloadkeystone-f5fd13f26f3e00478140c9d330a0a2689ac05a8c.tar.gz
Merge "Expiring Group Membership Driver - Add, List Groups"
Diffstat (limited to 'keystone')
-rw-r--r--keystone/identity/backends/sql.py54
-rw-r--r--keystone/identity/core.py3
-rw-r--r--keystone/identity/shadow_backends/sql.py29
-rw-r--r--keystone/tests/unit/test_backend_ldap.py2
-rw-r--r--keystone/tests/unit/test_backend_sql.py89
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.