class TokenModel(sql.ModelBase, sql.DictBase): __tablename__ = 'token' attributes = ['id', 'expires', 'user_id', 'trust_id'] id = sql.Column(sql.String(64), primary_key=True) expires = sql.Column(sql.DateTime(), default=None) extra = sql.Column(sql.JsonBlob()) valid = sql.Column(sql.Boolean(), default=True, nullable=False) user_id = sql.Column(sql.String(64)) trust_id = sql.Column(sql.String(64)) __table_args__ = (sql.Index('ix_token_expires', 'expires'), sql.Index('ix_token_expires_valid', 'expires', 'valid'), sql.Index('ix_token_user_id', 'user_id'), sql.Index('ix_token_trust_id', 'trust_id'))
class Project(sql.ModelBase, sql.DictBase): __tablename__ = 'project' attributes = ['id', 'name', 'domain_id', 'description', 'enabled'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class RequestToken(sql.ModelBase, sql.DictBase): __tablename__ = 'request_token' attributes = ['id', 'request_secret', 'verifier', 'authorizing_user_id', 'requested_project_id', 'role_ids', 'consumer_id', 'expires_at'] id = sql.Column(sql.String(64), primary_key=True, nullable=False) request_secret = sql.Column(sql.String(64), nullable=False) verifier = sql.Column(sql.String(64), nullable=True) authorizing_user_id = sql.Column(sql.String(64), nullable=True) requested_project_id = sql.Column(sql.String(64), nullable=False) role_ids = sql.Column(sql.Text(), nullable=True) consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'), nullable=False, index=True) expires_at = sql.Column(sql.String(64), nullable=True) @classmethod def from_dict(cls, user_dict): return cls(**user_dict) def to_dict(self): return dict(self.items())
class Password(sql.ModelBase, sql.DictBase): __tablename__ = 'password' attributes = ['id', 'local_user_id', 'password', 'created_at', 'expires_at'] id = sql.Column(sql.Integer, primary_key=True) local_user_id = sql.Column(sql.Integer, sql.ForeignKey('local_user.id', ondelete='CASCADE')) password = sql.Column(sql.String(128), nullable=True) # created_at default set here to safe guard in case it gets missed created_at = sql.Column(sql.DateTime, nullable=False, default=datetime.datetime.utcnow) expires_at = sql.Column(sql.DateTime, nullable=True) self_service = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0')
class AccessToken(sql.ModelBase, sql.DictBase): __tablename__ = 'access_token_oauth2' attributes = [ 'id', 'consumer_id', 'authorizing_user_id', 'expires_at', 'scopes', 'refresh_token', 'valid', 'extra' ] id = sql.Column(sql.String(64), primary_key=True, nullable=False) consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer_oauth2.id'), nullable=False, index=True) # NOTE(garcianavalon) if the consumers uses the client credentials grant # there is no authorizing user, so it should be nullable. authorizing_user_id = sql.Column(sql.String(64), nullable=True) # TODO(garcianavalon) datetime type or similar? expires_at = sql.Column(sql.String(64), nullable=False) scopes = sql.Column(sql.JsonBlob(), nullable=True) refresh_token = sql.Column(sql.String(64), nullable=True) valid = sql.Column(sql.Boolean(), default=True, nullable=False) extra = sql.Column(sql.JsonBlob(), nullable=True)
class Endpoint(sql.ModelBase, sql.DictBase): __tablename__ = 'endpoint' attributes = [ 'id', 'interface', 'region_id', 'service_id', 'url', 'legacy_endpoint_id', 'enabled' ] id = sql.Column(sql.String(64), primary_key=True) legacy_endpoint_id = sql.Column(sql.String(64)) interface = sql.Column(sql.String(8), nullable=False) region_id = sql.Column(sql.String(255), sql.ForeignKey('region.id', ondelete='RESTRICT'), nullable=True, default=None) service_id = sql.Column(sql.String(64), sql.ForeignKey('service.id'), nullable=False) url = sql.Column(sql.Text(), nullable=False) enabled = sql.Column(sql.Boolean, nullable=False, default=True, server_default=sqlalchemy.sql.expression.true()) extra = sql.Column(sql.JsonBlob())
class Password(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'password' attributes = ['id', 'local_user_id', 'password', 'password_hash', 'created_at', 'expires_at'] id = sql.Column(sql.Integer, primary_key=True) local_user_id = sql.Column(sql.Integer, sql.ForeignKey('local_user.id', ondelete='CASCADE')) # TODO(notmorgan): in the Q release the "password" field can be dropped as # long as data migration exists to move the hashes over to the # password_hash column if no value is in the password_hash column. password = sql.Column(sql.String(128), nullable=True) password_hash = sql.Column(sql.String(255), nullable=True) # created_at default set here to safe guard in case it gets missed created_at = sql.Column(sql.DateTime, nullable=False, default=datetime.datetime.utcnow) expires_at = sql.Column(sql.DateTime, nullable=True) self_service = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0')
class User(sql.ModelBase, sql.DictBase): __tablename__ = 'user' attributes = ['id', 'name', 'domain_id', 'password', 'enabled', 'default_project_id'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(255), nullable=False) domain_id = sql.Column(sql.String(64), nullable=False) password = sql.Column(sql.String(128)) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) default_project_id = sql.Column(sql.String(64)) # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {}) def to_dict(self, include_extra_dict=False): d = super(User, self).to_dict(include_extra_dict=include_extra_dict) if 'default_project_id' in d and d['default_project_id'] is None: del d['default_project_id'] return d
class RegisteredLimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'registered_limit' attributes = [ 'internal_id', 'id', 'service_id', 'region_id', 'resource_name', 'default_limit', 'description' ] internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(length=64), nullable=False, unique=True) service_id = sql.Column(sql.String(255), sql.ForeignKey('service.id')) region_id = sql.Column(sql.String(64), sql.ForeignKey('region.id'), nullable=True) resource_name = sql.Column(sql.String(255)) default_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) def to_dict(self): ref = super(RegisteredLimitModel, self).to_dict() ref.pop('internal_id') return ref
class FederatedUser(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'federated_user' attributes = ['id', 'user_id', 'idp_id', 'protocol_id', 'unique_id', 'display_name'] id = sql.Column(sql.Integer, primary_key=True) user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id', ondelete='CASCADE')) idp_id = sql.Column(sql.String(64), sql.ForeignKey('identity_provider.id', ondelete='CASCADE')) protocol_id = sql.Column(sql.String(64), nullable=False) unique_id = sql.Column(sql.String(255), nullable=False) display_name = sql.Column(sql.String(255), nullable=True) __table_args__ = ( sql.UniqueConstraint('idp_id', 'protocol_id', 'unique_id'), sqlalchemy.ForeignKeyConstraint(['protocol_id', 'idp_id'], ['federation_protocol.id', 'federation_protocol.idp_id']) )
class LocalUser(sql.ModelBase, sql.DictBase): __tablename__ = 'local_user' attributes = ['id', 'user_id', 'domain_id', 'name'] id = sql.Column(sql.Integer, primary_key=True) user_id = sql.Column(sql.String(64), sql.ForeignKey('user.id', ondelete='CASCADE'), unique=True) domain_id = sql.Column(sql.String(64), nullable=False) name = sql.Column(sql.String(255), nullable=False) passwords = orm.relationship('Password', single_parent=True, cascade='all,delete-orphan', lazy='subquery', backref='local_user', order_by='Password.created_at') failed_auth_count = sql.Column(sql.Integer, nullable=True) failed_auth_at = sql.Column(sql.DateTime, nullable=True) __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
class RegisteredLimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'registered_limit' attributes = [ 'id', 'service_id', 'region_id', 'resource_name', 'default_limit', 'description' ] id = sql.Column(sql.String(length=64), primary_key=True) service_id = sql.Column(sql.String(255), sql.ForeignKey('service.id')) region_id = sql.Column(sql.String(64), sql.ForeignKey('region.id'), nullable=True) resource_name = sql.Column(sql.String(255)) default_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) __table_args__ = (sqlalchemy.UniqueConstraint('service_id', 'region_id', 'resource_name'), )
class AccessRuleModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'access_rule' attributes = ['external_id', 'user_id', 'service', 'path', 'method'] id = sql.Column(sql.Integer, primary_key=True, nullable=False) external_id = sql.Column(sql.String(64), index=True, unique=True) user_id = sql.Column(sql.String(64), index=True) service = sql.Column(sql.String(64)) path = sql.Column(sql.String(128)) method = sql.Column(sql.String(16)) __table_args__ = (sql.UniqueConstraint( 'user_id', 'service', 'path', 'method', name='duplicate_access_rule_for_user_constraint'), ) application_credential = sqlalchemy.orm.relationship( 'ApplicationCredentialAccessRuleModel', backref=sqlalchemy.orm.backref('access_rule'))
class AccessToken(sql.ModelBase, sql.DictBase): __tablename__ = 'access_token' attributes = [ 'id', 'access_secret', 'authorizing_user_id', 'project_id', 'requested_roles', 'consumer_id', 'expires_at' ] id = sql.Column(sql.String(64), primary_key=True, nullable=False) access_secret = sql.Column(sql.String(64), nullable=False) authorizing_user_id = sql.Column(sql.String(64), nullable=False, index=True) project_id = sql.Column(sql.String(64), nullable=False) requested_roles = sql.Column(sql.Text(), nullable=False) consumer_id = sql.Column(sql.String(64), nullable=False) expires_at = sql.Column(sql.String(64), nullable=True) @classmethod def from_dict(cls, user_dict): return cls(**user_dict) def to_dict(self): return dict(self.iteritems())
class LimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'limit' attributes = [ 'id', 'project_id', 'service_id', 'region_id', 'resource_name', 'resource_limit' ] id = sql.Column(sql.String(length=64), primary_key=True) project_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) service_id = sql.Column(sql.String(255)) region_id = sql.Column(sql.String(64), nullable=True) resource_name = sql.Column(sql.String(255)) resource_limit = sql.Column(sql.Integer, nullable=False) __table_args__ = ( sqlalchemy.ForeignKeyConstraint( ['service_id', 'region_id', 'resource_name'], [ 'registered_limit.service_id', 'registered_limit.region_id', 'registered_limit.resource_name' ]), sqlalchemy.UniqueConstraint('project_id', 'service_id', 'region_id', 'resource_name'), )
class TestModel(ModelBase, sql.ModelDictMixin): __tablename__ = 'testmodel' id = sql.Column(sql.String(64), primary_key=True) text = sql.Column(sql.String(64), nullable=False)
class User(sql.ModelBase, sql.DictBase): __tablename__ = 'user' attributes = [ 'id', 'name', 'domain_id', 'password', 'enabled', 'default_project_id' ] id = sql.Column(sql.String(64), primary_key=True) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) default_project_id = sql.Column(sql.String(64)) local_user = orm.relationship('LocalUser', uselist=False, single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') federated_users = orm.relationship('FederatedUser', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') # name property @hybrid_property def name(self): if self.local_user: return self.local_user.name elif self.federated_users: return self.federated_users[0].display_name else: return None @name.setter def name(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.name = value @name.expression def name(cls): return LocalUser.name # password property @hybrid_property def password(self): if self.local_user and self.local_user.passwords: return self.local_user.passwords[0].password else: return None @password.setter def password(self, value): if not value: if self.local_user and self.local_user.passwords: self.local_user.passwords = [] else: if not self.local_user: self.local_user = LocalUser() if not self.local_user.passwords: self.local_user.passwords.append(Password()) self.local_user.passwords[0].password = value @password.expression def password(cls): return Password.password # domain_id property @hybrid_property def domain_id(self): if self.local_user: return self.local_user.domain_id else: return None @domain_id.setter def domain_id(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.domain_id = value @domain_id.expression def domain_id(cls): return LocalUser.domain_id def to_dict(self, include_extra_dict=False): d = super(User, self).to_dict(include_extra_dict=include_extra_dict) if 'default_project_id' in d and d['default_project_id'] is None: del d['default_project_id'] return d
class User(sql.ModelBase, sql.DictBase): __tablename__ = 'user' attributes = [ 'id', 'name', 'domain_id', 'password', 'enabled', 'default_project_id', 'password_expires_at' ] readonly_attributes = ['id', 'password_expires_at'] id = sql.Column(sql.String(64), primary_key=True) _enabled = sql.Column('enabled', sql.Boolean) extra = sql.Column(sql.JsonBlob()) default_project_id = sql.Column(sql.String(64)) local_user = orm.relationship('LocalUser', uselist=False, single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') federated_users = orm.relationship('FederatedUser', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') nonlocal_users = orm.relationship('NonLocalUser', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') created_at = sql.Column(sql.DateTime, nullable=True) last_active_at = sql.Column(sql.Date, nullable=True) # name property @hybrid_property def name(self): if self.local_user: return self.local_user.name elif self.nonlocal_users: return self.nonlocal_users[0].name elif self.federated_users: return self.federated_users[0].display_name else: return None @name.setter def name(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.name = value @name.expression def name(cls): return LocalUser.name # password properties @hybrid_property def password_ref(self): """Return the current password.""" if self.local_user and self.local_user.passwords: return self.local_user.passwords[-1] return None @hybrid_property def password(self): if self.password_ref: return self.password_ref.password return None @hybrid_property def password_created_at(self): if self.password_ref: return self.password_ref.created_at return None @hybrid_property def password_expires_at(self): if self.password_ref: return self.password_ref.expires_at return None @hybrid_property def password_is_expired(self): if self.password_expires_at: return datetime.datetime.utcnow() >= self.password_expires_at return False @password.setter def password(self, value): now = datetime.datetime.utcnow() if not self.local_user: self.local_user = LocalUser() # truncate extra passwords if self.local_user.passwords: unique_cnt = CONF.security_compliance.unique_last_password_count self.local_user.passwords = self.local_user.passwords[-unique_cnt:] # set all previous passwords to be expired for ref in self.local_user.passwords: if not ref.expires_at or ref.expires_at > now: ref.expires_at = now new_password_ref = Password() new_password_ref.password = value new_password_ref.created_at = now new_password_ref.expires_at = self._get_password_expires_at(now) self.local_user.passwords.append(new_password_ref) def _get_password_expires_at(self, created_at): expires_days = CONF.security_compliance.password_expires_days ignore_list = CONF.security_compliance.password_expires_ignore_user_ids if expires_days and (self.id not in ignore_list): expired_date = (created_at + datetime.timedelta(days=expires_days)) return expired_date.replace(microsecond=0) return None @password.expression def password(cls): return Password.password # domain_id property @hybrid_property def domain_id(self): if self.local_user: return self.local_user.domain_id elif self.nonlocal_users: return self.nonlocal_users[0].domain_id else: return None @domain_id.setter def domain_id(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.domain_id = value @domain_id.expression def domain_id(cls): return LocalUser.domain_id # enabled property @hybrid_property def enabled(self): if self._enabled: max_days = ( CONF.security_compliance.disable_user_account_days_inactive) last_active = self.last_active_at if not last_active and self.created_at: last_active = self.created_at.date() if max_days and last_active: now = datetime.datetime.utcnow().date() days_inactive = (now - last_active).days if days_inactive >= max_days: self._enabled = False return self._enabled @enabled.setter def enabled(self, value): if (value and CONF.security_compliance.disable_user_account_days_inactive): self.last_active_at = datetime.datetime.utcnow().date() if value and self.local_user: self.local_user.failed_auth_count = 0 self.local_user.failed_auth_at = None self._enabled = value @enabled.expression def enabled(cls): return User._enabled def to_dict(self, include_extra_dict=False): d = super(User, self).to_dict(include_extra_dict=include_extra_dict) if 'default_project_id' in d and d['default_project_id'] is None: del d['default_project_id'] return d @classmethod def from_dict(cls, user_dict): """Override from_dict to remove password_expires_at attribute. Overriding this method to remove password_expires_at attribute to support update_user and unit tests where password_expires_at inadvertently gets added by calling to_dict followed by from_dict. :param user_dict: User entity dictionary :returns User: User object """ new_dict = user_dict.copy() password_expires_at_key = 'password_expires_at' if password_expires_at_key in user_dict: del new_dict[password_expires_at_key] return super(User, cls).from_dict(new_dict)
class RevocationEvent(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'revocation_event' attributes = model.REVOKE_KEYS # The id field is not going to be exposed to the outside world. # It is, however, necessary for SQLAlchemy. id = sql.Column(sql.String(64), primary_key=True) domain_id = sql.Column(sql.String(64)) project_id = sql.Column(sql.String(64)) user_id = sql.Column(sql.String(64)) role_id = sql.Column(sql.String(64)) trust_id = sql.Column(sql.String(64)) consumer_id = sql.Column(sql.String(64)) access_token_id = sql.Column(sql.String(64)) issued_before = sql.Column(sql.DateTime(), nullable=False) expires_at = sql.Column(sql.DateTime()) revoked_at = sql.Column(sql.DateTime(), nullable=False)
class Role(sql.ModelBase, sql.DictBase): __tablename__ = 'role' attributes = ['id', 'name'] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), unique=True, nullable=False) extra = sql.Column(sql.JsonBlob())
class GroupProjectGrant(sql.ModelBase, BaseGrant): __tablename__ = 'group_project_metadata' group_id = sql.Column(sql.String(64), primary_key=True) project_id = sql.Column(sql.String(64), primary_key=True) data = sql.Column(sql.JsonBlob())
class Role(sql.ModelBase, sql.DictBase): __tablename__ = 'role' id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), unique=True, nullable=False)
class GroupDomainGrant(sql.ModelBase, BaseGrant): __tablename__ = 'group_domain_metadata' group_id = sql.Column(sql.String(64), primary_key=True) domain_id = sql.Column(sql.String(64), primary_key=True) data = sql.Column(sql.JsonBlob())
class ProjectEndpoint(sql.ModelBase, sql.DictBase): """project-endpoint relationship table.""" __tablename__ = 'project_endpoint' attributes = ['endpoint_id', 'project_id'] endpoint_id = sql.Column(sql.String(64), primary_key=True, nullable=False) project_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
class ConfigRegister(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'config_register' type = sql.Column(sql.String(64), primary_key=True) domain_id = sql.Column(sql.String(64), nullable=False)
class User(sql.ModelBase, sql.ModelDictMixinWithExtras): __tablename__ = 'user' attributes = ['id', 'name', 'domain_id', 'password', 'enabled', 'default_project_id', 'password_expires_at'] readonly_attributes = ['id', 'password_expires_at', 'password'] resource_options_registry = iro.USER_OPTIONS_REGISTRY id = sql.Column(sql.String(64), primary_key=True) domain_id = sql.Column(sql.String(64), nullable=False) _enabled = sql.Column('enabled', sql.Boolean) extra = sql.Column(sql.JsonBlob()) default_project_id = sql.Column(sql.String(64), index=True) _resource_option_mapper = orm.relationship( 'UserOption', single_parent=True, cascade='all,delete,delete-orphan', lazy='subquery', backref='user', collection_class=collections.attribute_mapped_collection('option_id')) local_user = orm.relationship('LocalUser', uselist=False, single_parent=True, lazy='joined', cascade='all,delete-orphan', backref='user') federated_users = orm.relationship('FederatedUser', single_parent=True, lazy='joined', cascade='all,delete-orphan', backref='user') nonlocal_user = orm.relationship('NonLocalUser', uselist=False, single_parent=True, lazy='joined', 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 __table_args__ = (sql.UniqueConstraint('id', 'domain_id'), {}) # NOTE(stevemar): we use a hybrid property here because we leverage the # expression method, see `@name.expression` and `LocalUser.name` below. @hybrid_property def name(self): """Return the current user name.""" if self.local_user: return self.local_user.name elif self.nonlocal_user: return self.nonlocal_user.name elif self.federated_users: return self.federated_users[0].display_name else: return None @name.setter def name(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.name = value @name.expression def name(cls): return LocalUser.name # password properties @property def password_ref(self): """Return the current password ref.""" if self.local_user and self.local_user.passwords: return self.local_user.passwords[-1] return None # NOTE(stevemar): we use a hybrid property here because we leverage the # expression method, see `@password.expression` and `Password.password` # below. @hybrid_property def password(self): """Return the current password.""" if self.password_ref: return self.password_ref.password_hash return None @property def password_created_at(self): """Return when password was created at.""" if self.password_ref: return self.password_ref.created_at return None @property def password_expires_at(self): """Return when password expires at.""" if self.password_ref: return self.password_ref.expires_at return None @property def password_is_expired(self): """Return whether password is expired or not.""" if self.password_expires_at and not self._password_expiry_exempt(): return datetime.datetime.utcnow() >= self.password_expires_at return False @password.setter def password(self, value): now = datetime.datetime.utcnow() if not self.local_user: self.local_user = LocalUser() # truncate extra passwords if self.local_user.passwords: unique_cnt = CONF.security_compliance.unique_last_password_count unique_cnt = unique_cnt + 1 if unique_cnt == 0 else unique_cnt self.local_user.passwords = self.local_user.passwords[-unique_cnt:] # set all previous passwords to be expired for ref in self.local_user.passwords: if not ref.expires_at or ref.expires_at > now: ref.expires_at = now new_password_ref = Password() hashed_passwd = None if value is not None: # NOTE(notmorgan): hash the passwords, never directly bind the # "value" in the unhashed form to hashed_passwd to ensure the # unhashed password cannot end up in the db. If an unhashed # password ends up in the DB, it cannot be used for auth, it is # however incorrect and could leak user credentials (due to users # doing insecure things such as sharing passwords across # different systems) to unauthorized parties. hashed_passwd = password_hashing.hash_password(value) new_password_ref.password_hash = hashed_passwd new_password_ref.created_at = now new_password_ref.expires_at = self._get_password_expires_at(now) self.local_user.passwords.append(new_password_ref) def _password_expiry_exempt(self): # Get the IGNORE_PASSWORD_EXPIRY_OPT value from the user's # option_mapper. return getattr( self.get_resource_option(iro.IGNORE_PASSWORD_EXPIRY_OPT.option_id), 'option_value', False) def _get_password_expires_at(self, created_at): expires_days = CONF.security_compliance.password_expires_days if not self._password_expiry_exempt(): if expires_days: expired_date = (created_at + datetime.timedelta(days=expires_days)) return expired_date.replace(microsecond=0) return None @password.expression def password(cls): return Password.password_hash # NOTE(stevemar): we use a hybrid property here because we leverage the # expression method, see `@enabled.expression` and `User._enabled` below. @hybrid_property def enabled(self): """Return whether user is enabled or not.""" if self._enabled: max_days = ( CONF.security_compliance.disable_user_account_days_inactive) last_active = self.last_active_at if not last_active and self.created_at: last_active = self.created_at.date() if max_days and last_active: now = datetime.datetime.utcnow().date() days_inactive = (now - last_active).days if days_inactive >= max_days: self._enabled = False return self._enabled @enabled.setter def enabled(self, value): if (value and CONF.security_compliance.disable_user_account_days_inactive): self.last_active_at = datetime.datetime.utcnow().date() if value and self.local_user: self.local_user.failed_auth_count = 0 self.local_user.failed_auth_at = None self._enabled = value @enabled.expression def enabled(cls): return User._enabled def get_resource_option(self, option_id): if option_id in self._resource_option_mapper.keys(): return self._resource_option_mapper[option_id] return None def to_dict(self, include_extra_dict=False): d = super(User, self).to_dict(include_extra_dict=include_extra_dict) if 'default_project_id' in d and d['default_project_id'] is None: del d['default_project_id'] # NOTE(notmorgan): Eventually it may make sense to drop the empty # option dict creation to the superclass (if enough models use it) d['options'] = resource_options.ref_mapper_to_dict_options(self) return d @classmethod def from_dict(cls, user_dict): """Override from_dict to remove password_expires_at attribute. Overriding this method to remove password_expires_at attribute to support update_user and unit tests where password_expires_at inadvertently gets added by calling to_dict followed by from_dict. :param user_dict: User entity dictionary :returns User: User object """ new_dict = user_dict.copy() resource_options = {} options = new_dict.pop('options', {}) password_expires_at_key = 'password_expires_at' if password_expires_at_key in user_dict: del new_dict[password_expires_at_key] for opt in cls.resource_options_registry.options: if opt.option_name in options: opt_value = options[opt.option_name] # NOTE(notmorgan): None is always a valid type if opt_value is not None: opt.validator(opt_value) resource_options[opt.option_id] = opt_value user_obj = super(User, cls).from_dict(new_dict) setattr(user_obj, '_resource_options', resource_options) return user_obj
class TrustRole(sql.ModelBase): __tablename__ = 'trust_role' attributes = ['trust_id', 'role_id'] trust_id = sql.Column(sql.String(64), primary_key=True, nullable=False) role_id = sql.Column(sql.String(64), primary_key=True, nullable=False)
class User(sql.ModelBase, sql.DictBase): __tablename__ = 'user' attributes = ['id', 'name', 'domain_id', 'password', 'enabled', 'default_project_id'] id = sql.Column(sql.String(64), primary_key=True) _enabled = sql.Column('enabled', sql.Boolean) extra = sql.Column(sql.JsonBlob()) default_project_id = sql.Column(sql.String(64)) local_user = orm.relationship('LocalUser', uselist=False, single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') federated_users = orm.relationship('FederatedUser', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') nonlocal_users = orm.relationship('NonLocalUser', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='user') created_at = sql.Column(sql.DateTime, nullable=True) last_active_at = sql.Column(sql.Date, nullable=True) # name property @hybrid_property def name(self): if self.local_user: return self.local_user.name elif self.nonlocal_users: return self.nonlocal_users[0].name elif self.federated_users: return self.federated_users[0].display_name else: return None @name.setter def name(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.name = value @name.expression def name(cls): return LocalUser.name # password properties @hybrid_property def password_ref(self): """Return the current password.""" if self.local_user and self.local_user.passwords: return self.local_user.passwords[-1] return None @hybrid_property def password(self): if self.password_ref: return self.password_ref.password return None @hybrid_property def password_expires_at(self): if self.password_ref: return self.password_ref.expires_at return None @password.setter def password(self, value): now = datetime.datetime.utcnow() if not self.local_user: self.local_user = LocalUser() # set all previous passwords to be expired for ref in self.local_user.passwords: if not ref.expires_at or ref.expires_at > now: ref.expires_at = now new_password_ref = Password() new_password_ref.password = value new_password_ref.created_at = now self.local_user.passwords.append(new_password_ref) @password.expression def password(cls): return Password.password # domain_id property @hybrid_property def domain_id(self): if self.local_user: return self.local_user.domain_id elif self.nonlocal_users: return self.nonlocal_users[0].domain_id else: return None @domain_id.setter def domain_id(self, value): if not self.local_user: self.local_user = LocalUser() self.local_user.domain_id = value @domain_id.expression def domain_id(cls): return LocalUser.domain_id # enabled property @hybrid_property def enabled(self): if self._enabled: max_days = ( CONF.security_compliance.disable_user_account_days_inactive) last_active = self.last_active_at if not last_active and self.created_at: last_active = self.created_at.date() if max_days and last_active: now = datetime.datetime.utcnow().date() days_inactive = (now - last_active).days if days_inactive >= max_days: self._enabled = False return self._enabled @enabled.setter def enabled(self, value): if (value and CONF.security_compliance.disable_user_account_days_inactive): self.last_active_at = datetime.datetime.utcnow().date() self._enabled = value @enabled.expression def enabled(cls): return User._enabled def to_dict(self, include_extra_dict=False): d = super(User, self).to_dict(include_extra_dict=include_extra_dict) if 'default_project_id' in d and d['default_project_id'] is None: del d['default_project_id'] return d
class LimitModel(sql.ModelBase, sql.ModelDictMixin): __tablename__ = 'limit' attributes = [ 'internal_id', 'id', 'project_id', 'service_id', 'region_id', 'resource_name', 'resource_limit', 'description', 'registered_limit_id' ] # TODO(wxy): Drop "service_id", "region_id" and "resource_name" columns # in T release. internal_id = sql.Column(sql.Integer, primary_key=True, nullable=False) id = sql.Column(sql.String(length=64), nullable=False, unique=True) project_id = sql.Column(sql.String(64)) _service_id = sql.Column('service_id', sql.String(255)) _region_id = sql.Column('region_id', sql.String(64), nullable=True) _resource_name = sql.Column('resource_name', sql.String(255)) resource_limit = sql.Column(sql.Integer, nullable=False) description = sql.Column(sql.Text()) registered_limit_id = sql.Column(sql.String(64), sql.ForeignKey('registered_limit.id')) registered_limit = sqlalchemy.orm.relationship('RegisteredLimitModel') @hybrid_property def service_id(self): if self.registered_limit: return self.registered_limit.service_id return self._service_id @service_id.setter def service_id(self, value): self._service_id = value @service_id.expression def service_id(self): return LimitModel._service_id @hybrid_property def region_id(self): if self.registered_limit: return self.registered_limit.region_id return self._region_id @region_id.setter def region_id(self, value): self._region_id = value @region_id.expression def region_id(self): return LimitModel._region_id @hybrid_property def resource_name(self): if self.registered_limit: return self.registered_limit.resource_name return self._resource_name @resource_name.setter def resource_name(self, value): self._resource_name = value @resource_name.expression def resource_name(self): return LimitModel._resource_name @classmethod def from_dict(cls, limit): obj = super(LimitModel, cls).from_dict(limit) with sql.session_for_read() as session: query = session.query(RegisteredLimitModel).filter_by( id=obj.registered_limit_id) obj.registered_limit = query.first() return obj def to_dict(self): ref = super(LimitModel, self).to_dict() ref.pop('internal_id') ref.pop('registered_limit_id') return ref
class Project(sql.ModelBase, sql.ModelDictMixinWithExtras): # NOTE(henry-nash): From the manager and above perspective, the domain_id # is nullable. However, to ensure uniqueness in multi-process # configurations, it is better to still use the sql uniqueness constraint. # Since the support for a nullable component of a uniqueness constraint # across different sql databases is mixed, we instead store a special value # to represent null, as defined in NULL_DOMAIN_ID above. def to_dict(self, include_extra_dict=False): d = super(Project, self).to_dict(include_extra_dict=include_extra_dict) if d['domain_id'] == base.NULL_DOMAIN_ID: d['domain_id'] = None return d __tablename__ = 'project' attributes = [ 'id', 'name', 'domain_id', 'description', 'enabled', 'parent_id', 'is_domain', 'tags' ] id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), nullable=False) domain_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'), nullable=False) description = sql.Column(sql.Text()) enabled = sql.Column(sql.Boolean) extra = sql.Column(sql.JsonBlob()) parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id')) is_domain = sql.Column(sql.Boolean, default=False, nullable=False, server_default='0') _tags = orm.relationship( 'ProjectTag', single_parent=True, lazy='subquery', cascade='all,delete-orphan', backref='project', primaryjoin='and_(ProjectTag.project_id==Project.id)') # Unique constraint across two columns to create the separation # rather than just only 'name' being unique __table_args__ = (sql.UniqueConstraint('domain_id', 'name'), ) @hybrid_property def tags(self): if self._tags: return [tag.name for tag in self._tags] return [] @tags.setter def tags(self, values): new_tags = [] for tag in values: tag_ref = ProjectTag() tag_ref.project_id = self.id tag_ref.name = tag new_tags.append(tag_ref) self._tags = new_tags @tags.expression def tags(cls): return ProjectTag.name