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

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 

25 

26The :term:`auth handler` is primarily responsible for managing the 

27data for these models. 

28 

29Basic design/structure is as follows: 

30 

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 

37 

38So a user's permissions are "inherited" from the role(s) to which they 

39belong. 

40""" 

41 

42import sqlalchemy as sa 

43from sqlalchemy import orm 

44from sqlalchemy.ext.associationproxy import association_proxy 

45 

46from .base import Base, uuid_column, uuid_fk_column 

47 

48 

49class Role(Base): 

50 """ 

51 Represents an authentication role within the system; used for 

52 permission management. 

53 

54 .. attribute:: permissions 

55 

56 List of keys (string names) for permissions granted to this 

57 role. 

58 

59 See also :attr:`permission_refs`. 

60 

61 .. attribute:: users 

62 

63 List of :class:`User` instances belonging to this role. 

64 

65 See also :attr:`user_refs`. 

66 """ 

67 __tablename__ = 'role' 

68 __versioned__ = {} 

69 

70 uuid = uuid_column() 

71 

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 """) 

76 

77 notes = sa.Column(sa.Text(), nullable=True, doc=""" 

78 Arbitrary notes for the role. 

79 """) 

80 

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. 

88 

89 See also :attr:`permissions`. 

90 """) 

91 

92 permissions = association_proxy( 

93 'permission_refs', 'permission', 

94 creator=lambda p: Permission(permission=p), 

95 # TODO 

96 # getset_factory=getset_factory, 

97 ) 

98 

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. 

106 

107 See also :attr:`users`. 

108 """) 

109 

110 users = association_proxy( 

111 'user_refs', 'user', 

112 creator=lambda u: UserRole(user=u), 

113 # TODO 

114 # getset_factory=getset_factory, 

115 ) 

116 

117 def __str__(self): 

118 return self.name or "" 

119 

120 

121class Permission(Base): 

122 """ 

123 Represents a permission granted to a role. 

124 """ 

125 __tablename__ = 'permission' 

126 __versioned__ = {} 

127 

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 """) 

137 

138 permission = sa.Column(sa.String(length=254), primary_key=True, doc=""" 

139 Key (name) of the permission which is granted. 

140 """) 

141 

142 def __str__(self): 

143 return self.permission or "" 

144 

145 

146class User(Base): 

147 """ 

148 Represents a user of the system. 

149 

150 This may or may not correspond to a real person, i.e. some users 

151 may exist solely for automated tasks. 

152 

153 .. attribute:: roles 

154 

155 List of :class:`Role` instances to which the user belongs. 

156 

157 See also :attr:`role_refs`. 

158 """ 

159 __tablename__ = 'user' 

160 __versioned__ = {} 

161 

162 uuid = uuid_column() 

163 

164 username = sa.Column(sa.String(length=25), nullable=False, unique=True, doc=""" 

165 Account username. This is required and must be unique. 

166 """) 

167 

168 password = sa.Column(sa.String(length=60), nullable=True, doc=""" 

169 Hashed password for login. (The raw password is not stored.) 

170 """) 

171 

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 """) 

183 

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. 

187 

188 The default auth logic will prevent login for "inactive" user accounts. 

189 """) 

190 

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. 

198 

199 See also :attr:`roles`. 

200 """) 

201 

202 roles = association_proxy( 

203 'role_refs', 'role', 

204 creator=lambda r: UserRole(role=r), 

205 # TODO 

206 # getset_factory=getset_factory, 

207 ) 

208 

209 def __str__(self): 

210 if self.person: 

211 name = str(self.person) 

212 if name: 

213 return name 

214 return self.username or "" 

215 

216 

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__ = {} 

224 

225 uuid = uuid_column() 

226 

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 """) 

235 

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 """)