Coverage for .tox/coverage/lib/python3.11/site-packages/wuttjamaican/db/model/auth.py: 100%
50 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-08-27 21:08 -0500
« prev ^ index » next coverage.py v7.3.2, created at 2024-08-27 21:08 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttJamaican -- Base package for Wutta Framework
5# Copyright © 2023-2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Auth Models
26The :term:`auth handler` is primarily responsible for managing the
27data for these models.
29Basic design/structure is as follows:
31* :class:`User` may be assigned to multiple roles
32* :class:`Role` may contain multiple users (cf. :class:`UserRole`)
33* :class:`Role` may be granted multiple permissions
34* :class:`Permission` is a permission granted to a role
35* roles are not nested/grouped; each is independent
36* a few roles are built-in, e.g. Administrators
38So a user's permissions are "inherited" from the role(s) to which they
39belong.
40"""
42import sqlalchemy as sa
43from sqlalchemy import orm
44from sqlalchemy.ext.associationproxy import association_proxy
46from .base import Base, uuid_column, uuid_fk_column
49class Role(Base):
50 """
51 Represents an authentication role within the system; used for
52 permission management.
54 .. attribute:: permissions
56 List of keys (string names) for permissions granted to this
57 role.
59 See also :attr:`permission_refs`.
61 .. attribute:: users
63 List of :class:`User` instances belonging to this role.
65 See also :attr:`user_refs`.
66 """
67 __tablename__ = 'role'
68 __versioned__ = {}
70 uuid = uuid_column()
72 name = sa.Column(sa.String(length=100), nullable=False, unique=True, doc="""
73 Name for the role. Each role must have a name, which must be
74 unique.
75 """)
77 notes = sa.Column(sa.Text(), nullable=True, doc="""
78 Arbitrary notes for the role.
79 """)
81 permission_refs = orm.relationship(
82 'Permission',
83 back_populates='role',
84 cascade='all, delete-orphan',
85 cascade_backrefs=False,
86 doc="""
87 List of :class:`Permission` references for the role.
89 See also :attr:`permissions`.
90 """)
92 permissions = association_proxy(
93 'permission_refs', 'permission',
94 creator=lambda p: Permission(permission=p),
95 # TODO
96 # getset_factory=getset_factory,
97 )
99 user_refs = orm.relationship(
100 'UserRole',
101 back_populates='role',
102 cascade='all, delete-orphan',
103 cascade_backrefs=False,
104 doc="""
105 List of :class:`UserRole` instances belonging to the role.
107 See also :attr:`users`.
108 """)
110 users = association_proxy(
111 'user_refs', 'user',
112 creator=lambda u: UserRole(user=u),
113 # TODO
114 # getset_factory=getset_factory,
115 )
117 def __str__(self):
118 return self.name or ""
121class Permission(Base):
122 """
123 Represents a permission granted to a role.
124 """
125 __tablename__ = 'permission'
126 __versioned__ = {}
128 role_uuid = uuid_fk_column('role.uuid', primary_key=True, nullable=False)
129 role = orm.relationship(
130 Role,
131 back_populates='permission_refs',
132 cascade_backrefs=False,
133 doc="""
134 Reference to the :class:`Role` for which the permission is
135 granted.
136 """)
138 permission = sa.Column(sa.String(length=254), primary_key=True, doc="""
139 Key (name) of the permission which is granted.
140 """)
142 def __str__(self):
143 return self.permission or ""
146class User(Base):
147 """
148 Represents a user of the system.
150 This may or may not correspond to a real person, i.e. some users
151 may exist solely for automated tasks.
153 .. attribute:: roles
155 List of :class:`Role` instances to which the user belongs.
157 See also :attr:`role_refs`.
158 """
159 __tablename__ = 'user'
160 __versioned__ = {}
162 uuid = uuid_column()
164 username = sa.Column(sa.String(length=25), nullable=False, unique=True, doc="""
165 Account username. This is required and must be unique.
166 """)
168 password = sa.Column(sa.String(length=60), nullable=True, doc="""
169 Hashed password for login. (The raw password is not stored.)
170 """)
172 person_uuid = uuid_fk_column('person.uuid', nullable=True)
173 person = orm.relationship(
174 'Person',
175 # TODO: seems like this is not needed?
176 # uselist=False,
177 back_populates='users',
178 cascade_backrefs=False,
179 doc="""
180 Reference to the :class:`~wuttjamaican.db.model.base.Person`
181 whose user account this is.
182 """)
184 active = sa.Column(sa.Boolean(), nullable=False, default=True, doc="""
185 Flag indicating whether the user account is "active" - it is
186 ``True`` by default.
188 The default auth logic will prevent login for "inactive" user accounts.
189 """)
191 role_refs = orm.relationship(
192 'UserRole',
193 back_populates='user',
194 cascade='all, delete-orphan',
195 cascade_backrefs=False,
196 doc="""
197 List of :class:`UserRole` instances belonging to the user.
199 See also :attr:`roles`.
200 """)
202 roles = association_proxy(
203 'role_refs', 'role',
204 creator=lambda r: UserRole(role=r),
205 # TODO
206 # getset_factory=getset_factory,
207 )
209 def __str__(self):
210 if self.person:
211 name = str(self.person)
212 if name:
213 return name
214 return self.username or ""
217class UserRole(Base):
218 """
219 Represents the association between a user and a role; i.e. the
220 user "belongs" or "is assigned" to the role.
221 """
222 __tablename__ = 'user_x_role'
223 __versioned__ = {}
225 uuid = uuid_column()
227 user_uuid = uuid_fk_column('user.uuid', nullable=False)
228 user = orm.relationship(
229 User,
230 back_populates='role_refs',
231 cascade_backrefs=False,
232 doc="""
233 Reference to the :class:`User` involved.
234 """)
236 role_uuid = uuid_fk_column('role.uuid', nullable=False)
237 role = orm.relationship(
238 Role,
239 back_populates='user_refs',
240 cascade_backrefs=False,
241 doc="""
242 Reference to the :class:`Role` involved.
243 """)