sanic-security 1.11.7__py3-none-any.whl → 1.16.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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.6.dist-info/LICENSE +21 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.16.6.dist-info}/METADATA +685 -591
- sanic_security-1.16.6.dist-info/RECORD +17 -0
- {sanic_security-1.11.7.dist-info → sanic_security-1.16.6.dist-info}/WHEEL +2 -1
- sanic_security-1.16.6.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/verification.py
CHANGED
@@ -1,35 +1,42 @@
|
|
1
1
|
import functools
|
2
2
|
from contextlib import suppress
|
3
3
|
|
4
|
+
from sanic.log import logger
|
4
5
|
from sanic.request import Request
|
5
6
|
|
6
7
|
from sanic_security.exceptions import (
|
7
8
|
JWTDecodeError,
|
8
9
|
NotFoundError,
|
9
10
|
VerifiedError,
|
11
|
+
MaxedOutChallengeError,
|
10
12
|
)
|
11
13
|
from sanic_security.models import (
|
12
14
|
Account,
|
13
15
|
TwoStepSession,
|
14
16
|
CaptchaSession,
|
15
17
|
)
|
18
|
+
from sanic_security.utils import get_ip
|
16
19
|
|
17
20
|
"""
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
21
|
+
Copyright (c) 2020-present Nicholas Aidan Stewart
|
22
|
+
|
23
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
24
|
+
of this software and associated documentation files (the "Software"), to deal
|
25
|
+
in the Software without restriction, including without limitation the rights
|
26
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
27
|
+
copies of the Software, and to permit persons to whom the Software is
|
28
|
+
furnished to do so, subject to the following conditions:
|
29
|
+
|
30
|
+
The above copyright notice and this permission notice shall be included in all
|
31
|
+
copies or substantial portions of the Software.
|
32
|
+
|
33
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
34
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
35
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
36
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
37
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
38
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
39
|
+
SOFTWARE.
|
33
40
|
"""
|
34
41
|
|
35
42
|
|
@@ -58,6 +65,7 @@ async def request_two_step_verification(
|
|
58
65
|
if request.form.get("email") or not account:
|
59
66
|
account = await Account.get_via_email(request.form.get("email"))
|
60
67
|
two_step_session = await TwoStepSession.new(request, account)
|
68
|
+
request.ctx.session = two_step_session
|
61
69
|
return two_step_session
|
62
70
|
|
63
71
|
|
@@ -85,7 +93,16 @@ async def two_step_verification(request: Request) -> TwoStepSession:
|
|
85
93
|
two_step_session = await TwoStepSession.decode(request)
|
86
94
|
two_step_session.validate()
|
87
95
|
two_step_session.bearer.validate()
|
88
|
-
|
96
|
+
try:
|
97
|
+
await two_step_session.check_code(request.form.get("code"))
|
98
|
+
except MaxedOutChallengeError as e:
|
99
|
+
logger.warning(
|
100
|
+
f"Client {get_ip(request)} has exceeded maximum two-step session {two_step_session.id} challenge attempts."
|
101
|
+
)
|
102
|
+
raise e
|
103
|
+
logger.info(
|
104
|
+
f"Client {get_ip(request)} has completed two-step session {two_step_session.id} challenge."
|
105
|
+
)
|
89
106
|
return two_step_session
|
90
107
|
|
91
108
|
|
@@ -117,15 +134,12 @@ def requires_two_step_verification(arg=None):
|
|
117
134
|
def decorator(func):
|
118
135
|
@functools.wraps(func)
|
119
136
|
async def wrapper(request, *args, **kwargs):
|
120
|
-
|
137
|
+
await two_step_verification(request)
|
121
138
|
return await func(request, *args, **kwargs)
|
122
139
|
|
123
140
|
return wrapper
|
124
141
|
|
125
|
-
if callable(arg)
|
126
|
-
return decorator(arg)
|
127
|
-
else:
|
128
|
-
return decorator
|
142
|
+
return decorator(arg) if callable(arg) else decorator
|
129
143
|
|
130
144
|
|
131
145
|
async def verify_account(request: Request) -> TwoStepSession:
|
@@ -150,31 +164,24 @@ async def verify_account(request: Request) -> TwoStepSession:
|
|
150
164
|
"""
|
151
165
|
two_step_session = await TwoStepSession.decode(request)
|
152
166
|
if two_step_session.bearer.verified:
|
153
|
-
raise VerifiedError
|
167
|
+
raise VerifiedError
|
154
168
|
two_step_session.validate()
|
155
|
-
|
169
|
+
try:
|
170
|
+
await two_step_session.check_code(request.form.get("code"))
|
171
|
+
except MaxedOutChallengeError as e:
|
172
|
+
logger.warning(
|
173
|
+
f"Client {get_ip(request)} has exceeded maximum two-step session {two_step_session.id} challenge attempts "
|
174
|
+
"during account verification."
|
175
|
+
)
|
176
|
+
raise e
|
156
177
|
two_step_session.bearer.verified = True
|
157
178
|
await two_step_session.bearer.save(update_fields=["verified"])
|
179
|
+
logger.info(
|
180
|
+
f"Client {get_ip(request)} has verified account {two_step_session.bearer.id}."
|
181
|
+
)
|
158
182
|
return two_step_session
|
159
183
|
|
160
184
|
|
161
|
-
async def request_captcha(request: Request) -> CaptchaSession:
|
162
|
-
"""
|
163
|
-
Creates a captcha session and deactivates the client's current captcha session if found.
|
164
|
-
|
165
|
-
Args:
|
166
|
-
request (Request): Sanic request parameter.
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
captcha_session
|
170
|
-
"""
|
171
|
-
with suppress(NotFoundError, JWTDecodeError):
|
172
|
-
captcha_session = await CaptchaSession.decode(request)
|
173
|
-
if captcha_session.active:
|
174
|
-
await captcha_session.deactivate()
|
175
|
-
return await CaptchaSession.new(request)
|
176
|
-
|
177
|
-
|
178
185
|
async def captcha(request: Request) -> CaptchaSession:
|
179
186
|
"""
|
180
187
|
Validates a captcha challenge attempt.
|
@@ -196,7 +203,16 @@ async def captcha(request: Request) -> CaptchaSession:
|
|
196
203
|
"""
|
197
204
|
captcha_session = await CaptchaSession.decode(request)
|
198
205
|
captcha_session.validate()
|
199
|
-
|
206
|
+
try:
|
207
|
+
await captcha_session.check_code(request.form.get("captcha"))
|
208
|
+
except MaxedOutChallengeError as e:
|
209
|
+
logger.warning(
|
210
|
+
f"Client {get_ip(request)} has exceeded maximum captcha session {captcha_session.id} challenge attempts."
|
211
|
+
)
|
212
|
+
raise e
|
213
|
+
logger.info(
|
214
|
+
f"Client {get_ip(request)} has completed captcha session {captcha_session.id} challenge."
|
215
|
+
)
|
200
216
|
return captcha_session
|
201
217
|
|
202
218
|
|
@@ -225,12 +241,9 @@ def requires_captcha(arg=None):
|
|
225
241
|
def decorator(func):
|
226
242
|
@functools.wraps(func)
|
227
243
|
async def wrapper(request, *args, **kwargs):
|
228
|
-
|
244
|
+
await captcha(request)
|
229
245
|
return await func(request, *args, **kwargs)
|
230
246
|
|
231
247
|
return wrapper
|
232
248
|
|
233
|
-
if callable(arg)
|
234
|
-
return decorator(arg)
|
235
|
-
else:
|
236
|
-
return decorator
|
249
|
+
return decorator(arg) if callable(arg) else decorator
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Nicholas Aidan Stewart
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|