from __future__ import annotations import enum from typing import Dict from typing import Optional from typing import Sequence from flask_security import RoleMixin from flask_security import SQLAlchemyUserDatastore from flask_security import UserMixin from werkzeug.security import check_password_hash from werkzeug.security import generate_password_hash from ..database import BaseModel from ..database import db roles_users = db.Table( "roles_users", db.Column("user_id", db.Integer(), db.ForeignKey("user.id")), db.Column("role_id", db.Integer(), db.ForeignKey("role.id")), ) class RoleType(enum.Enum): """The type of a role""" admin: int = 1 ROLE_DESCRIPTIONS: Dict[RoleType, str] = { RoleType.admin: "Has admin rights", } class Role(BaseModel, RoleMixin): id: int # type: ignore """The DB id""" name: str = db.Column(db.String(80), unique=True) """The name of the role, see :class:`RoleType`""" description: str = db.Column(db.String(255)) """A short description of the role""" def __init__( self, name: str, description: str, ): """ :param name: The name of the role, see :class:`RoleType` :param description: A short description of the role """ self.name = name self.description = description @classmethod def get_role( cls, role_type: RoleType, ) -> Role: """Fetches the role from the DB or creates it if necessary :param role_type: The type of the role :return: The role instance """ role_: Optional[Role] = cls.query.filter_by(name=role_type.name).first() if role_ is None: role_ = cls( name=role_type.name, description=ROLE_DESCRIPTIONS[role_type], ) role_.save() return role_ class User(BaseModel, UserMixin): id: int # type: ignore """The DB id""" email: str = db.Column(db.String(255), unique=True) """The email address""" password: str = db.Column(db.String(512)) """The password as hash""" active: bool = db.Column(db.Boolean()) """Is the user activated?""" roles: Sequence[Role] = db.relationship( "Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic"), ) """The roles which the user has""" def __init__( self, email: str, password: str, active: bool = True, roles: Sequence[Role] = None, ): """ :param email: The email address :param password: The password as plain text :param active: *True* if the user is active :param roles: The Roles of the user. """ self.email = email self.set_password(password=password) self.active = active self.roles = roles or [Role.get_role(RoleType.admin)] def set_password( self, password: str, ): """Create hashed password.""" self.password = generate_password_hash( password=password, ) def check_password( self, password: str, ) -> bool: """Check hashed password :param password: The password in plain text :return: True if equal """ return bool( check_password_hash( pwhash=self.password, password=password, ), ) def __repr__(self): return ( f"<{self.__class__.__name__} " f"email={self.email!r}, " f"active={self.active!r}, " f"roles={self.roles!r}>" ) user_datastore = SQLAlchemyUserDatastore(db, User, Role)