plain 0.17.0__py3-none-any.whl → 0.18.0__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.
- plain/csrf/middleware.py +5 -9
- plain/csrf/views.py +23 -2
- plain/internal/handlers/base.py +1 -20
- plain/test/client.py +1 -1
- plain/utils/decorators.py +0 -80
- plain/views/base.py +14 -15
- plain/views/csrf.py +3 -23
- {plain-0.17.0.dist-info → plain-0.18.0.dist-info}/METADATA +1 -1
- {plain-0.17.0.dist-info → plain-0.18.0.dist-info}/RECORD +12 -12
- {plain-0.17.0.dist-info → plain-0.18.0.dist-info}/WHEEL +0 -0
- {plain-0.17.0.dist-info → plain-0.18.0.dist-info}/entry_points.txt +0 -0
- {plain-0.17.0.dist-info → plain-0.18.0.dist-info}/licenses/LICENSE +0 -0
plain/csrf/middleware.py
CHANGED
@@ -180,6 +180,9 @@ class CsrfViewMiddleware:
|
|
180
180
|
# saved anyways.
|
181
181
|
request.META["CSRF_COOKIE"] = csrf_secret
|
182
182
|
|
183
|
+
if csrf_response := self._get_csrf_response(request):
|
184
|
+
return csrf_response
|
185
|
+
|
183
186
|
response = self.get_response(request)
|
184
187
|
|
185
188
|
if request.META.get("CSRF_COOKIE_NEEDS_UPDATE"):
|
@@ -395,23 +398,16 @@ class CsrfViewMiddleware:
|
|
395
398
|
reason = self._bad_token_message("incorrect", token_source)
|
396
399
|
raise RejectRequest(reason)
|
397
400
|
|
398
|
-
def
|
401
|
+
def _get_csrf_response(self, request):
|
399
402
|
# Wait until request.META["CSRF_COOKIE"] has been manipulated before
|
400
403
|
# bailing out, so that get_token still works
|
401
|
-
if getattr(
|
404
|
+
if getattr(request, "csrf_exempt", True):
|
402
405
|
return None
|
403
406
|
|
404
407
|
# Assume that anything not defined as 'safe' by RFC 9110 needs protection
|
405
408
|
if request.method in ("GET", "HEAD", "OPTIONS", "TRACE"):
|
406
409
|
return None
|
407
410
|
|
408
|
-
if getattr(request, "_dont_enforce_csrf_checks", False):
|
409
|
-
# Mechanism to turn off CSRF checks for test suite. It comes after
|
410
|
-
# the creation of CSRF cookies, so that everything else continues
|
411
|
-
# to work exactly the same (e.g. cookies are sent, etc.), but
|
412
|
-
# before any branches that call the _reject method.
|
413
|
-
return None
|
414
|
-
|
415
411
|
# Reject the request if the Origin header doesn't match an allowed
|
416
412
|
# value.
|
417
413
|
if "HTTP_ORIGIN" in request.META:
|
plain/csrf/views.py
CHANGED
@@ -4,7 +4,28 @@ from plain.views import TemplateView
|
|
4
4
|
class CsrfFailureView(TemplateView):
|
5
5
|
template_name = "403.html"
|
6
6
|
|
7
|
-
def
|
8
|
-
response = super().
|
7
|
+
def get_response(self):
|
8
|
+
response = super().get_response()
|
9
9
|
response.status_code = 403
|
10
10
|
return response
|
11
|
+
|
12
|
+
def post(self):
|
13
|
+
return self.get()
|
14
|
+
|
15
|
+
def put(self):
|
16
|
+
return self.get()
|
17
|
+
|
18
|
+
def patch(self):
|
19
|
+
return self.get()
|
20
|
+
|
21
|
+
def delete(self):
|
22
|
+
return self.get()
|
23
|
+
|
24
|
+
def head(self):
|
25
|
+
return self.get()
|
26
|
+
|
27
|
+
def options(self):
|
28
|
+
return self.get()
|
29
|
+
|
30
|
+
def trace(self):
|
31
|
+
return self.get()
|
plain/internal/handlers/base.py
CHANGED
@@ -23,7 +23,6 @@ BUILTIN_MIDDLEWARE = [
|
|
23
23
|
|
24
24
|
|
25
25
|
class BaseHandler:
|
26
|
-
_view_middleware = None
|
27
26
|
_middleware_chain = None
|
28
27
|
|
29
28
|
def load_middleware(self):
|
@@ -32,8 +31,6 @@ class BaseHandler:
|
|
32
31
|
|
33
32
|
Must be called after the environment is fixed (see __call__ in subclasses).
|
34
33
|
"""
|
35
|
-
self._view_middleware = []
|
36
|
-
|
37
34
|
get_response = self._get_response
|
38
35
|
handler = convert_exception_to_response(get_response)
|
39
36
|
|
@@ -48,12 +45,6 @@ class BaseHandler:
|
|
48
45
|
f"Middleware factory {middleware_path} returned None."
|
49
46
|
)
|
50
47
|
|
51
|
-
if hasattr(mw_instance, "process_view"):
|
52
|
-
self._view_middleware.insert(
|
53
|
-
0,
|
54
|
-
mw_instance.process_view,
|
55
|
-
)
|
56
|
-
|
57
48
|
handler = convert_exception_to_response(mw_instance)
|
58
49
|
|
59
50
|
# We only assign to this when initialization is complete as it is used
|
@@ -82,19 +73,9 @@ class BaseHandler:
|
|
82
73
|
template_response middleware. This method is everything that happens
|
83
74
|
inside the request/response middleware.
|
84
75
|
"""
|
85
|
-
response = None
|
86
76
|
callback, callback_args, callback_kwargs = self.resolve_request(request)
|
87
77
|
|
88
|
-
|
89
|
-
for middleware_method in self._view_middleware:
|
90
|
-
response = middleware_method(
|
91
|
-
request, callback, callback_args, callback_kwargs
|
92
|
-
)
|
93
|
-
if response:
|
94
|
-
break
|
95
|
-
|
96
|
-
if response is None:
|
97
|
-
response = callback(request, *callback_args, **callback_kwargs)
|
78
|
+
response = callback(request, *callback_args, **callback_kwargs)
|
98
79
|
|
99
80
|
# Complain if the view returned None (a common error).
|
100
81
|
self.check_response(response, callback)
|
plain/test/client.py
CHANGED
@@ -177,7 +177,7 @@ class ClientHandler(BaseHandler):
|
|
177
177
|
# CsrfViewMiddleware. This makes life easier, and is probably
|
178
178
|
# required for backwards compatibility with external tests against
|
179
179
|
# admin views.
|
180
|
-
request.
|
180
|
+
request.csrf_exempt = not self.enforce_csrf_checks
|
181
181
|
|
182
182
|
# Request goes through middleware.
|
183
183
|
response = self.get_response(request)
|
plain/utils/decorators.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
"Functions that help with dynamically creating decorators for views."
|
2
2
|
|
3
|
-
from functools import partial, update_wrapper, wraps
|
4
|
-
|
5
3
|
|
6
4
|
class classonlymethod(classmethod):
|
7
5
|
def __get__(self, instance, cls=None):
|
@@ -10,81 +8,3 @@ class classonlymethod(classmethod):
|
|
10
8
|
"This method is available only on the class, not on instances."
|
11
9
|
)
|
12
10
|
return super().__get__(instance, cls)
|
13
|
-
|
14
|
-
|
15
|
-
def _update_method_wrapper(_wrapper, decorator):
|
16
|
-
# _multi_decorate()'s bound_method isn't available in this scope. Cheat by
|
17
|
-
# using it on a dummy function.
|
18
|
-
@decorator
|
19
|
-
def dummy(*args, **kwargs):
|
20
|
-
pass
|
21
|
-
|
22
|
-
update_wrapper(_wrapper, dummy)
|
23
|
-
|
24
|
-
|
25
|
-
def _multi_decorate(decorators, method):
|
26
|
-
"""
|
27
|
-
Decorate `method` with one or more function decorators. `decorators` can be
|
28
|
-
a single decorator or an iterable of decorators.
|
29
|
-
"""
|
30
|
-
if hasattr(decorators, "__iter__"):
|
31
|
-
# Apply a list/tuple of decorators if 'decorators' is one. Decorator
|
32
|
-
# functions are applied so that the call order is the same as the
|
33
|
-
# order in which they appear in the iterable.
|
34
|
-
decorators = decorators[::-1]
|
35
|
-
else:
|
36
|
-
decorators = [decorators]
|
37
|
-
|
38
|
-
def _wrapper(self, *args, **kwargs):
|
39
|
-
# bound_method has the signature that 'decorator' expects i.e. no
|
40
|
-
# 'self' argument, but it's a closure over self so it can call
|
41
|
-
# 'func'. Also, wrap method.__get__() in a function because new
|
42
|
-
# attributes can't be set on bound method objects, only on functions.
|
43
|
-
bound_method = wraps(method)(partial(method.__get__(self, type(self))))
|
44
|
-
for dec in decorators:
|
45
|
-
bound_method = dec(bound_method)
|
46
|
-
return bound_method(*args, **kwargs)
|
47
|
-
|
48
|
-
# Copy any attributes that a decorator adds to the function it decorates.
|
49
|
-
for dec in decorators:
|
50
|
-
_update_method_wrapper(_wrapper, dec)
|
51
|
-
# Preserve any existing attributes of 'method', including the name.
|
52
|
-
update_wrapper(_wrapper, method)
|
53
|
-
return _wrapper
|
54
|
-
|
55
|
-
|
56
|
-
def method_decorator(decorator, name=""):
|
57
|
-
"""
|
58
|
-
Convert a function decorator into a method decorator
|
59
|
-
"""
|
60
|
-
|
61
|
-
# 'obj' can be a class or a function. If 'obj' is a function at the time it
|
62
|
-
# is passed to _dec, it will eventually be a method of the class it is
|
63
|
-
# defined on. If 'obj' is a class, the 'name' is required to be the name
|
64
|
-
# of the method that will be decorated.
|
65
|
-
def _dec(obj):
|
66
|
-
if not isinstance(obj, type):
|
67
|
-
return _multi_decorate(decorator, obj)
|
68
|
-
if not (name and hasattr(obj, name)):
|
69
|
-
raise ValueError(
|
70
|
-
"The keyword argument `name` must be the name of a method "
|
71
|
-
f"of the decorated class: {obj}. Got '{name}' instead."
|
72
|
-
)
|
73
|
-
method = getattr(obj, name)
|
74
|
-
if not callable(method):
|
75
|
-
raise TypeError(
|
76
|
-
f"Cannot decorate '{name}' as it isn't a callable attribute of "
|
77
|
-
f"{obj} ({method})."
|
78
|
-
)
|
79
|
-
_wrapper = _multi_decorate(decorator, method)
|
80
|
-
setattr(obj, name, _wrapper)
|
81
|
-
return obj
|
82
|
-
|
83
|
-
# Don't worry about making _dec look similar to a list/tuple as it's rather
|
84
|
-
# meaningless.
|
85
|
-
if not hasattr(decorator, "__iter__"):
|
86
|
-
update_wrapper(_dec, decorator)
|
87
|
-
# Change the name to aid debugging.
|
88
|
-
obj = decorator if hasattr(decorator, "__name__") else decorator.__class__
|
89
|
-
_dec.__name__ = f"method_decorator({obj.__name__})"
|
90
|
-
return _dec
|
plain/views/base.py
CHANGED
@@ -19,6 +19,18 @@ class View:
|
|
19
19
|
url_args: tuple
|
20
20
|
url_kwargs: dict
|
21
21
|
|
22
|
+
# By default, any of these are allowed if a method is defined for it.
|
23
|
+
allowed_http_methods = [
|
24
|
+
"get",
|
25
|
+
"post",
|
26
|
+
"put",
|
27
|
+
"patch",
|
28
|
+
"delete",
|
29
|
+
"head",
|
30
|
+
"options",
|
31
|
+
"trace",
|
32
|
+
]
|
33
|
+
|
22
34
|
def __init__(self, *args, **kwargs) -> None:
|
23
35
|
# Views can customize their init, which receives
|
24
36
|
# the args and kwargs from as_view()
|
@@ -42,9 +54,6 @@ class View:
|
|
42
54
|
except ResponseException as e:
|
43
55
|
return e.response
|
44
56
|
|
45
|
-
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
|
46
|
-
# the dispatch method.
|
47
|
-
view.__dict__.update(cls.get_response.__dict__)
|
48
57
|
view.view_class = cls
|
49
58
|
|
50
59
|
return view
|
@@ -57,7 +66,7 @@ class View:
|
|
57
66
|
|
58
67
|
handler = getattr(self, self.request.method.lower(), None)
|
59
68
|
|
60
|
-
if not handler:
|
69
|
+
if not handler or self.request.method.lower() not in self.allowed_http_methods:
|
61
70
|
logger.warning(
|
62
71
|
"Method Not Allowed (%s): %s",
|
63
72
|
self.request.method,
|
@@ -100,14 +109,4 @@ class View:
|
|
100
109
|
return response
|
101
110
|
|
102
111
|
def _allowed_methods(self) -> list[str]:
|
103
|
-
|
104
|
-
"get",
|
105
|
-
"post",
|
106
|
-
"put",
|
107
|
-
"patch",
|
108
|
-
"delete",
|
109
|
-
"head",
|
110
|
-
"options",
|
111
|
-
"trace",
|
112
|
-
]
|
113
|
-
return [m.upper() for m in known_http_method_names if hasattr(self, m)]
|
112
|
+
return [m.upper() for m in self.allowed_http_methods if hasattr(self, m)]
|
plain/views/csrf.py
CHANGED
@@ -1,24 +1,4 @@
|
|
1
|
-
from functools import wraps
|
2
|
-
|
3
|
-
from plain.utils.decorators import method_decorator
|
4
|
-
|
5
|
-
|
6
|
-
def csrf_exempt(view_func):
|
7
|
-
"""Mark a view function as being exempt from the CSRF view protection."""
|
8
|
-
|
9
|
-
# view_func.csrf_exempt = True would also work, but decorators are nicer
|
10
|
-
# if they don't have side effects, so return a new function.
|
11
|
-
@wraps(view_func)
|
12
|
-
def wrapper_view(*args, **kwargs):
|
13
|
-
return view_func(*args, **kwargs)
|
14
|
-
|
15
|
-
wrapper_view.csrf_exempt = True
|
16
|
-
return wrapper_view
|
17
|
-
|
18
|
-
|
19
|
-
@method_decorator(csrf_exempt, name="get_response")
|
20
1
|
class CsrfExemptViewMixin:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
return super().get_response()
|
2
|
+
def setup(self, *args, **kwargs):
|
3
|
+
super().setup(*args, **kwargs)
|
4
|
+
self.request.csrf_exempt = True
|
@@ -22,8 +22,8 @@ plain/cli/packages.py,sha256=FqRJaizaxpQ8lSS4nYNjqIGpYGDbeTmCXCvkGxusGOM,2160
|
|
22
22
|
plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
|
23
23
|
plain/cli/startup.py,sha256=3LIz9JrIZoF52Sa0j0SCypQwEaBDkhvuGaBdtiQLr5Q,680
|
24
24
|
plain/csrf/README.md,sha256=RXMWMtHmzf30gVVNOfj0kD4xlSqFIPgJh-n7dIciaEM,163
|
25
|
-
plain/csrf/middleware.py,sha256=
|
26
|
-
plain/csrf/views.py,sha256=
|
25
|
+
plain/csrf/middleware.py,sha256=1szNRF-kKJdjhTw_Jw1Cp2IldtrLtbUqAApJOrVP1qc,17449
|
26
|
+
plain/csrf/views.py,sha256=HwQqfI6KPelHP9gSXhjfZaTLQic71PKsoZ6DPhr1rKI,572
|
27
27
|
plain/forms/README.md,sha256=fglB9MmHiEgfGGdZmcRstNl6eYaFljrElu2mzapK52M,377
|
28
28
|
plain/forms/__init__.py,sha256=UxqPwB8CiYPCQdHmUc59jadqaXqDmXBH8y4bt9vTPms,226
|
29
29
|
plain/forms/boundfield.py,sha256=LhydhCVR0okrli0-QBMjGjAJ8-06gTCXVEaBZhBouQk,1741
|
@@ -47,7 +47,7 @@ plain/internal/files/uploadedfile.py,sha256=JRB7T3quQjg-1y3l1ASPxywtSQZhaeMc45uF
|
|
47
47
|
plain/internal/files/uploadhandler.py,sha256=eEnd5onstypjHYtg367PnVWwCaF1kAPlLPSV7goIf_E,7198
|
48
48
|
plain/internal/files/utils.py,sha256=xN4HTJXDRdcoNyrL1dFd528MBwodRlHZM8DGTD_oBIg,2646
|
49
49
|
plain/internal/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
50
|
-
plain/internal/handlers/base.py,sha256=
|
50
|
+
plain/internal/handlers/base.py,sha256=_1aRNrNgqdx8kkUlXYRJNOTnZ_Z236JNYNcTdp7RRjc,4228
|
51
51
|
plain/internal/handlers/exception.py,sha256=DH9gh1FyqgetFpMaB8yLIVE6phBTVPKQLA1GIn9MOeI,4555
|
52
52
|
plain/internal/handlers/wsgi.py,sha256=estA1QKHTk3ZqziWxenHsw5UO2cwPp3Zr0XjkDeM5TY,7561
|
53
53
|
plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -90,7 +90,7 @@ plain/templates/jinja/filters.py,sha256=3KJKKbxcv9dLzUDWPcaa88k3NU2m1GG3iMIgFhzX
|
|
90
90
|
plain/templates/jinja/globals.py,sha256=qhvQuikkRkOTpHSW5FwdsvoViJNlRgHq3-O7ZyeajsE,669
|
91
91
|
plain/test/README.md,sha256=Zso3Ir7a8vQerzKB6egjROQWkpveLAbscn7VTROPAiU,37
|
92
92
|
plain/test/__init__.py,sha256=rXe88Y602NP8DBnReSyXb7dUzKoWweLuT43j-qwOUl4,138
|
93
|
-
plain/test/client.py,sha256=
|
93
|
+
plain/test/client.py,sha256=wqypz5iIu28ubaEqMdx4wO7n6IPcBRkgkVXeEdr_qZg,31351
|
94
94
|
plain/urls/README.md,sha256=pWnCvgYkWN7rG7hSyBOtX4ZUP3iO7FhqM6lvwwYll6c,33
|
95
95
|
plain/urls/__init__.py,sha256=3UzwIufXjIks2K_X_Vms2MV19IqvyPLrXUeHU3WP47c,753
|
96
96
|
plain/urls/base.py,sha256=Q1TzI5WvqYkN1U81fDom1q-AID4dXbszEMF6wAeTAI0,3717
|
@@ -109,7 +109,7 @@ plain/utils/dateformat.py,sha256=nsy71l16QuPN0ozGpVlCU5Et101fhk9L38F-wqT5p5I,102
|
|
109
109
|
plain/utils/dateparse.py,sha256=u9_tF85YteXSjW9KQzNg_pcCEFDZS3EGorCddcWU0vE,5351
|
110
110
|
plain/utils/dates.py,sha256=hSDKz8eIb3W5QjmGiklFZZELB0inYXsfiRUy49Cx-2Q,1226
|
111
111
|
plain/utils/deconstruct.py,sha256=7NwEFIDCiadAArUBFmiErzDgfIgDWeKqqQFDXwSgQoQ,1830
|
112
|
-
plain/utils/decorators.py,sha256=
|
112
|
+
plain/utils/decorators.py,sha256=mLHOo2jLdvYRo2z8lkeVn2vQErlj7xC6XoLwZBYf_z8,358
|
113
113
|
plain/utils/deprecation.py,sha256=qtj33kHEmJU7mGSrNcKidZsMo5W8MN-zrXzUq3aVVy8,131
|
114
114
|
plain/utils/duration.py,sha256=l0Gc41-DeyyAmpdy2XG-YO5UKxMf1NDpWIlQuD5hAn0,1162
|
115
115
|
plain/utils/email.py,sha256=puRTBVuz44YvpnqV3LT4nNIKqdqfY3L8zbDJIkqHk2Y,328
|
@@ -130,16 +130,16 @@ plain/utils/timezone.py,sha256=6u0sE-9RVp0_OCe0Y1KiYYQoq5THWLokZFQYY8jf78g,6221
|
|
130
130
|
plain/utils/tree.py,sha256=wdWzmfsgc26YDF2wxhAY3yVxXTixQYqYDKE9mL3L3ZY,4383
|
131
131
|
plain/views/README.md,sha256=qndsXKyNMnipPlLaAvgQeGxqXknNQwlFh31Yxk8rHp8,5994
|
132
132
|
plain/views/__init__.py,sha256=a-N1nkklVohJTtz0yD1MMaS0g66HviEjsKydNVVjvVc,392
|
133
|
-
plain/views/base.py,sha256=
|
134
|
-
plain/views/csrf.py,sha256=
|
133
|
+
plain/views/base.py,sha256=HxOOOKZDCCZDmy2cJCEhYswVeIHZ9TukYh1zibeUn6w,3160
|
134
|
+
plain/views/csrf.py,sha256=7q6l5xzLWhRnMY64aNj0hR6G-3pxI2yhRwG6k_5j00E,144
|
135
135
|
plain/views/errors.py,sha256=Y4oGX4Z6D2COKcDEfINvXE1acE8Ad15KwNNWPs5BCfc,967
|
136
136
|
plain/views/exceptions.py,sha256=b4euI49ZUKS9O8AGAcFfiDpstzkRAuuj_uYQXzWNHME,138
|
137
137
|
plain/views/forms.py,sha256=RhlaUcZCkeqokY_fvv-NOS-kgZAG4XhDLOPbf9K_Zlc,2691
|
138
138
|
plain/views/objects.py,sha256=fRfS6KNehIGqkbPw4nSafj8HStxYExHmbggolBbzcxs,7921
|
139
139
|
plain/views/redirect.py,sha256=KLnlktzK6ZNMTlaEiZpMKQMEP5zeTgGLJ9BIkIJfwBo,1733
|
140
140
|
plain/views/templates.py,sha256=nF9CcdhhjAyp3LB0RrSYnBaHpHzMfPSw719RCdcXk7o,2007
|
141
|
-
plain-0.
|
142
|
-
plain-0.
|
143
|
-
plain-0.
|
144
|
-
plain-0.
|
145
|
-
plain-0.
|
141
|
+
plain-0.18.0.dist-info/METADATA,sha256=Qik7zQyhLtbB8QcE6qwCar14RSDbiFaalX_6-VejaBc,2518
|
142
|
+
plain-0.18.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
143
|
+
plain-0.18.0.dist-info/entry_points.txt,sha256=DHHprvufgd7xypiBiqMANYRnpJ9xPPYhYbnPGwOkWqE,40
|
144
|
+
plain-0.18.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
|
145
|
+
plain-0.18.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|