plain.auth 0.19.0__tar.gz → 0.20.0__tar.gz
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_auth-0.19.0 → plain_auth-0.20.0}/.gitignore +1 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/PKG-INFO +1 -2
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/CHANGELOG.md +16 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/README.md +0 -1
- plain_auth-0.20.0/plain/auth/__init__.py +9 -0
- plain_auth-0.20.0/plain/auth/requests.py +38 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/sessions.py +30 -22
- plain_auth-0.20.0/plain/auth/templates.py +14 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/test.py +14 -7
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/views.py +51 -26
- {plain_auth-0.19.0 → plain_auth-0.20.0}/pyproject.toml +1 -1
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/app/settings.py +0 -1
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/app/urls.py +4 -1
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/test_views.py +2 -1
- plain_auth-0.19.0/plain/auth/__init__.py +0 -8
- plain_auth-0.19.0/plain/auth/middleware.py +0 -33
- {plain_auth-0.19.0 → plain_auth-0.20.0}/LICENSE +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/README.md +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/default_settings.py +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/plain/auth/utils.py +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/app/users/migrations/0001_initial.py +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/app/users/migrations/__init__.py +0 -0
- {plain_auth-0.19.0 → plain_auth-0.20.0}/tests/app/users/models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plain.auth
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Add users to your app and decide what they can access.
|
|
5
5
|
Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -64,7 +64,6 @@ INSTALLED_PACKAGES = [
|
|
|
64
64
|
|
|
65
65
|
MIDDLEWARE = [
|
|
66
66
|
"plain.sessions.middleware.SessionMiddleware",
|
|
67
|
-
"plain.auth.middleware.AuthenticationMiddleware",
|
|
68
67
|
]
|
|
69
68
|
|
|
70
69
|
AUTH_USER_MODEL = "users.User"
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# plain-auth changelog
|
|
2
2
|
|
|
3
|
+
## [0.20.0](https://github.com/dropseed/plain/releases/plain-auth@0.20.0) (2025-10-02)
|
|
4
|
+
|
|
5
|
+
### What's changed
|
|
6
|
+
|
|
7
|
+
- Removed `AuthenticationMiddleware` - authentication is now handled through request-based functions instead of middleware ([154ee10](https://github.com/dropseed/plain/commit/154ee10))
|
|
8
|
+
- Replaced `request.user` attribute with `get_request_user(request)` function and `{{ get_current_user() }}` template global ([154ee10](https://github.com/dropseed/plain/commit/154ee10))
|
|
9
|
+
- `AuthViewMixin` now provides a `self.user` property for accessing the authenticated user in views ([154ee10](https://github.com/dropseed/plain/commit/154ee10))
|
|
10
|
+
- Renamed `get_user` to `get_request_user` in the public API ([154ee10](https://github.com/dropseed/plain/commit/154ee10))
|
|
11
|
+
|
|
12
|
+
### Upgrade instructions
|
|
13
|
+
|
|
14
|
+
- Remove `plain.auth.middleware.AuthenticationMiddleware` from your `MIDDLEWARE` setting
|
|
15
|
+
- In views, use `AuthViewMixin` for access to `self.user` instead of `self.request.user`
|
|
16
|
+
- Replace `request.user` with `get_request_user(request)` in code outside of `AuthViewMixin` views
|
|
17
|
+
- In templates, replace `{{ request.user }}` with `{{ user }}` (from `AuthViewMixin`) or with `{{ get_current_user() }}`
|
|
18
|
+
|
|
3
19
|
## [0.19.0](https://github.com/dropseed/plain/releases/plain-auth@0.19.0) (2025-09-30)
|
|
4
20
|
|
|
5
21
|
### What's changed
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from weakref import WeakKeyDictionary
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from plain.http import Request
|
|
8
|
+
|
|
9
|
+
from .sessions import get_user_model
|
|
10
|
+
|
|
11
|
+
User = get_user_model()
|
|
12
|
+
|
|
13
|
+
_request_users: WeakKeyDictionary[Request, User | None] = WeakKeyDictionary()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def set_request_user(request: Request, user: User | None) -> None:
|
|
17
|
+
"""Store the authenticated user for this request."""
|
|
18
|
+
_request_users[request] = user
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_request_user(request: Request) -> User | None:
|
|
22
|
+
"""
|
|
23
|
+
Get the authenticated user for this request, if any.
|
|
24
|
+
|
|
25
|
+
Lazily loads the user from the session on first access.
|
|
26
|
+
"""
|
|
27
|
+
if request not in _request_users:
|
|
28
|
+
from .sessions import get_user
|
|
29
|
+
|
|
30
|
+
user = get_user(request)
|
|
31
|
+
|
|
32
|
+
# Don't need to store a bunch of None values
|
|
33
|
+
if not user:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
_request_users[request] = user
|
|
37
|
+
|
|
38
|
+
return _request_users[request]
|
|
@@ -3,9 +3,12 @@ import hmac
|
|
|
3
3
|
from plain.exceptions import ImproperlyConfigured
|
|
4
4
|
from plain.models import models_registry
|
|
5
5
|
from plain.runtime import settings
|
|
6
|
+
from plain.sessions import get_request_session
|
|
6
7
|
from plain.utils.crypto import salted_hmac
|
|
7
8
|
from plain.utils.encoding import force_bytes
|
|
8
9
|
|
|
10
|
+
from .requests import get_request_user, set_request_user
|
|
11
|
+
|
|
9
12
|
USER_ID_SESSION_KEY = "_auth_user_id"
|
|
10
13
|
USER_HASH_SESSION_KEY = "_auth_user_hash"
|
|
11
14
|
|
|
@@ -26,9 +29,11 @@ def update_session_auth_hash(request, user):
|
|
|
26
29
|
prevent a password change from logging out the session from which the
|
|
27
30
|
password was changed.
|
|
28
31
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
session = get_request_session(request)
|
|
34
|
+
session.cycle_key()
|
|
35
|
+
if get_request_user(request) == user:
|
|
36
|
+
session[USER_HASH_SESSION_KEY] = get_session_auth_hash(user)
|
|
32
37
|
|
|
33
38
|
|
|
34
39
|
def get_session_auth_fallback_hash(user):
|
|
@@ -52,33 +57,34 @@ def login(request, user):
|
|
|
52
57
|
have to reauthenticate on every request. Note that data set during
|
|
53
58
|
the anonymous session is retained when the user logs in.
|
|
54
59
|
"""
|
|
60
|
+
session = get_request_session(request)
|
|
61
|
+
|
|
55
62
|
if settings.AUTH_USER_SESSION_HASH_FIELD:
|
|
56
63
|
session_auth_hash = get_session_auth_hash(user)
|
|
57
64
|
else:
|
|
58
65
|
session_auth_hash = ""
|
|
59
66
|
|
|
60
|
-
if USER_ID_SESSION_KEY in
|
|
61
|
-
if int(
|
|
67
|
+
if USER_ID_SESSION_KEY in session:
|
|
68
|
+
if int(session[USER_ID_SESSION_KEY]) != user.id:
|
|
62
69
|
# To avoid reusing another user's session, create a new, empty
|
|
63
70
|
# session if the existing session corresponds to a different
|
|
64
71
|
# authenticated user.
|
|
65
|
-
|
|
72
|
+
session.flush()
|
|
66
73
|
elif session_auth_hash and not hmac.compare_digest(
|
|
67
|
-
force_bytes(
|
|
74
|
+
force_bytes(session.get(USER_HASH_SESSION_KEY, "")),
|
|
68
75
|
force_bytes(session_auth_hash),
|
|
69
76
|
):
|
|
70
77
|
# If the session hash does not match the current hash, reset the
|
|
71
78
|
# session. Most likely this means the password was changed.
|
|
72
|
-
|
|
79
|
+
session.flush()
|
|
73
80
|
else:
|
|
74
81
|
# Invalidate the current session key and generate a new one to enhance security,
|
|
75
82
|
# typically done after user login to prevent session fixation attacks.
|
|
76
|
-
|
|
83
|
+
session.cycle_key()
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
request.user = user
|
|
85
|
+
session[USER_ID_SESSION_KEY] = user.id
|
|
86
|
+
session[USER_HASH_SESSION_KEY] = session_auth_hash
|
|
87
|
+
set_request_user(request, user)
|
|
82
88
|
|
|
83
89
|
|
|
84
90
|
def logout(request):
|
|
@@ -88,9 +94,9 @@ def logout(request):
|
|
|
88
94
|
"""
|
|
89
95
|
# Dispatch the signal before the user is logged out so the receivers have a
|
|
90
96
|
# chance to find out *who* logged out.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
session = get_request_session(request)
|
|
98
|
+
session.flush()
|
|
99
|
+
set_request_user(request, None)
|
|
94
100
|
|
|
95
101
|
|
|
96
102
|
def get_user_model():
|
|
@@ -114,12 +120,14 @@ def get_user(request):
|
|
|
114
120
|
Return the user model instance associated with the given request session.
|
|
115
121
|
If no user is retrieved, return None.
|
|
116
122
|
"""
|
|
117
|
-
|
|
123
|
+
session = get_request_session(request)
|
|
124
|
+
|
|
125
|
+
if USER_ID_SESSION_KEY not in session:
|
|
118
126
|
return None
|
|
119
127
|
|
|
120
128
|
UserModel = get_user_model()
|
|
121
129
|
try:
|
|
122
|
-
user = UserModel.query.get(id=
|
|
130
|
+
user = UserModel.query.get(id=session[USER_ID_SESSION_KEY])
|
|
123
131
|
except UserModel.DoesNotExist:
|
|
124
132
|
return None
|
|
125
133
|
|
|
@@ -130,7 +138,7 @@ def get_user(request):
|
|
|
130
138
|
# If it has changed (i.e. password changed), then the session
|
|
131
139
|
# is no longer valid and cleared out.
|
|
132
140
|
if settings.AUTH_USER_SESSION_HASH_FIELD:
|
|
133
|
-
session_hash =
|
|
141
|
+
session_hash = session.get(USER_HASH_SESSION_KEY)
|
|
134
142
|
if not session_hash:
|
|
135
143
|
session_hash_verified = False
|
|
136
144
|
else:
|
|
@@ -148,10 +156,10 @@ def get_user(request):
|
|
|
148
156
|
)
|
|
149
157
|
for fallback_auth_hash in get_session_auth_fallback_hash(user)
|
|
150
158
|
):
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
session.cycle_key()
|
|
160
|
+
session[USER_HASH_SESSION_KEY] = session_auth_hash
|
|
153
161
|
else:
|
|
154
|
-
|
|
162
|
+
session.flush()
|
|
155
163
|
user = None
|
|
156
164
|
|
|
157
165
|
return user
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from jinja2 import pass_context
|
|
2
|
+
|
|
3
|
+
from plain.templates import register_template_global
|
|
4
|
+
|
|
5
|
+
from .requests import get_request_user
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@register_template_global
|
|
9
|
+
@pass_context
|
|
10
|
+
def get_current_user(context):
|
|
11
|
+
"""Get the authenticated user for the current request."""
|
|
12
|
+
request = context.get("request")
|
|
13
|
+
assert request is not None, "No request in template context"
|
|
14
|
+
return get_request_user(request)
|
|
@@ -3,7 +3,9 @@ from http.cookies import SimpleCookie
|
|
|
3
3
|
from plain.http.request import Request
|
|
4
4
|
from plain.runtime import settings
|
|
5
5
|
from plain.sessions import SessionStore
|
|
6
|
+
from plain.sessions.requests import get_request_session, set_request_session
|
|
6
7
|
|
|
8
|
+
from .requests import set_request_user
|
|
7
9
|
from .sessions import get_user, login, logout
|
|
8
10
|
|
|
9
11
|
|
|
@@ -11,13 +13,15 @@ def login_client(client, user):
|
|
|
11
13
|
"""Log a user into a test client."""
|
|
12
14
|
request = Request()
|
|
13
15
|
if client.session:
|
|
14
|
-
|
|
16
|
+
session = client.session
|
|
15
17
|
else:
|
|
16
|
-
|
|
18
|
+
session = SessionStore()
|
|
19
|
+
set_request_session(request, session)
|
|
17
20
|
login(request, user)
|
|
18
|
-
|
|
21
|
+
session = get_request_session(request)
|
|
22
|
+
session.save()
|
|
19
23
|
session_cookie = settings.SESSION_COOKIE_NAME
|
|
20
|
-
client.cookies[session_cookie] =
|
|
24
|
+
client.cookies[session_cookie] = session.session_key
|
|
21
25
|
cookie_data = {
|
|
22
26
|
"max-age": None,
|
|
23
27
|
"path": "/",
|
|
@@ -32,9 +36,12 @@ def logout_client(client):
|
|
|
32
36
|
"""Log out a user from a test client."""
|
|
33
37
|
request = Request()
|
|
34
38
|
if client.session:
|
|
35
|
-
|
|
36
|
-
request
|
|
39
|
+
session = client.session
|
|
40
|
+
set_request_session(request, session)
|
|
41
|
+
user = get_user(request)
|
|
42
|
+
set_request_user(request, user)
|
|
37
43
|
else:
|
|
38
|
-
|
|
44
|
+
session = SessionStore()
|
|
45
|
+
set_request_session(request, session)
|
|
39
46
|
logout(request)
|
|
40
47
|
client.cookies = SimpleCookie()
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
1
5
|
from urllib.parse import urlparse, urlunparse
|
|
2
6
|
|
|
3
7
|
from plain.exceptions import PermissionDenied
|
|
@@ -8,6 +12,7 @@ from plain.http import (
|
|
|
8
12
|
ResponseRedirect,
|
|
9
13
|
)
|
|
10
14
|
from plain.runtime import settings
|
|
15
|
+
from plain.sessions.views import SessionViewMixin
|
|
11
16
|
from plain.urls import reverse
|
|
12
17
|
from plain.utils.cache import patch_cache_control
|
|
13
18
|
from plain.views import View
|
|
@@ -15,6 +20,13 @@ from plain.views import View
|
|
|
15
20
|
from .sessions import logout
|
|
16
21
|
from .utils import resolve_url
|
|
17
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from plain.http import Request
|
|
25
|
+
|
|
26
|
+
from .sessions import get_user_model
|
|
27
|
+
|
|
28
|
+
User = get_user_model()
|
|
29
|
+
|
|
18
30
|
|
|
19
31
|
class LoginRequired(Exception):
|
|
20
32
|
def __init__(self, login_url=None, redirect_field_name="next"):
|
|
@@ -22,43 +34,53 @@ class LoginRequired(Exception):
|
|
|
22
34
|
self.redirect_field_name = redirect_field_name
|
|
23
35
|
|
|
24
36
|
|
|
25
|
-
class AuthViewMixin:
|
|
26
|
-
login_required =
|
|
27
|
-
admin_required = False
|
|
37
|
+
class AuthViewMixin(SessionViewMixin):
|
|
38
|
+
login_required = False
|
|
39
|
+
admin_required = False # Implies login_required
|
|
28
40
|
login_url = settings.AUTH_LOGIN_URL
|
|
29
41
|
|
|
42
|
+
request: Request
|
|
43
|
+
|
|
44
|
+
@cached_property
|
|
45
|
+
def user(self) -> User | None:
|
|
46
|
+
"""Get the authenticated user for this request."""
|
|
47
|
+
from .requests import get_request_user
|
|
48
|
+
|
|
49
|
+
return get_request_user(self.request)
|
|
50
|
+
|
|
51
|
+
def get_template_context(self) -> dict:
|
|
52
|
+
"""Add user and impersonator to template context."""
|
|
53
|
+
context = super().get_template_context()
|
|
54
|
+
context["user"] = self.user
|
|
55
|
+
return context
|
|
56
|
+
|
|
30
57
|
def check_auth(self) -> None:
|
|
31
58
|
"""
|
|
32
59
|
Raises either LoginRequired or PermissionDenied.
|
|
33
60
|
- LoginRequired can specify a login_url and redirect_field_name
|
|
34
61
|
- PermissionDenied can specify a message
|
|
35
62
|
"""
|
|
63
|
+
if not self.login_required and not self.admin_required:
|
|
64
|
+
return None
|
|
36
65
|
|
|
37
|
-
if not
|
|
38
|
-
raise AttributeError(
|
|
39
|
-
"AuthViewMixin requires the request attribute to be set."
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if self.login_required and not self.request.user:
|
|
66
|
+
if not self.user:
|
|
43
67
|
raise LoginRequired(login_url=self.login_url)
|
|
44
68
|
|
|
45
|
-
if
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
if self.admin_required:
|
|
70
|
+
# At this point, we know user is authenticated (from check above)
|
|
71
|
+
# Check if impersonation is active
|
|
72
|
+
if impersonator := getattr(self, "impersonator", None):
|
|
73
|
+
# Impersonators should be able to view admin pages while impersonating.
|
|
74
|
+
# There's probably never a case where an impersonator isn't admin, but it can be configured.
|
|
75
|
+
if not impersonator.is_admin:
|
|
76
|
+
raise PermissionDenied(
|
|
77
|
+
"You do not have permission to access this page."
|
|
78
|
+
)
|
|
79
|
+
elif not self.user.is_admin:
|
|
80
|
+
# Show a 404 so we don't expose admin urls to non-admin users
|
|
81
|
+
raise Http404()
|
|
55
82
|
|
|
56
83
|
def get_response(self) -> Response:
|
|
57
|
-
if not hasattr(self, "request"):
|
|
58
|
-
raise AttributeError(
|
|
59
|
-
"AuthViewMixin requires the request attribute to be set."
|
|
60
|
-
)
|
|
61
|
-
|
|
62
84
|
try:
|
|
63
85
|
self.check_auth()
|
|
64
86
|
except LoginRequired as e:
|
|
@@ -85,8 +107,11 @@ class AuthViewMixin:
|
|
|
85
107
|
raise PermissionDenied("Login required")
|
|
86
108
|
|
|
87
109
|
response = super().get_response()
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
|
|
111
|
+
if self.user:
|
|
112
|
+
# Make sure it at least has private as a default
|
|
113
|
+
patch_cache_control(response, private=True)
|
|
114
|
+
|
|
90
115
|
return response
|
|
91
116
|
|
|
92
117
|
|
|
@@ -9,12 +9,14 @@ class LoginView(View):
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ProtectedView(AuthViewMixin, View):
|
|
12
|
+
login_required = True
|
|
13
|
+
|
|
12
14
|
def get(self):
|
|
13
15
|
return "protected"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class OpenView(AuthViewMixin, View):
|
|
17
|
-
login_required = False
|
|
19
|
+
# login_required = False
|
|
18
20
|
|
|
19
21
|
def get(self):
|
|
20
22
|
return "open"
|
|
@@ -28,6 +30,7 @@ class AdminView(AuthViewMixin, View):
|
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class NoLoginUrlView(AuthViewMixin, View):
|
|
33
|
+
login_required = True
|
|
31
34
|
login_url = None
|
|
32
35
|
|
|
33
36
|
def get(self):
|
|
@@ -14,7 +14,7 @@ def test_view_without_login_required(db):
|
|
|
14
14
|
response = client.get("/open/")
|
|
15
15
|
assert response.status_code == 200
|
|
16
16
|
assert response.content == b"open"
|
|
17
|
-
assert
|
|
17
|
+
assert "Cache-Control" not in response.headers
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def test_admin_required(db):
|
|
@@ -33,6 +33,7 @@ def test_admin_required(db):
|
|
|
33
33
|
resp = client.get("/admin/")
|
|
34
34
|
assert resp.status_code == 200
|
|
35
35
|
assert resp.content == b"admin"
|
|
36
|
+
assert resp.headers["Cache-Control"] == "private"
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def test_no_login_url_forbidden(db):
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from opentelemetry import trace
|
|
2
|
-
from opentelemetry.semconv._incubating.attributes.user_attributes import USER_ID
|
|
3
|
-
|
|
4
|
-
from plain import auth
|
|
5
|
-
from plain.exceptions import ImproperlyConfigured
|
|
6
|
-
from plain.utils.functional import SimpleLazyObject
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_user(request):
|
|
10
|
-
if not hasattr(request, "_cached_user"):
|
|
11
|
-
request._cached_user = auth.get_user(request)
|
|
12
|
-
if request._cached_user:
|
|
13
|
-
trace.get_current_span().set_attribute(USER_ID, request._cached_user.id)
|
|
14
|
-
return request._cached_user
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class AuthenticationMiddleware:
|
|
18
|
-
def __init__(self, get_response):
|
|
19
|
-
self.get_response = get_response
|
|
20
|
-
|
|
21
|
-
def __call__(self, request):
|
|
22
|
-
if not hasattr(request, "session"):
|
|
23
|
-
raise ImproperlyConfigured(
|
|
24
|
-
"The Plain authentication middleware requires session "
|
|
25
|
-
"middleware to be installed. Edit your MIDDLEWARE setting to "
|
|
26
|
-
"insert "
|
|
27
|
-
"'plain.sessions.middleware.SessionMiddleware' before "
|
|
28
|
-
"'plain.auth.middleware.AuthenticationMiddleware'."
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
request.user = SimpleLazyObject(lambda: get_user(request))
|
|
32
|
-
|
|
33
|
-
return self.get_response(request)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|