summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristi Nikolla <kristi@nikolla.me>2019-07-29 16:19:51 -0400
committerKristi Nikolla <kristi@nikolla.me>2020-04-07 11:04:38 -0400
commitee54ba0ce49a5cebbf991e705492ad060e11867f (patch)
tree9571a59bb13e8485bcf8e3e3f23855ba4ef44fe5
parentba2e4b83e88d5c1576e9d2cd6a760367deef3a62 (diff)
downloadkeystone-ee54ba0ce49a5cebbf991e705492ad060e11867f.tar.gz
Expiring User Group Membership Model
Creates the model and migration for the expiring user group membership table. Change-Id: I48093403539918f81e6a174bdfa7b6497dd307fb Partial-Bug: 1809116
-rw-r--r--keystone/common/sql/contract_repo/versions/073_contract_expiring_group_membership.py15
-rw-r--r--keystone/common/sql/data_migration_repo/versions/073_migrate_expiring_group_membership.py15
-rw-r--r--keystone/common/sql/expand_repo/versions/073_expand_expiring_group_membership.py47
-rw-r--r--keystone/conf/federation.py10
-rw-r--r--keystone/federation/backends/sql.py13
-rw-r--r--keystone/identity/backends/sql_model.py38
-rw-r--r--keystone/tests/unit/test_backend_federation_sql.py3
-rw-r--r--keystone/tests/unit/test_sql_upgrade.py25
8 files changed, 163 insertions, 3 deletions
diff --git a/keystone/common/sql/contract_repo/versions/073_contract_expiring_group_membership.py b/keystone/common/sql/contract_repo/versions/073_contract_expiring_group_membership.py
new file mode 100644
index 000000000..8aa15c1ef
--- /dev/null
+++ b/keystone/common/sql/contract_repo/versions/073_contract_expiring_group_membership.py
@@ -0,0 +1,15 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+def upgrade(migrate_engine):
+ pass
diff --git a/keystone/common/sql/data_migration_repo/versions/073_migrate_expiring_group_membership.py b/keystone/common/sql/data_migration_repo/versions/073_migrate_expiring_group_membership.py
new file mode 100644
index 000000000..8aa15c1ef
--- /dev/null
+++ b/keystone/common/sql/data_migration_repo/versions/073_migrate_expiring_group_membership.py
@@ -0,0 +1,15 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+def upgrade(migrate_engine):
+ pass
diff --git a/keystone/common/sql/expand_repo/versions/073_expand_expiring_group_membership.py b/keystone/common/sql/expand_repo/versions/073_expand_expiring_group_membership.py
new file mode 100644
index 000000000..8577ee052
--- /dev/null
+++ b/keystone/common/sql/expand_repo/versions/073_expand_expiring_group_membership.py
@@ -0,0 +1,47 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import sqlalchemy as sql
+
+
+def upgrade(migrate_engine):
+ meta = sql.MetaData()
+ meta.bind = migrate_engine
+
+ identity_provider = sql.Table('identity_provider', meta, autoload=True)
+ authorization_ttl = sql.Column('authorization_ttl', sql.Integer,
+ nullable=True)
+ identity_provider.create_column(authorization_ttl)
+
+ user_table = sql.Table('user', meta, autoload=True)
+ group_table = sql.Table('group', meta, autoload=True)
+ idp_table = sql.Table('identity_provider', meta, autoload=True)
+
+ expiring_user_group_membership = sql.Table(
+ 'expiring_user_group_membership', meta,
+
+ sql.Column('user_id', sql.String(64),
+ sql.ForeignKey(user_table.c.id), primary_key=True),
+ sql.Column('group_id', sql.String(64),
+ sql.ForeignKey(group_table.c.id), primary_key=True),
+ sql.Column('idp_id',
+ sql.String(64),
+ sql.ForeignKey(idp_table.c.id,
+ ondelete='CASCADE'),
+ primary_key=True),
+ sql.Column('last_verified', sql.DateTime(), nullable=False),
+
+ mysql_engine='InnoDB',
+ mysql_charset='utf8'
+ )
+
+ expiring_user_group_membership.create(migrate_engine, checkfirst=True)
diff --git a/keystone/conf/federation.py b/keystone/conf/federation.py
index f38c04b4c..f99aef9b5 100644
--- a/keystone/conf/federation.py
+++ b/keystone/conf/federation.py
@@ -94,6 +94,15 @@ enabled. There is typically no reason to disable this.
"""))
+default_authorization_ttl = cfg.IntOpt(
+ 'default_authorization_ttl',
+ default=0,
+ help=utils.fmt("""
+Default time in minutes for the validity of group memberships carried over
+from a mapping. Default is 0, which means disabled.
+"""))
+
+
GROUP_NAME = __name__.split('.')[-1]
ALL_OPTS = [
driver,
@@ -103,6 +112,7 @@ ALL_OPTS = [
trusted_dashboard,
sso_callback_template,
caching,
+ default_authorization_ttl,
]
diff --git a/keystone/federation/backends/sql.py b/keystone/federation/backends/sql.py
index ca7a592d3..7d39f69c3 100644
--- a/keystone/federation/backends/sql.py
+++ b/keystone/federation/backends/sql.py
@@ -51,16 +51,25 @@ class FederationProtocolModel(sql.ModelBase, sql.ModelDictMixin):
class IdentityProviderModel(sql.ModelBase, sql.ModelDictMixin):
__tablename__ = 'identity_provider'
- attributes = ['id', 'domain_id', 'enabled', 'description', 'remote_ids']
- mutable_attributes = frozenset(['description', 'enabled', 'remote_ids'])
+ attributes = ['id', 'domain_id', 'enabled', 'description', 'remote_ids',
+ 'authorization_ttl']
+ mutable_attributes = frozenset(['description', 'enabled', 'remote_ids',
+ 'authorization_ttl'])
id = sql.Column(sql.String(64), primary_key=True)
domain_id = sql.Column(sql.String(64), nullable=False)
enabled = sql.Column(sql.Boolean, nullable=False)
description = sql.Column(sql.Text(), nullable=True)
+ authorization_ttl = sql.Column(sql.Integer, nullable=True)
+
remote_ids = orm.relationship('IdPRemoteIdsModel',
order_by='IdPRemoteIdsModel.remote_id',
cascade='all, delete-orphan')
+ expiring_user_group_memberships = orm.relationship(
+ 'ExpiringUserGroupMembership',
+ cascade='all, delete-orphan',
+ backref="idp"
+ )
@classmethod
def from_dict(cls, dictionary):
diff --git a/keystone/identity/backends/sql_model.py b/keystone/identity/backends/sql_model.py
index 8798d326c..72e86aaaa 100644
--- a/keystone/identity/backends/sql_model.py
+++ b/keystone/identity/backends/sql_model.py
@@ -61,6 +61,11 @@ class User(sql.ModelBase, sql.ModelDictMixinWithExtras):
lazy='joined',
cascade='all,delete-orphan',
backref='user')
+ expiring_user_group_memberships = orm.relationship(
+ 'ExpiringUserGroupMembership',
+ cascade='all, delete-orphan',
+ backref="user"
+ )
created_at = sql.Column(sql.DateTime, nullable=True)
last_active_at = sql.Column(sql.Date, nullable=True)
# unique constraint needed here to support composite fk constraints
@@ -370,6 +375,11 @@ class Group(sql.ModelBase, sql.ModelDictMixinWithExtras):
domain_id = sql.Column(sql.String(64), nullable=False)
description = sql.Column(sql.Text())
extra = sql.Column(sql.JsonBlob())
+ expiring_user_group_memberships = orm.relationship(
+ 'ExpiringUserGroupMembership',
+ cascade='all, delete-orphan',
+ backref="group"
+ )
# Unique constraint across two columns to create the separation
# rather than just only 'name' being unique
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
@@ -387,6 +397,34 @@ class UserGroupMembership(sql.ModelBase, sql.ModelDictMixin):
primary_key=True)
+class ExpiringUserGroupMembership(sql.ModelBase, sql.ModelDictMixin):
+ """Expiring group membership through federation mapping rules."""
+
+ __tablename__ = 'expiring_user_group_membership'
+ user_id = sql.Column(sql.String(64),
+ sql.ForeignKey('user.id'),
+ primary_key=True)
+ group_id = sql.Column(sql.String(64),
+ sql.ForeignKey('group.id'),
+ primary_key=True)
+ idp_id = sql.Column(sql.String(64),
+ sql.ForeignKey('identity_provider.id',
+ ondelete='CASCADE'),
+ primary_key=True)
+ last_verified = sql.Column(sql.DateTime, nullable=False)
+
+ @hybrid_property
+ def expires(self):
+ ttl = self.idp.authorization_ttl
+ if not ttl:
+ ttl = CONF.federation.default_authorization_ttl
+ return self.last_verified + datetime.timedelta(minutes=ttl)
+
+ @hybrid_property
+ def expired(self):
+ return self.expires <= datetime.datetime.utcnow()
+
+
class UserOption(sql.ModelBase):
__tablename__ = 'user_option'
user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id',
diff --git a/keystone/tests/unit/test_backend_federation_sql.py b/keystone/tests/unit/test_backend_federation_sql.py
index d20e076cf..ffd0f30f9 100644
--- a/keystone/tests/unit/test_backend_federation_sql.py
+++ b/keystone/tests/unit/test_backend_federation_sql.py
@@ -23,7 +23,8 @@ class SqlFederation(test_backend_sql.SqlModels):
cols = (('id', sql.String, 64),
('domain_id', sql.String, 64),
('enabled', sql.Boolean, None),
- ('description', sql.Text, None))
+ ('description', sql.Text, None),
+ ('authorization_ttl', sql.Integer, None))
self.assertExpectedSchema('identity_provider', cols)
def test_idp_remote_ids(self):
diff --git a/keystone/tests/unit/test_sql_upgrade.py b/keystone/tests/unit/test_sql_upgrade.py
index 044fe2c59..22cb44260 100644
--- a/keystone/tests/unit/test_sql_upgrade.py
+++ b/keystone/tests/unit/test_sql_upgrade.py
@@ -3474,6 +3474,31 @@ class FullMigration(SqlMigrateBase, unit.TestCase):
self.assertFalse(self.does_fk_exist('user', 'domain_id'))
self.assertFalse(self.does_fk_exist('identity_provider', 'domain_id'))
+ def test_migration_073_contract_expiring_group_membership(self):
+ self.expand(72)
+ self.migrate(72)
+ self.contract(72)
+
+ membership_table = 'expiring_user_group_membership'
+ self.assertTableDoesNotExist(membership_table)
+
+ idp_table = 'identity_provider'
+ self.assertTableColumns(
+ idp_table,
+ ['id', 'domain_id', 'enabled', 'description'])
+
+ self.expand(73)
+ self.migrate(73)
+ self.contract(73)
+
+ self.assertTableColumns(
+ membership_table,
+ ['user_id', 'group_id', 'idp_id', 'last_verified'])
+ self.assertTableColumns(
+ idp_table,
+ ['id', 'domain_id', 'enabled', 'description',
+ 'authorization_ttl'])
+
class MySQLOpportunisticFullMigration(FullMigration):
FIXTURE = db_fixtures.MySQLOpportunisticFixture