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]
|
GROUP_NAME = __name__.split('.')[-1]
|
||||||
ALL_OPTS = [
|
ALL_OPTS = [
|
||||||
driver,
|
driver,
|
||||||
@ -103,6 +112,7 @@ ALL_OPTS = [
|
|||||||
trusted_dashboard,
|
trusted_dashboard,
|
||||||
sso_callback_template,
|
sso_callback_template,
|
||||||
caching,
|
caching,
|
||||||
|
default_authorization_ttl,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,16 +51,25 @@ class FederationProtocolModel(sql.ModelBase, sql.ModelDictMixin):
|
|||||||
|
|
||||||
class IdentityProviderModel(sql.ModelBase, sql.ModelDictMixin):
|
class IdentityProviderModel(sql.ModelBase, sql.ModelDictMixin):
|
||||||
__tablename__ = 'identity_provider'
|
__tablename__ = 'identity_provider'
|
||||||
attributes = ['id', 'domain_id', 'enabled', 'description', 'remote_ids']
|
attributes = ['id', 'domain_id', 'enabled', 'description', 'remote_ids',
|
||||||
mutable_attributes = frozenset(['description', 'enabled', 'remote_ids'])
|
'authorization_ttl']
|
||||||
|
mutable_attributes = frozenset(['description', 'enabled', 'remote_ids',
|
||||||
|
'authorization_ttl'])
|
||||||
|
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
domain_id = sql.Column(sql.String(64), nullable=False)
|
domain_id = sql.Column(sql.String(64), nullable=False)
|
||||||
enabled = sql.Column(sql.Boolean, nullable=False)
|
enabled = sql.Column(sql.Boolean, nullable=False)
|
||||||
description = sql.Column(sql.Text(), nullable=True)
|
description = sql.Column(sql.Text(), nullable=True)
|
||||||
|
authorization_ttl = sql.Column(sql.Integer, nullable=True)
|
||||||
|
|
||||||
remote_ids = orm.relationship('IdPRemoteIdsModel',
|
remote_ids = orm.relationship('IdPRemoteIdsModel',
|
||||||
order_by='IdPRemoteIdsModel.remote_id',
|
order_by='IdPRemoteIdsModel.remote_id',
|
||||||
cascade='all, delete-orphan')
|
cascade='all, delete-orphan')
|
||||||
|
expiring_user_group_memberships = orm.relationship(
|
||||||
|
'ExpiringUserGroupMembership',
|
||||||
|
cascade='all, delete-orphan',
|
||||||
|
backref="idp"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dictionary):
|
def from_dict(cls, dictionary):
|
||||||
|
@ -61,6 +61,11 @@ class User(sql.ModelBase, sql.ModelDictMixinWithExtras):
|
|||||||
lazy='joined',
|
lazy='joined',
|
||||||
cascade='all,delete-orphan',
|
cascade='all,delete-orphan',
|
||||||
backref='user')
|
backref='user')
|
||||||
|
expiring_user_group_memberships = orm.relationship(
|
||||||
|
'ExpiringUserGroupMembership',
|
||||||
|
cascade='all, delete-orphan',
|
||||||
|
backref="user"
|
||||||
|
)
|
||||||
created_at = sql.Column(sql.DateTime, nullable=True)
|
created_at = sql.Column(sql.DateTime, nullable=True)
|
||||||
last_active_at = sql.Column(sql.Date, nullable=True)
|
last_active_at = sql.Column(sql.Date, nullable=True)
|
||||||
# unique constraint needed here to support composite fk constraints
|
# 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)
|
domain_id = sql.Column(sql.String(64), nullable=False)
|
||||||
description = sql.Column(sql.Text())
|
description = sql.Column(sql.Text())
|
||||||
extra = sql.Column(sql.JsonBlob())
|
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
|
# Unique constraint across two columns to create the separation
|
||||||
# rather than just only 'name' being unique
|
# rather than just only 'name' being unique
|
||||||
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
|
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'),)
|
||||||
@ -387,6 +397,34 @@ class UserGroupMembership(sql.ModelBase, sql.ModelDictMixin):
|
|||||||
primary_key=True)
|
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):
|
class UserOption(sql.ModelBase):
|
||||||
__tablename__ = 'user_option'
|
__tablename__ = 'user_option'
|
||||||
user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id',
|
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),
|
cols = (('id', sql.String, 64),
|
||||||
('domain_id', sql.String, 64),
|
('domain_id', sql.String, 64),
|
||||||
('enabled', sql.Boolean, None),
|
('enabled', sql.Boolean, None),
|
||||||
('description', sql.Text, None))
|
('description', sql.Text, None),
|
||||||
|
('authorization_ttl', sql.Integer, None))
|
||||||
self.assertExpectedSchema('identity_provider', cols)
|
self.assertExpectedSchema('identity_provider', cols)
|
||||||
|
|
||||||
def test_idp_remote_ids(self):
|
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('user', 'domain_id'))
|
||||||
self.assertFalse(self.does_fk_exist('identity_provider', '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):
|
class MySQLOpportunisticFullMigration(FullMigration):
|
||||||
FIXTURE = db_fixtures.MySQLOpportunisticFixture
|
FIXTURE = db_fixtures.MySQLOpportunisticFixture
|
||||||
|
Loading…
Reference in New Issue
Block a user