sanic-security 1.11.7__py3-none-any.whl → 1.16.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sanic_security/authentication.py +192 -203
- sanic_security/authorization.py +110 -64
- sanic_security/configuration.py +42 -25
- sanic_security/exceptions.py +58 -24
- sanic_security/models.py +287 -161
- sanic_security/oauth.py +238 -0
- sanic_security/test/server.py +174 -112
- sanic_security/test/tests.py +137 -103
- sanic_security/utils.py +67 -28
- sanic_security/verification.py +59 -46
- sanic_security-1.16.7.dist-info/LICENSE +21 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.16.7.dist-info}/METADATA +685 -591
- sanic_security-1.16.7.dist-info/RECORD +17 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.16.7.dist-info}/WHEEL +2 -1
- sanic_security-1.16.7.dist-info/top_level.txt +1 -0
- sanic_security-1.11.7.dist-info/LICENSE +0 -661
- sanic_security-1.11.7.dist-info/RECORD +0 -15
sanic_security/authorization.py
CHANGED
@@ -1,31 +1,34 @@
|
|
1
1
|
import functools
|
2
|
-
import logging
|
3
|
-
from fnmatch import fnmatch
|
4
2
|
|
3
|
+
from sanic.log import logger
|
5
4
|
from sanic.request import Request
|
6
5
|
from tortoise.exceptions import DoesNotExist
|
7
6
|
|
8
7
|
from sanic_security.authentication import authenticate
|
9
|
-
from sanic_security.exceptions import AuthorizationError
|
8
|
+
from sanic_security.exceptions import AuthorizationError, AnonymousError
|
10
9
|
from sanic_security.models import Role, Account, AuthenticationSession
|
11
10
|
from sanic_security.utils import get_ip
|
12
11
|
|
13
12
|
"""
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
13
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
14
|
+
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
17
|
+
in the Software without restriction, including without limitation the rights
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
20
|
+
furnished to do so, subject to the following conditions:
|
21
|
+
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
23
|
+
copies or substantial portions of the Software.
|
24
|
+
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
31
|
+
SOFTWARE.
|
29
32
|
"""
|
30
33
|
|
31
34
|
|
@@ -51,19 +54,48 @@ async def check_permissions(
|
|
51
54
|
UnverifiedError
|
52
55
|
DisabledError
|
53
56
|
AuthorizationError
|
57
|
+
AnonymousError
|
54
58
|
"""
|
55
59
|
authentication_session = await authenticate(request)
|
60
|
+
if authentication_session.anonymous:
|
61
|
+
logger.warning(
|
62
|
+
f"Client {get_ip(request)} attempted an unauthorized action anonymously."
|
63
|
+
)
|
64
|
+
raise AnonymousError
|
56
65
|
roles = await authentication_session.bearer.roles.filter(deleted=False).all()
|
57
66
|
for role in roles:
|
58
|
-
for
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
67
|
+
for role_permission in role.permissions:
|
68
|
+
for required_permission in required_permissions:
|
69
|
+
if check_wildcard(role_permission, required_permission):
|
70
|
+
return authentication_session
|
71
|
+
logger.warning(
|
72
|
+
f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action."
|
73
|
+
)
|
64
74
|
raise AuthorizationError("Insufficient permissions required for this action.")
|
65
75
|
|
66
76
|
|
77
|
+
def check_wildcard(wildcard: str, pattern: str):
|
78
|
+
"""
|
79
|
+
Evaluates if the wildcard matches the pattern.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
wildcard (str): A wildcard string (e.g., "a:b:c").
|
83
|
+
pattern (str): A wildcard pattern optional (`*`) or comma-separated values to match against (e.g., "a:b,c:*").
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
is_match
|
87
|
+
"""
|
88
|
+
wildcard_parts = [set(part.split(",")) for part in wildcard.split(":")]
|
89
|
+
pattern_parts = [set(part.split(",")) for part in pattern.split(":")]
|
90
|
+
for i, pattern_part in enumerate(pattern_parts):
|
91
|
+
if i >= len(wildcard_parts):
|
92
|
+
return False
|
93
|
+
wildcard_part = wildcard_parts[i]
|
94
|
+
if "*" not in wildcard_part and not wildcard_part.issuperset(pattern_part):
|
95
|
+
return False
|
96
|
+
return all("*" in part for part in wildcard_parts[len(pattern_parts) :])
|
97
|
+
|
98
|
+
|
67
99
|
async def check_roles(request: Request, *required_roles: str) -> AuthenticationSession:
|
68
100
|
"""
|
69
101
|
Authenticates client and determines if the account has sufficient roles for an action.
|
@@ -84,17 +116,55 @@ async def check_roles(request: Request, *required_roles: str) -> AuthenticationS
|
|
84
116
|
UnverifiedError
|
85
117
|
DisabledError
|
86
118
|
AuthorizationError
|
119
|
+
AnonymousError
|
87
120
|
"""
|
88
121
|
authentication_session = await authenticate(request)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
122
|
+
if authentication_session.anonymous:
|
123
|
+
logger.warning(
|
124
|
+
f"Client {get_ip(request)} attempted an unauthorized action anonymously."
|
125
|
+
)
|
126
|
+
raise AnonymousError
|
127
|
+
if set(required_roles) & {
|
128
|
+
role.name
|
129
|
+
for role in await authentication_session.bearer.roles.filter(
|
130
|
+
deleted=False
|
131
|
+
).all()
|
132
|
+
}:
|
133
|
+
return authentication_session
|
134
|
+
logger.warning(
|
135
|
+
f"Client {get_ip(request)} with account {authentication_session.bearer.id} attempted an unauthorized action"
|
136
|
+
)
|
137
|
+
raise AuthorizationError("Insufficient roles required for this action")
|
95
138
|
|
96
139
|
|
97
|
-
def
|
140
|
+
async def assign_role(
|
141
|
+
name: str,
|
142
|
+
account: Account,
|
143
|
+
description: str,
|
144
|
+
*permissions: str,
|
145
|
+
) -> Role:
|
146
|
+
"""
|
147
|
+
Easy account role assignment, role being assigned to an account will be created if it doesn't exist.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
name (str): The name of the role associated with the account.
|
151
|
+
account (Account): The account associated with the created role.
|
152
|
+
description (str): The description of the role associated with the account.
|
153
|
+
*permissions (Tuple[str, ...]): The permissions of the role associated with the account, must be in wildcard format.
|
154
|
+
"""
|
155
|
+
try:
|
156
|
+
role = await Role.filter(name=name).get()
|
157
|
+
except DoesNotExist:
|
158
|
+
role = await Role.create(
|
159
|
+
name=name,
|
160
|
+
description=description,
|
161
|
+
permissions=permissions,
|
162
|
+
)
|
163
|
+
await account.roles.add(role)
|
164
|
+
return role
|
165
|
+
|
166
|
+
|
167
|
+
def requires_permission(*required_permissions: str):
|
98
168
|
"""
|
99
169
|
Authenticates client and determines if the account has sufficient permissions for an action.
|
100
170
|
|
@@ -105,8 +175,8 @@ def require_permissions(*required_permissions: str):
|
|
105
175
|
This method is not called directly and instead used as a decorator:
|
106
176
|
|
107
177
|
@app.post("api/auth/perms")
|
108
|
-
@
|
109
|
-
async def
|
178
|
+
@requires_permission("admin:update", "employee:add")
|
179
|
+
async def on_authorize(request):
|
110
180
|
return text("Account permitted.")
|
111
181
|
|
112
182
|
Raises:
|
@@ -118,14 +188,13 @@ def require_permissions(*required_permissions: str):
|
|
118
188
|
UnverifiedError
|
119
189
|
DisabledError
|
120
190
|
AuthorizationError
|
191
|
+
AnonymousError
|
121
192
|
"""
|
122
193
|
|
123
194
|
def decorator(func):
|
124
195
|
@functools.wraps(func)
|
125
196
|
async def wrapper(request, *args, **kwargs):
|
126
|
-
|
127
|
-
request, *required_permissions
|
128
|
-
)
|
197
|
+
await check_permissions(request, *required_permissions)
|
129
198
|
return await func(request, *args, **kwargs)
|
130
199
|
|
131
200
|
return wrapper
|
@@ -133,7 +202,7 @@ def require_permissions(*required_permissions: str):
|
|
133
202
|
return decorator
|
134
203
|
|
135
204
|
|
136
|
-
def
|
205
|
+
def requires_role(*required_roles: str):
|
137
206
|
"""
|
138
207
|
Authenticates client and determines if the account has sufficient roles for an action.
|
139
208
|
|
@@ -144,8 +213,8 @@ def require_roles(*required_roles: str):
|
|
144
213
|
This method is not called directly and instead used as a decorator:
|
145
214
|
|
146
215
|
@app.post("api/auth/roles")
|
147
|
-
@
|
148
|
-
async def
|
216
|
+
@requires_role("Admin", "Moderator")
|
217
|
+
async def on_authorize(request):
|
149
218
|
return text("Account permitted")
|
150
219
|
|
151
220
|
Raises:
|
@@ -157,38 +226,15 @@ def require_roles(*required_roles: str):
|
|
157
226
|
UnverifiedError
|
158
227
|
DisabledError
|
159
228
|
AuthorizationError
|
229
|
+
AnonymousError
|
160
230
|
"""
|
161
231
|
|
162
232
|
def decorator(func):
|
163
233
|
@functools.wraps(func)
|
164
234
|
async def wrapper(request, *args, **kwargs):
|
165
|
-
|
166
|
-
request, *required_roles
|
167
|
-
)
|
235
|
+
await check_roles(request, *required_roles)
|
168
236
|
return await func(request, *args, **kwargs)
|
169
237
|
|
170
238
|
return wrapper
|
171
239
|
|
172
240
|
return decorator
|
173
|
-
|
174
|
-
|
175
|
-
async def assign_role(
|
176
|
-
name: str, account: Account, permissions: str = None, description: str = None
|
177
|
-
) -> Role:
|
178
|
-
"""
|
179
|
-
Easy account role assignment. Role being assigned to an account will be created if it doesn't exist.
|
180
|
-
|
181
|
-
Args:
|
182
|
-
name (str): The name of the role associated with the account.
|
183
|
-
account (Account): The account associated with the created role.
|
184
|
-
permissions (str): The permissions of the role associated with the account. Permissions must be separated via comma and in wildcard format.
|
185
|
-
description (str): The description of the role associated with the account.
|
186
|
-
"""
|
187
|
-
try:
|
188
|
-
role = await Role.filter(name=name).get()
|
189
|
-
except DoesNotExist:
|
190
|
-
role = await Role.create(
|
191
|
-
description=description, permissions=permissions, name=name
|
192
|
-
)
|
193
|
-
await account.roles.add(role)
|
194
|
-
return role
|
sanic_security/configuration.py
CHANGED
@@ -2,40 +2,47 @@ from os import environ
|
|
2
2
|
|
3
3
|
from sanic.utils import str_to_bool
|
4
4
|
|
5
|
-
|
6
5
|
"""
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
16
|
+
copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
SOFTWARE.
|
22
25
|
"""
|
23
26
|
|
24
|
-
|
25
27
|
DEFAULT_CONFIG = {
|
26
28
|
"SECRET": "This is a big secret. Shhhhh",
|
27
29
|
"PUBLIC_SECRET": None,
|
28
|
-
"
|
30
|
+
"OAUTH_CLIENT": None,
|
31
|
+
"OAUTH_SECRET": None,
|
32
|
+
"OAUTH_REDIRECT": None,
|
33
|
+
"SESSION_SAMESITE": "Strict",
|
29
34
|
"SESSION_SECURE": True,
|
30
35
|
"SESSION_HTTPONLY": True,
|
31
36
|
"SESSION_DOMAIN": None,
|
32
|
-
"SESSION_PREFIX": "
|
37
|
+
"SESSION_PREFIX": "tkn",
|
33
38
|
"SESSION_ENCODING_ALGORITHM": "HS256",
|
34
|
-
"MAX_CHALLENGE_ATTEMPTS":
|
35
|
-
"CAPTCHA_SESSION_EXPIRATION":
|
39
|
+
"MAX_CHALLENGE_ATTEMPTS": 3,
|
40
|
+
"CAPTCHA_SESSION_EXPIRATION": 180,
|
36
41
|
"CAPTCHA_FONT": "captcha-font.ttf",
|
37
|
-
"
|
38
|
-
"
|
42
|
+
"CAPTCHA_VOICE": "captcha-voice/",
|
43
|
+
"TWO_STEP_SESSION_EXPIRATION": 300,
|
44
|
+
"AUTHENTICATION_SESSION_EXPIRATION": 86400,
|
45
|
+
"AUTHENTICATION_REFRESH_EXPIRATION": 604800,
|
39
46
|
"ALLOW_LOGIN_WITH_USERNAME": False,
|
40
47
|
"INITIAL_ADMIN_EMAIL": "admin@example.com",
|
41
48
|
"INITIAL_ADMIN_PASSWORD": "admin123",
|
@@ -50,6 +57,9 @@ class Config(dict):
|
|
50
57
|
Attributes:
|
51
58
|
SECRET (str): The secret used by the hashing algorithm for generating and signing JWTs. This should be a string unique to your application. Keep it safe.
|
52
59
|
PUBLIC_SECRET (str): The secret used for verifying and decoding JWTs and can be publicly shared. This should be a string unique to your application.
|
60
|
+
OAUTH_CLIENT (str): The client ID provided by the OAuth provider, this is used to identify the application making the OAuth request.
|
61
|
+
OAUTH_SECRET (str): The client secret provided by the OAuth provider, this is used in conjunction with the client ID to authenticate the application.
|
62
|
+
OAUTH_REDIRECT (str): The redirect URI registered with the OAuth provider, This is the URI where the user will be redirected after a successful authentication.
|
53
63
|
SESSION_SAMESITE (str): The SameSite attribute of session cookies.
|
54
64
|
SESSION_SECURE (bool): The Secure attribute of session cookies.
|
55
65
|
SESSION_HTTPONLY (bool): The HttpOnly attribute of session cookies. HIGHLY recommended that you do not turn this off, unless you know what you are doing.
|
@@ -59,8 +69,10 @@ class Config(dict):
|
|
59
69
|
MAX_CHALLENGE_ATTEMPTS (str): The maximum amount of session challenge attempts allowed.
|
60
70
|
CAPTCHA_SESSION_EXPIRATION (int): The amount of seconds till captcha session expiration on creation. Setting to 0 will disable expiration.
|
61
71
|
CAPTCHA_FONT (str): The file path to the font being used for captcha generation.
|
62
|
-
|
63
|
-
|
72
|
+
CAPTCHA_VOICE (str): The directory of the voice library being used for audio captcha generation.
|
73
|
+
TWO_STEP_SESSION_EXPIRATION (int): The amount of seconds till two-step session expiration on creation. Setting to 0 will disable expiration.
|
74
|
+
AUTHENTICATION_SESSION_EXPIRATION (int): The amount of seconds till authentication session expiration on creation. Setting to 0 will disable expiration.
|
75
|
+
AUTHENTICATION_REFRESH_EXPIRATION (int): The amount of seconds till authentication session refresh expiration. Setting to 0 will disable refresh mechanism.
|
64
76
|
ALLOW_LOGIN_WITH_USERNAME (bool): Allows login via username and email.
|
65
77
|
INITIAL_ADMIN_EMAIL (str): Email used when creating the initial admin account.
|
66
78
|
INITIAL_ADMIN_PASSWORD (str): Password used when creating the initial admin account.
|
@@ -69,6 +81,9 @@ class Config(dict):
|
|
69
81
|
|
70
82
|
SECRET: str
|
71
83
|
PUBLIC_SECRET: str
|
84
|
+
OAUTH_CLIENT: str
|
85
|
+
OAUTH_SECRET: str
|
86
|
+
OAUTH_REDIRECT: str
|
72
87
|
SESSION_SAMESITE: str
|
73
88
|
SESSION_SECURE: bool
|
74
89
|
SESSION_HTTPONLY: bool
|
@@ -78,8 +93,10 @@ class Config(dict):
|
|
78
93
|
MAX_CHALLENGE_ATTEMPTS: int
|
79
94
|
CAPTCHA_SESSION_EXPIRATION: int
|
80
95
|
CAPTCHA_FONT: str
|
96
|
+
CAPTCHA_VOICE: str
|
81
97
|
TWO_STEP_SESSION_EXPIRATION: int
|
82
98
|
AUTHENTICATION_SESSION_EXPIRATION: int
|
99
|
+
AUTHENTICATION_REFRESH_EXPIRATION: int
|
83
100
|
ALLOW_LOGIN_WITH_USERNAME: bool
|
84
101
|
INITIAL_ADMIN_EMAIL: str
|
85
102
|
INITIAL_ADMIN_PASSWORD: str
|
sanic_security/exceptions.py
CHANGED
@@ -3,21 +3,25 @@ from sanic.exceptions import SanicException
|
|
3
3
|
from sanic_security.utils import json
|
4
4
|
|
5
5
|
"""
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
6
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
16
|
+
copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
SOFTWARE.
|
21
25
|
"""
|
22
26
|
|
23
27
|
|
@@ -53,7 +57,25 @@ class DeletedError(SecurityError):
|
|
53
57
|
"""
|
54
58
|
|
55
59
|
def __init__(self, message):
|
56
|
-
super().__init__(message,
|
60
|
+
super().__init__(message, 404)
|
61
|
+
|
62
|
+
|
63
|
+
class CredentialsError(SecurityError):
|
64
|
+
"""
|
65
|
+
Raised when credentials are invalid.
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __init__(self, message, code=400):
|
69
|
+
super().__init__(message, code)
|
70
|
+
|
71
|
+
|
72
|
+
class OAuthError(SecurityError):
|
73
|
+
"""
|
74
|
+
Raised when an error occurs during OAuth flow.
|
75
|
+
"""
|
76
|
+
|
77
|
+
def __init__(self, message, code=401):
|
78
|
+
super().__init__(message, code)
|
57
79
|
|
58
80
|
|
59
81
|
class AccountError(SecurityError):
|
@@ -106,7 +128,9 @@ class JWTDecodeError(SessionError):
|
|
106
128
|
Raised when client JWT is invalid.
|
107
129
|
"""
|
108
130
|
|
109
|
-
def __init__(
|
131
|
+
def __init__(
|
132
|
+
self, message="Session token invalid, not provided, or expired.", code=401
|
133
|
+
):
|
110
134
|
super().__init__(message, code)
|
111
135
|
|
112
136
|
|
@@ -115,7 +139,11 @@ class DeactivatedError(SessionError):
|
|
115
139
|
Raised when session is deactivated.
|
116
140
|
"""
|
117
141
|
|
118
|
-
def __init__(
|
142
|
+
def __init__(
|
143
|
+
self,
|
144
|
+
message: str = "Session has been deactivated.",
|
145
|
+
code: int = 401,
|
146
|
+
):
|
119
147
|
super().__init__(message, code)
|
120
148
|
|
121
149
|
|
@@ -124,8 +152,8 @@ class ExpiredError(SessionError):
|
|
124
152
|
Raised when session has expired.
|
125
153
|
"""
|
126
154
|
|
127
|
-
def __init__(self):
|
128
|
-
super().__init__(
|
155
|
+
def __init__(self, message="Session has expired."):
|
156
|
+
super().__init__(message)
|
129
157
|
|
130
158
|
|
131
159
|
class SecondFactorRequiredError(SessionError):
|
@@ -173,10 +201,16 @@ class AuthorizationError(SecurityError):
|
|
173
201
|
super().__init__(message, 403)
|
174
202
|
|
175
203
|
|
176
|
-
class
|
204
|
+
class AnonymousError(AuthorizationError):
|
177
205
|
"""
|
178
|
-
Raised when
|
206
|
+
Raised when attempting to authorize an anonymous user.
|
179
207
|
"""
|
180
208
|
|
181
|
-
def __init__(self
|
182
|
-
super().__init__(
|
209
|
+
def __init__(self):
|
210
|
+
super().__init__("Session is anonymous.")
|
211
|
+
|
212
|
+
|
213
|
+
class AuditWarning(Warning):
|
214
|
+
"""
|
215
|
+
Raised when configuration may be dangerous.
|
216
|
+
"""
|