Skip to content

Commit aab6311

Browse files
committed
Rework middleware and add BUILTIN_MIDDLEWARE
1 parent 18d4e87 commit aab6311

File tree

20 files changed

+106
-199
lines changed

20 files changed

+106
-199
lines changed

plain-auth/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ INSTALLED_PACKAGES = [
2323
]
2424

2525
MIDDLEWARE = [
26-
"plain.sessions.middleware.SessionMiddleware", # <--
27-
"plain.middleware.common.CommonMiddleware",
28-
"plain.csrf.middleware.CsrfViewMiddleware",
29-
"plain.auth.middleware.AuthenticationMiddleware", # <--
26+
"plain.sessions.middleware.SessionMiddleware",
27+
"plain.auth.middleware.AuthenticationMiddleware",
3028
]
3129

3230
AUTH_USER_MODEL = "users.User"

plain-auth/plain/auth/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ INSTALLED_PACKAGES = [
2121
]
2222

2323
MIDDLEWARE = [
24-
"plain.sessions.middleware.SessionMiddleware", # <--
25-
"plain.middleware.common.CommonMiddleware",
26-
"plain.csrf.middleware.CsrfViewMiddleware",
27-
"plain.auth.middleware.AuthenticationMiddleware", # <--
24+
"plain.sessions.middleware.SessionMiddleware",
25+
"plain.auth.middleware.AuthenticationMiddleware",
2826
]
2927

3028
AUTH_USER_MODEL = "users.User"

plain-importmap/test_project/settings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626

2727
MIDDLEWARE = [
2828
"plain.sessions.middleware.SessionMiddleware",
29-
"plain.middleware.common.CommonMiddleware",
30-
"plain.csrf.middleware.CsrfViewMiddleware",
3129
"plain.auth.middleware.AuthenticationMiddleware",
3230
]
3331

plain-oauth/tests/provider_tests/test_github.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def test_github_provider(db, client, settings):
3939
assert response.status_code == 302
4040
assert (
4141
response.url
42-
== "https://github.com/login/oauth/authorize?client_id=test_id&redirect_uri=http%3A%2F%2Ftestserver%2Foauth%2Fgithub%2Fcallback%2F&response_type=code&scope=user&state=dummy_state"
42+
== "https://github.com/login/oauth/authorize?client_id=test_id&redirect_uri=https%3A%2F%2Ftestserver%2Foauth%2Fgithub%2Fcallback%2F&response_type=code&scope=user&state=dummy_state"
4343
)
4444

4545
# GitHub redirects to the callback url

plain-oauth/tests/test_providers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_dummy_signup(db, client, settings):
6868
assert response.status_code == 302
6969
assert (
7070
response.url
71-
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=http%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
71+
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=https%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
7272
)
7373

7474
# Provider redirects to the callback url
@@ -148,7 +148,7 @@ def test_dummy_login_connection(db, client, settings):
148148
assert response.status_code == 302
149149
assert (
150150
response.url
151-
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=http%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
151+
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=https%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
152152
)
153153

154154
# Provider redirects to the callback url
@@ -215,7 +215,7 @@ def test_dummy_login_without_connection(db, client, settings):
215215
assert response.status_code == 302
216216
assert (
217217
response.url
218-
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=http%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
218+
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=https%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
219219
)
220220

221221
# Provider redirects to the callback url
@@ -253,7 +253,7 @@ def test_dummy_connect(db, client, settings):
253253
assert response.status_code == 302
254254
assert (
255255
response.url
256-
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=http%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
256+
== "https://example.com/oauth/authorize?client_id=dummy_client_id&redirect_uri=https%3A%2F%2Ftestserver%2Foauth%2Fdummy%2Fcallback%2F&response_type=code&scope=dummy_scope&state=dummy_state"
257257
)
258258

259259
# Provider redirects to the callback url

plain-sessions/tests/test_sessions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
def test_session_created(db, client):
55
assert Session.objects.count() == 0
66

7-
client.get("/")
7+
response = client.get("/")
8+
9+
assert response.status_code == 200
810

