File: fsqla.py

package info (click to toggle)
flask-security 5.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,448 kB
  • sloc: python: 23,247; javascript: 204; makefile: 138
file content (137 lines) | stat: -rw-r--r-- 4,131 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""
Copyright 2019-2024 by J. Christopher Wagner (jwag). All rights reserved.
:license: MIT, see LICENSE for more details.


Complete models for all features when using Flask-SqlAlchemy.

You can change the table names by passing them in to the set_db_info() method.

BE AWARE: Once any version of this is shipped no changes can be made - instead
a new version needs to be created.
"""

# mypy: disable-error-code="assignment"
# pyright: reportAssignmentType = false, reportIncompatibleVariableOverride=false

from typing import cast
from sqlalchemy import (
    Boolean,
    DateTime,
    Column,
    Integer,
    String,
    ForeignKey,
)
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.mutable import MutableList
from sqlalchemy.sql import func

from flask_security import AsaList, RoleMixin, UserMixin, naive_utcnow


class FsModels:
    """
    Helper class for model mixins.
    This records the ``db`` (which is a Flask-SqlAlchemy object) for use in
    mixins.
    """

    roles_users = None
    db = None
    fs_model_version = 1
    user_table_name = "user"
    role_table_name = "role"
    webauthn_table_name = "webauthn"

    @classmethod
    def set_db_info(
        cls,
        appdb,
        user_table_name="user",
        role_table_name="role",
        webauthn_table_name="webauthn",
    ):
        """Initialize Model.
        This needs to be called after the DB object has been created
        (e.g. db = Sqlalchemy()).

        .. note::
            This should only be used if you are utilizing the fsqla data
            models. With your own models you would need similar but slightly
            different code.
        """
        cls.db = appdb
        cls.user_table_name = user_table_name
        cls.role_table_name = role_table_name
        cls.webauthn_table_name = webauthn_table_name
        cls.roles_users = appdb.Table(
            "roles_users",
            Column("user_id", Integer(), ForeignKey(f"{cls.user_table_name}.id")),
            Column("role_id", Integer(), ForeignKey(f"{cls.role_table_name}.id")),
        )


class FsRoleMixin(RoleMixin):
    id = Column(Integer(), primary_key=True)
    name = Column(String(80), unique=True, nullable=False)
    description = Column(String(255))
    # A comma separated list of strings
    permissions = Column(
        MutableList.as_mutable(AsaList()), nullable=True  # type: ignore
    )
    update_datetime = Column(
        type_=DateTime,
        nullable=False,
        server_default=func.now(),
        onupdate=naive_utcnow,
    )


class FsUserMixin(UserMixin):
    """User information"""

    # flask_security basic fields
    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False)
    # Username is important since shouldn't expose email to other users in most cases.
    username = Column(String(255))
    password = Column(String(255), nullable=False)
    active = cast(bool, Column(Boolean(), nullable=False))

    # Flask-Security user identifier
    fs_uniquifier = Column(String(64), unique=True, nullable=False)

    # confirmable
    confirmed_at = Column(DateTime())

    # trackable
    last_login_at = Column(DateTime())
    current_login_at = Column(DateTime())
    last_login_ip = Column(String(64))
    current_login_ip = Column(String(64))
    login_count = Column(Integer)

    # 2FA
    tf_primary_method = Column(String(64), nullable=True)
    tf_totp_secret = Column(String(255), nullable=True)
    tf_phone_number = Column(String(128), nullable=True)

    @declared_attr
    def roles(cls):
        # The first arg is a class name, the backref is a column name
        return FsModels.db.relationship(
            "Role",
            secondary=FsModels.roles_users,
            backref=FsModels.db.backref(
                "users", lazy="dynamic", cascade_backrefs=False
            ),
        )

    create_datetime = Column(type_=DateTime, nullable=False, server_default=func.now())
    update_datetime = Column(
        type_=DateTime,
        nullable=False,
        server_default=func.now(),
        onupdate=naive_utcnow,
    )