Expiring User Group Membership Model
Creates the model and migration for the expiring user group membership table. Change-Id: I48093403539918f81e6a174bdfa7b6497dd307fb Partial-Bug: 1809116
This commit is contained in:
parent
ba2e4b83e8
commit
ee54ba0ce4
@ -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
|
@ -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
|
@ -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)
|
@ -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,
|
||||
]
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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',
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user