911
assert Session.objects.count() == 1

plain-staff/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ INSTALLED_PACKAGES = [
5252
5353
MIDDLEWARE = [
5454
"plain.sessions.middleware.SessionMiddleware",
55-
"plain.middleware.common.CommonMiddleware",
56-
"plain.csrf.middleware.CsrfViewMiddleware",
5755
"plain.auth.middleware.AuthenticationMiddleware",
5856
5957
"plain.staff.querystats.QueryStatsMiddleware",

plain-staff/plain/staff/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ INSTALLED_PACKAGES = [
5050
5151
MIDDLEWARE = [
5252
"plain.sessions.middleware.SessionMiddleware",
53-
"plain.middleware.common.CommonMiddleware",
54-
"plain.csrf.middleware.CsrfViewMiddleware",
5553
"plain.auth.middleware.AuthenticationMiddleware",
5654
5755
"plain.staff.querystats.QueryStatsMiddleware",

plain-staff/plain/staff/querystats/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ INSTALLED_PACKAGES = [
2727

2828
MIDDLEWARE = [
2929
"plain.sessions.middleware.SessionMiddleware",
30-
"plain.middleware.common.CommonMiddleware",
31-
"plain.csrf.middleware.CsrfViewMiddleware",
3230
"plain.auth.middleware.AuthenticationMiddleware",
3331

3432
"plain.staff.querystats.QueryStatsMiddleware",

plain/plain/csrf/middleware.py

Lines changed: 21 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from collections import defaultdict
1010
from urllib.parse import urlparse
1111

12-
from plain.exceptions import DisallowedHost, ImproperlyConfigured
12+
from plain.exceptions import DisallowedHost
1313
from plain.http import HttpHeaders, UnreadablePostError
1414
from plain.logs import log_response
1515
from plain.runtime import settings
@@ -242,44 +242,31 @@ def _get_secret(self, request):
242242
If the CSRF_USE_SESSIONS setting is false, raises InvalidTokenFormat if
243243
the request's secret has invalid characters or an invalid length.
244244
"""
245-
if settings.CSRF_USE_SESSIONS:
246-
try:
247-
csrf_secret = request.session.get(CSRF_SESSION_KEY)
248-
except AttributeError:
249-
raise ImproperlyConfigured(
250-
"CSRF_USE_SESSIONS is enabled, but request.session is not "
251-
"set. SessionMiddleware must appear before CsrfViewMiddleware "
252-
"in MIDDLEWARE."
253-
)
245+
try:
246+
csrf_secret = request.COOKIES[settings.CSRF_COOKIE_NAME]
247+
except KeyError:
248+
csrf_secret = None
254249
else:
255-
try:
256-
csrf_secret = request.COOKIES[settings.CSRF_COOKIE_NAME]
257-
except KeyError:
258-
csrf_secret = None
259-
else:
260-
# This can raise InvalidTokenFormat.
261-
_check_token_format(csrf_secret)
250+
# This can raise InvalidTokenFormat.
251+
_check_token_format(csrf_secret)
252+
262253
if csrf_secret is None:
263254
return None
264255
return csrf_secret
265256

266257
def _set_csrf_cookie(self, request, response):
267-
if settings.CSRF_USE_SESSIONS:
268-
if request.session.get(CSRF_SESSION_KEY) != request.META["CSRF_COOKIE"]:
269-
request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"]
270-
else:
271-
response.set_cookie(
272-
settings.CSRF_COOKIE_NAME,
273-
request.META["CSRF_COOKIE"],
274-
max_age=settings.CSRF_COOKIE_AGE,
275-
domain=settings.CSRF_COOKIE_DOMAIN,
276-
path=settings.CSRF_COOKIE_PATH,
277-
secure=settings.CSRF_COOKIE_SECURE,
278-
httponly=settings.CSRF_COOKIE_HTTPONLY,
279-
samesite=settings.CSRF_COOKIE_SAMESITE,
280-
)
281-
# Set the Vary header since content varies with the CSRF cookie.
282-
patch_vary_headers(response, ("Cookie",))
258+
response.set_cookie(
259+
settings.CSRF_COOKIE_NAME,
260+
request.META["CSRF_COOKIE"],
261+
max_age=settings.CSRF_COOKIE_AGE,
262+
domain=settings.CSRF_COOKIE_DOMAIN,
263+
path=settings.CSRF_COOKIE_PATH,
264+
secure=settings.CSRF_COOKIE_SECURE,
265+
httponly=settings.CSRF_COOKIE_HTTPONLY,
266+
samesite=settings.CSRF_COOKIE_SAMESITE,
267+
)
268+
# Set the Vary header since content varies with the CSRF cookie.
269+
patch_vary_headers(response, ("Cookie",))
283270

284271
def _origin_verified(self, request):
285272
request_origin = request.META["HTTP_ORIGIN"]
@@ -331,11 +318,7 @@ def _check_referer(self, request):
331318
):
332319
return
333320
# Allow matching the configured cookie domain.
334-
good_referer = (
335-
settings.SESSION_COOKIE_DOMAIN
336-
if settings.CSRF_USE_SESSIONS
337-
else settings.CSRF_COOKIE_DOMAIN
338-
)
321+
good_referer = settings.CSRF_COOKIE_DOMAIN
339322
if good_referer is None:
340323
# If no cookie domain is configured, allow matching the current
341324
# host:port exactly if it's permitted by ALLOWED_HOSTS.

plain/plain/internal/handlers/base.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313
logger = logging.getLogger("plain.request")
1414

1515

16+
# These middleware classes are always used by Plain.
17+
BUILTIN_MIDDLEWARE = [
18+
"plain.internal.middleware.headers.DefaultHeadersMiddleware",
19+
"plain.internal.middleware.https.HttpsRedirectMiddleware",
20+
"plain.internal.middleware.slash.RedirectSlashMiddleware",
21+
"plain.csrf.middleware.CsrfViewMiddleware",
22+
]
23+
24+
1625
class BaseHandler:
1726
_view_middleware = None
1827
_middleware_chain = None
@@ -27,7 +36,10 @@ def load_middleware(self):
2736

2837
get_response = self._get_response
2938
handler = convert_exception_to_response(get_response)
30-
for middleware_path in reversed(settings.MIDDLEWARE):
39+
40+
middlewares = reversed(BUILTIN_MIDDLEWARE + settings.MIDDLEWARE)
41+
42+
for middleware_path in middlewares:
3143
middleware = import_string(middleware_path)
3244
mw_instance = middleware(handler)
3345

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from plain.runtime import settings
2+
3+
4+
class DefaultHeadersMiddleware:
5+
def __init__(self, get_response):
6+
self.get_response = get_response
7+
8+
def __call__(self, request):
9+
response = self.get_response(request)
10+
11+
for header, value in settings.DEFAULT_RESPONSE_HEADERS.items():
12+
response.headers.setdefault(header, value)
13+
14+
# Add the Content-Length header to non-streaming responses if not
15+
# already set.
16+
if not response.streaming and not response.has_header("Content-Length"):
17+
response.headers["Content-Length"] = str(len(response.content))
18+
19+
return response
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import re
2+
3+
from plain.http import ResponsePermanentRedirect
4+
from plain.runtime import settings
5+
6+
7+
class HttpsRedirectMiddleware:
8+
def __init__(self, get_response):
9+
self.get_response = get_response
10+
11+
# Settings for https (compile regexes once)
12+
self.https_redirect_enabled = settings.HTTPS_REDIRECT_ENABLED
13+
self.https_redirect_host = settings.HTTPS_REDIRECT_HOST
14+
self.https_redirect_exempt = [
15+
re.compile(r) for r in settings.HTTPS_REDIRECT_EXEMPT
16+
]
17+
18+
def __call__(self, request):
19+
"""
20+
Rewrite the URL based on settings.APPEND_SLASH
21+
"""
22+
23+
if redirect_response := self.maybe_https_redirect(request):
24+
return redirect_response
25+
26+
return self.get_response(request)
27+
28+
def maybe_https_redirect(self, request):
29+
path = request.path.lstrip("/")
30+
if (
31+
self.https_redirect_enabled
32+
and not request.is_https()
33+
and not any(pattern.search(path) for pattern in self.https_redirect_exempt)
34+
):
35+
host = self.https_redirect_host or request.get_host()
36+
return ResponsePermanentRedirect(f"https://{host}{request.get_full_path()}")

plain/plain/middleware/common.py renamed to plain/plain/internal/middleware/slash.py

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,31 @@
1-
import re
2-
31
from plain.http import ResponsePermanentRedirect
42
from plain.runtime import settings
53
from plain.urls import is_valid_path
64
from plain.utils.http import escape_leading_slashes
75

86

9-
class CommonMiddleware:
10-
"""
11-
"Common" middleware for taking care of some basic operations:
12-
13-
- Redirecting to HTTPS: Based on the HTTPS_REDIRECT_ENABLED setting,
14-
redirect to HTTPS if the request is not secure.
15-
16-
- Default response headers: Add default headers to responses.
17-
18-
- URL rewriting: Based on the APPEND_SLASH setting,
19-
append missing slashes.
20-
21-
- If APPEND_SLASH is set and the initial URL doesn't end with a
22-
slash, and it is not found in urlpatterns, form a new URL by
23-
appending a slash at the end. If this new URL is found in
24-
urlpatterns, return an HTTP redirect to this new URL; otherwise
25-
process the initial URL as usual.
26-
27-
This behavior can be customized by subclassing CommonMiddleware and
28-
overriding the response_redirect_class attribute.
29-
"""
30-
31-
response_redirect_class = ResponsePermanentRedirect
32-
7+
class RedirectSlashMiddleware:
338
def __init__(self, get_response):
349
self.get_response = get_response
3510

36-
# Settings for https (compile regexes once)
37-
self.https_redirect_enabled = settings.HTTPS_REDIRECT_ENABLED
38-
self.https_redirect_host = settings.HTTPS_REDIRECT_HOST
39-
self.https_redirect_exempt = [
40-
re.compile(r) for r in settings.HTTPS_REDIRECT_EXEMPT
41-
]
42-
4311
def __call__(self, request):
4412
"""
4513
Rewrite the URL based on settings.APPEND_SLASH
4614
"""
4715

48-
if redirect_response := self.maybe_https_redirect(request):
49-
return redirect_response
50-
5116
response = self.get_response(request)
5217

53-
self.set_default_headers(response)
54-
5518
"""
5619
When the status code of the response is 404, it may redirect to a path
5720
with an appended slash if should_redirect_with_slash() returns True.
5821
"""
5922
# If the given URL is "Not Found", then check if we should redirect to
6023
# a path with a slash appended.
6124
if response.status_code == 404 and self.should_redirect_with_slash(request):
62-
return self.response_redirect_class(self.get_full_path_with_slash(request))
63-
64-
# Add the Content-Length header to non-streaming responses if not
65-
# already set.
66-
if not response.streaming and not response.has_header("Content-Length"):
67-
response.headers["Content-Length"] = str(len(response.content))
25+
return ResponsePermanentRedirect(self.get_full_path_with_slash(request))
6826

6927
return response
7028

71-
def maybe_https_redirect(self, request):
72-
path = request.path.lstrip("/")
73-
if (
74-
self.https_redirect_enabled
75-
and not request.is_https()
76-
and not any(pattern.search(path) for pattern in self.https_redirect_exempt)
77-
):
78-
host = self.https_redirect_host or request.get_host()
79-
return ResponsePermanentRedirect(f"https://{host}{request.get_full_path()}")
80-
81-
def set_default_headers(self, response):
82-
for header, value in settings.DEFAULT_RESPONSE_HEADERS.items():
83-
response.headers.setdefault(header, value)
84-
8529
def should_redirect_with_slash(self, request):
8630
"""
8731
Return True if settings.APPEND_SLASH is True and appending a slash to

plain/plain/middleware/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)