-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathUserManager.py
More file actions
285 lines (210 loc) · 9.25 KB
/
UserManager.py
File metadata and controls
285 lines (210 loc) · 9.25 KB
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
"""The abstract UserManager class."""
from MiscUtils import AbstractError, NoDefault
from .User import User
class UserManager:
"""The base class for all user manager classes.
A UserManager manages a set of users including authentication,
indexing and persistence. Keep in mind that UserManager is abstract;
you must always use a concrete subclasses like UserManagerToFile
(but please read the rest of this docstring).
You can create a user through the manager (preferred)::
user = manager.createUser(name, password)
Or directly through the user class::
user = RoleUser(manager, name, password)
manager.addUser(user)
The manager tracks users by whether or not they are "active"
(e.g., logged in) and indexes them by:
* user serial number
* external user id
* user name
These methods provide access to the users by these keys::
def userForSerialNum(self, serialNum, default=NoDefault)
def userForExternalId(self, extId, default=NoDefault)
def userForName(self, name, default=NoDefault)
UserManager provides convenient methods for iterating through the
various users. Each method returns an object that can be used in a
for loop and asked for its len()::
def users(self)
def activeUsers(self)
def inactiveUsers(self)
You can authenticate a user by passing the user object and attempted
password to login(). If the authentication is successful, then login()
returns the User, otherwise it returns None::
user = mgr.userForExternalId(externalId)
if mgr.login(user, password):
self.doSomething()
As a convenience, you can authenticate by passing the serialNum,
externalId or name of the user::
def loginSerialNum(self, serialNum, password):
def loginExternalId(self, externalId, password):
def loginName(self, userName, password):
The user will automatically log out after a period of inactivity
(see below), or you can make it happen with::
def logout(self, user):
There are three user states that are important to the manager:
* modified
* cached
* authenticated or "active"
A modified user is one whose data has changed and eventually requires
storage to a persistent location. A cached user is a user whose data
resides in memory (regardless of the other states). An active user
has been authenticated (e.g., their username and password were checked)
and has not yet logged out or timed out.
The manager keeps three timeouts, expressed in minutes, to:
* save modified users after a period of time following the first
unsaved modification
* push users out of memory after a period of inactivity
* deactivate (e.g., log out) users after a period of inactivity
The methods for managing these values deal with the timeouts as
number-of-minutes. The default values and the methods are:
* 20 modifiedUserTimeout() setModifiedUserTimeout()
* 20 cachedUserTimeout() setCachedUserTimeout()
* 20 activeUserTimeout() setActiveUserTimeout()
Subclasses of UserManager provide persistence such as to the file
system or a MiddleKit store. Subclasses must implement all methods
that raise AbstractErrors. Subclasses typically override (while still
invoking super) addUser().
Subclasses should ensure "uniqueness" of users. For example, invoking
any of the userForSomething() methods repeatedly should always return
the same user instance for a given key. Without uniqueness, consistency
issues could arise with users that are modified.
Please read the method docstrings and other class documentation to
fully understand UserKit.
"""
# region Init
def __init__(self, userClass=None):
if userClass is None:
self._userClass = None
else:
self.setUserClass(userClass)
self._cachedUsers = []
self._cachedUsersBySerialNum = {}
self.setModifiedUserTimeout(20)
self.setCachedUserTimeout(20)
self.setActiveUserTimeout(20)
self._numActive = 0
def shutDown(self):
"""Perform any tasks necessary to shut down the user manager.
Subclasses may override and must invoke super as their *last* step.
"""
# base method does nothing
# endregion Init
# region Settings
def userClass(self):
"""Return the userClass, which is used by createUser.
The default value is UserKit.User.User.
"""
if self._userClass is None:
self.setUserClass(User)
return self._userClass
def setUserClass(self, userClass):
"""Set the userClass, which cannot be None and must inherit from User.
See also: userClass().
"""
if not issubclass(userClass, User):
raise TypeError(f'{userClass} is not a User class')
self._userClass = userClass
def modifiedUserTimeout(self):
return self._modifiedUserTimeout
def setModifiedUserTimeout(self, value):
self._modifiedUserTimeout = value
def cachedUserTimeout(self):
return self._cachedUserTimeout
def setCachedUserTimeout(self, value):
self._cachedUserTimeout = value
def activeUserTimeout(self):
return self._activeUserTimeout
def setActiveUserTimeout(self, value):
self._activeUserTimeout = value
# endregion Settings
# region Basic user access
def createUser(self, name, password, userClass=None):
"""Return a newly created user that is added to the manager.
If userClass is not specified, the manager's default user class
is instantiated. This not imply that the user is logged in.
This method invokes self.addUser().
See also: userClass(), setUserClass()
"""
if userClass is None:
userClass = self.userClass()
user = userClass(manager=self, name=name, password=password)
self.addUser(user)
return user
def addUser(self, user):
if not isinstance(user, User):
raise TypeError(f'{user} is not a User object')
self._cachedUsers.append(user)
if user.serialNum() in self._cachedUsersBySerialNum:
raise KeyError('a user with this number already exists')
self._cachedUsersBySerialNum[user.serialNum()] = user
def userForSerialNum(self, serialNum, default=NoDefault):
"""Return the user with the given serialNum.
The user record is pulled into memory if needed.
"""
raise AbstractError(self.__class__)
def userForExternalId(self, externalId, default=NoDefault):
"""Return the user with the given external id.
The user record is pulled into memory if needed.
"""
raise AbstractError(self.__class__)
def userForName(self, name, default=NoDefault):
"""Return the user with the given name.
The user record is pulled into memory if needed.
"""
raise AbstractError(self.__class__)
def users(self):
"""Return a list of all users (regardless of login status)."""
raise AbstractError(self.__class__)
def numActiveUsers(self):
"""Return the number of active users (e.g. the logged in users)."""
return self._numActive
def activeUsers(self):
"""Return a list of all active users."""
raise AbstractError(self.__class__)
def inactiveUsers(self):
raise AbstractError(self.__class__)
# endregion Basic user access
# region Logging in and out
def login(self, user, password):
"""Return the user if login is successful, otherwise return None."""
if not isinstance(user, User):
raise TypeError(f'{user} is not a User object')
result = user.login(password, fromMgr=True)
if result:
self._numActive += 1
return result
def logout(self, user):
if not isinstance(user, User):
raise TypeError(f'{user} is not a User object')
user.logout(fromMgr=True)
self._numActive -= 1
def loginSerialNum(self, serialNum, password):
# pylint: disable=assignment-from-no-return
user = self.userForSerialNum(serialNum, None)
if user:
return self.login(user, password)
return None
def loginExternalId(self, externalId, password):
# pylint: disable=assignment-from-no-return
user = self.userForExternalId(externalId, None)
if user:
return self.login(user, password)
return None
def loginName(self, userName, password):
# pylint: disable=assignment-from-no-return
user = self.userForName(userName, None)
if user:
return self.login(user, password)
return None
# endregion Logging in and out
# region Cached
def clearCache(self):
"""Clear the cache of the manager.
Use with extreme caution. If your program maintains a reference
to a user object, but the manager loads in a new copy later on,
then consistency problems could occur.
The most popular use of this method is in the regression test suite.
"""
self._cachedUsers = []
self._cachedUsersBySerialNum = {}
# endregion Cached