Skip to content

Commit 46c6f35

Browse files
committed
Use the async auth api methods if they exist
1 parent 8d90b07 commit 46c6f35

File tree

1 file changed

+228
-110
lines changed

1 file changed

+228
-110
lines changed

channels/auth.py

+228-110
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import django
12
from django.conf import settings
23
from django.contrib.auth import (
34
BACKEND_SESSION_KEY,
@@ -16,127 +17,244 @@
1617
from channels.middleware import BaseMiddleware
1718
from channels.sessions import CookieMiddleware, SessionMiddleware
1819

20+
if django.VERSION >= (5, 2):
1921

20-
@database_sync_to_async
21-
def get_user(scope):
22-
"""
23-
Return the user model instance associated with the given scope.
24-
If no user is retrieved, return an instance of `AnonymousUser`.
25-
"""
26-
# postpone model import to avoid ImproperlyConfigured error before Django
27-
# setup is complete.
28-
from django.contrib.auth.models import AnonymousUser
29-
30-
if "session" not in scope:
31-
raise ValueError(
32-
"Cannot find session in scope. You should wrap your consumer in "
33-
"SessionMiddleware."
34-
)
35-
session = scope["session"]
36-
user = None
37-
try:
38-
user_id = _get_user_session_key(session)
39-
backend_path = session[BACKEND_SESSION_KEY]
40-
except KeyError:
41-
pass
42-
else:
43-
if backend_path in settings.AUTHENTICATION_BACKENDS:
44-
backend = load_backend(backend_path)
45-
user = backend.get_user(user_id)
46-
# Verify the session
47-
if hasattr(user, "get_session_auth_hash"):
48-
session_hash = session.get(HASH_SESSION_KEY)
49-
session_hash_verified = session_hash and constant_time_compare(
50-
session_hash, user.get_session_auth_hash()
22+
async def get_user(scope):
23+
"""
24+
Return the user model instance associated with the given scope.
25+
If no user is retrieved, return an instance of `AnonymousUser`.
26+
"""
27+
# postpone model import to avoid ImproperlyConfigured error before Django
28+
# setup is complete.
29+
from django.contrib.auth.models import AnonymousUser
30+
31+
if "session" not in scope:
32+
raise ValueError(
33+
"Cannot find session in scope. You should wrap your consumer in "
34+
"SessionMiddleware."
35+
)
36+
session = scope["session"]
37+
user = None
38+
try:
39+
user_id = _get_user_session_key(session)
40+
backend_path = await session.aget(BACKEND_SESSION_KEY)
41+
except KeyError:
42+
pass
43+
else:
44+
if backend_path in settings.AUTHENTICATION_BACKENDS:
45+
backend = load_backend(backend_path)
46+
user = await backend.aget_user(user_id)
47+
# Verify the session
48+
if hasattr(user, "get_session_auth_hash"):
49+
session_hash = await session.aget(HASH_SESSION_KEY)
50+
session_hash_verified = session_hash and constant_time_compare(
51+
session_hash, user.get_session_auth_hash()
52+
)
53+
if not session_hash_verified:
54+
await session.aflush()
55+
user = None
56+
return user or AnonymousUser()
57+
58+
async def login(scope, user, backend=None):
59+
"""
60+
Persist a user id and a backend in the request.
61+
This way a user doesn't have to re-authenticate on every request.
62+
Note that data set during the anonymous session is retained when the user
63+
logs in.
64+
"""
65+
if "session" not in scope:
66+
raise ValueError(
67+
"Cannot find session in scope. You should wrap your consumer in "
68+
"SessionMiddleware."
69+
)
70+
session = scope["session"]
71+
session_auth_hash = ""
72+
if user is None:
73+
user = scope.get("user", None)
74+
if user is None:
75+
raise ValueError(
76+
"User must be passed as an argument or must be present in the scope."
77+
)
78+
if hasattr(user, "get_session_auth_hash"):
79+
session_auth_hash = user.get_session_auth_hash()
80+
if SESSION_KEY in session:
81+
if _get_user_session_key(session) != user.pk or (
82+
session_auth_hash
83+
and not constant_time_compare(
84+
await session.aget(HASH_SESSION_KEY, ""), session_auth_hash
85+
)
86+
):
87+
# To avoid reusing another user's session, create a new, empty
88+
# session if the existing session corresponds to a different
89+
# authenticated user.
90+
await session.aflush()
91+
else:
92+
await session.acycle_key()
93+
try:
94+
backend = backend or user.backend
95+
except AttributeError:
96+
backends = _get_backends(return_tuples=True)
97+
if len(backends) == 1:
98+
_, backend = backends[0]
99+
else:
100+
raise ValueError(
101+
"You have multiple authentication backends configured and "
102+
"therefore must provide the `backend` "
103+
"argument or set the `backend` attribute on the user."
51104
)
52-
if not session_hash_verified:
53-
session.flush()
54-
user = None
55-
return user or AnonymousUser()
105+
await session.aset(SESSION_KEY, user._meta.pk.value_to_string(user))
106+
await session.aset(BACKEND_SESSION_KEY, backend)
107+
await session.aset(HASH_SESSION_KEY, session_auth_hash)
108+
scope["user"] = user
109+
# note this does not reset the CSRF_COOKIE/Token
110+
await user_logged_in.asend(sender=user.__class__, request=None, user=user)
56111

112+
async def logout(scope):
113+
"""
114+
Remove the authenticated user's ID from the request and flush their session
115+
data.
116+
"""
117+
# postpone model import to avoid ImproperlyConfigured error before Django
118+
# setup is complete.
119+
from django.contrib.auth.models import AnonymousUser
57120

58-
@database_sync_to_async
59-
def login(scope, user, backend=None):
60-
"""
61-
Persist a user id and a backend in the request.
62-
This way a user doesn't have to re-authenticate on every request.
63-
Note that data set during the anonymous session is retained when the user
64-
logs in.
65-
"""
66-
if "session" not in scope:
67-
raise ValueError(
68-
"Cannot find session in scope. You should wrap your consumer in "
69-
"SessionMiddleware."
70-
)
71-
session = scope["session"]
72-
session_auth_hash = ""
73-
if user is None:
121+
if "session" not in scope:
122+
raise ValueError(
123+
"Login cannot find session in scope. You should wrap your "
124+
"consumer in SessionMiddleware."
125+
)
126+
session = scope["session"]
127+
# Dispatch the signal before the user is logged out so the receivers have a
128+
# chance to find out *who* logged out.
74129
user = scope.get("user", None)
75-
if user is None:
76-
raise ValueError(
77-
"User must be passed as an argument or must be present in the scope."
78-
)
79-
if hasattr(user, "get_session_auth_hash"):
80-
session_auth_hash = user.get_session_auth_hash()
81-
if SESSION_KEY in session:
82-
if _get_user_session_key(session) != user.pk or (
83-
session_auth_hash
84-
and not constant_time_compare(
85-
session.get(HASH_SESSION_KEY, ""), session_auth_hash
130+
if hasattr(user, "is_authenticated") and not user.is_authenticated:
131+
user = None
132+
if user is not None:
133+
await user_logged_out.asend(sender=user.__class__, request=None, user=user)
134+
await session.aflush()
135+
if "user" in scope:
136+
scope["user"] = AnonymousUser()
137+
138+
else:
139+
140+
@database_sync_to_async
141+
def get_user(scope):
142+
"""
143+
Return the user model instance associated with the given scope.
144+
If no user is retrieved, return an instance of `AnonymousUser`.
145+
"""
146+
# postpone model import to avoid ImproperlyConfigured error before Django
147+
# setup is complete.
148+
from django.contrib.auth.models import AnonymousUser
149+
150+
if "session" not in scope:
151+
raise ValueError(
152+
"Cannot find session in scope. You should wrap your consumer in "
153+
"SessionMiddleware."
86154
)
87-
):
88-
# To avoid reusing another user's session, create a new, empty
89-
# session if the existing session corresponds to a different
90-
# authenticated user.
91-
session.flush()
92-
else:
93-
session.cycle_key()
94-
try:
95-
backend = backend or user.backend
96-
except AttributeError:
97-
backends = _get_backends(return_tuples=True)
98-
if len(backends) == 1:
99-
_, backend = backends[0]
155+
session = scope["session"]
156+
user = None
157+
try:
158+
user_id = _get_user_session_key(session)
159+
backend_path = session[BACKEND_SESSION_KEY]
160+
except KeyError:
161+
pass
100162
else:
163+
if backend_path in settings.AUTHENTICATION_BACKENDS:
164+
backend = load_backend(backend_path)
165+
user = backend.get_user(user_id)
166+
# Verify the session
167+
if hasattr(user, "get_session_auth_hash"):
168+
session_hash = session.get(HASH_SESSION_KEY)
169+
session_hash_verified = session_hash and constant_time_compare(
170+
session_hash, user.get_session_auth_hash()
171+
)
172+
if not session_hash_verified:
173+
session.flush()
174+
user = None
175+
return user or AnonymousUser()
176+
177+
@database_sync_to_async
178+
def login(scope, user, backend=None):
179+
"""
180+
Persist a user id and a backend in the request.
181+
This way a user doesn't have to re-authenticate on every request.
182+
Note that data set during the anonymous session is retained when the user
183+
logs in.
184+
"""
185+
if "session" not in scope:
186+
raise ValueError(
187+
"Cannot find session in scope. You should wrap your consumer in "
188+
"SessionMiddleware."
189+
)
190+
session = scope["session"]
191+
session_auth_hash = ""
192+
if user is None:
193+
user = scope.get("user", None)
194+
if user is None:
101195
raise ValueError(
102-
"You have multiple authentication backends configured and "
103-
"therefore must provide the `backend` "
104-
"argument or set the `backend` attribute on the user."
196+
"User must be passed as an argument or must be present in the scope."
105197
)
106-
session[SESSION_KEY] = user._meta.pk.value_to_string(user)
107-
session[BACKEND_SESSION_KEY] = backend
108-
session[HASH_SESSION_KEY] = session_auth_hash
109-
scope["user"] = user
110-
# note this does not reset the CSRF_COOKIE/Token
111-
user_logged_in.send(sender=user.__class__, request=None, user=user)
198+
if hasattr(user, "get_session_auth_hash"):
199+
session_auth_hash = user.get_session_auth_hash()
200+
if SESSION_KEY in session:
201+
if _get_user_session_key(session) != user.pk or (
202+
session_auth_hash
203+
and not constant_time_compare(
204+
session.get(HASH_SESSION_KEY, ""), session_auth_hash
205+
)
206+
):
207+
# To avoid reusing another user's session, create a new, empty
208+
# session if the existing session corresponds to a different
209+
# authenticated user.
210+
session.flush()
211+
else:
212+
session.cycle_key()
213+
try:
214+
backend = backend or user.backend
215+
except AttributeError:
216+
backends = _get_backends(return_tuples=True)
217+
if len(backends) == 1:
218+
_, backend = backends[0]
219+
else:
220+
raise ValueError(
221+
"You have multiple authentication backends configured and "
222+
"therefore must provide the `backend` "
223+
"argument or set the `backend` attribute on the user."
224+
)
225+
session[SESSION_KEY] = user._meta.pk.value_to_string(user)
226+
session[BACKEND_SESSION_KEY] = backend
227+
session[HASH_SESSION_KEY] = session_auth_hash
228+
scope["user"] = user
229+
# note this does not reset the CSRF_COOKIE/Token
230+
user_logged_in.send(sender=user.__class__, request=None, user=user)
112231

232+
@database_sync_to_async
233+
def logout(scope):
234+
"""
235+
Remove the authenticated user's ID from the request and flush their session
236+
data.
237+
"""
238+
# postpone model import to avoid ImproperlyConfigured error before Django
239+
# setup is complete.
240+
from django.contrib.auth.models import AnonymousUser
113241

114-
@database_sync_to_async
115-
def logout(scope):
116-
"""
117-
Remove the authenticated user's ID from the request and flush their session
118-
data.
119-
"""
120-
# postpone model import to avoid ImproperlyConfigured error before Django
121-
# setup is complete.
122-
from django.contrib.auth.models import AnonymousUser
123-
124-
if "session" not in scope:
125-
raise ValueError(
126-
"Login cannot find session in scope. You should wrap your "
127-
"consumer in SessionMiddleware."
128-
)
129-
session = scope["session"]
130-
# Dispatch the signal before the user is logged out so the receivers have a
131-
# chance to find out *who* logged out.
132-
user = scope.get("user", None)
133-
if hasattr(user, "is_authenticated") and not user.is_authenticated:
134-
user = None
135-
if user is not None:
136-
user_logged_out.send(sender=user.__class__, request=None, user=user)
137-
session.flush()
138-
if "user" in scope:
139-
scope["user"] = AnonymousUser()
242+
if "session" not in scope:
243+
raise ValueError(
244+
"Login cannot find session in scope. You should wrap your "
245+
"consumer in SessionMiddleware."
246+
)
247+
session = scope["session"]
248+
# Dispatch the signal before the user is logged out so the receivers have a
249+
# chance to find out *who* logged out.
250+
user = scope.get("user", None)
251+
if hasattr(user, "is_authenticated") and not user.is_authenticated:
252+
user = None
253+
if user is not None:
254+
user_logged_out.send(sender=user.__class__, request=None, user=user)
255+
session.flush()
256+
if "user" in scope:
257+
scope["user"] = AnonymousUser()
140258

141259

142260
def _get_user_session_key(session):

0 commit comments

Comments
 (0)