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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
"""
Copyright 2019-2020 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.
"""
import datetime
from sqlalchemy import (
Boolean,
DateTime,
Column,
Integer,
String,
UnicodeText,
ForeignKey,
)
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from flask_security import RoleMixin, UserMixin
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"
@classmethod
def set_db_info(cls, appdb, user_table_name="user", role_table_name="role"):
"""Initialize Model.
This needs to be called after the DB object has been created
(e.g. db = Sqlalchemy())
"""
cls.db = appdb
cls.user_table_name = user_table_name
cls.role_table_name = role_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(UnicodeText, nullable=True)
update_datetime = Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=datetime.datetime.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 = 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"),
)
create_datetime = Column(DateTime, nullable=False, server_default=func.now())
update_datetime = Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=datetime.datetime.utcnow,
)
"""
These are placeholders - not current used
"""
class FsOauth2ClientMixin:
""" Oauth2 client """
id = Column(String(64), primary_key=True)
@declared_attr
def user_id(cls):
return Column(
Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
@declared_attr
def user(cls):
return relationship("User")
grant_type = Column(String(32), nullable=False)
scopes = Column(UnicodeText(), default="")
response_type = Column(UnicodeText, nullable=False, default="")
redirect_uris = Column(UnicodeText())
class FsTokenMixin:
""" (Bearer) Tokens that have been given out """
id = Column(Integer, primary_key=True)
@declared_attr
def client_id(cls):
return Column(
Integer, ForeignKey("oauth2_client.id", ondelete="CASCADE"), nullable=False
)
# client = relationship("fs_oauth2_client")
@declared_attr
def user_id(cls):
return Column(
Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
scopes = Column(UnicodeText(), default="")
revoked = Column(Boolean(), nullable=False, default=False)
access_token = Column(String(100), unique=True, nullable=False)
refresh_token = Column(String(100), unique=True)
issued_at = Column(DateTime, nullable=False, server_default=func.now())
expires_at = Column(DateTime())
|