-
-
Save leonardoo/9574251b3c7eefccd84fc38905110ce4 to your computer and use it in GitHub Desktop.
import functools | |
from channels.handler import AsgiRequest | |
from rest_framework.exceptions import AuthenticationFailed | |
from rest_framework.settings import api_settings | |
authenticators = [auth() for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES] | |
def rest_auth(func): | |
""" | |
Wraps a HTTP or WebSocket connect consumer (or any consumer of messages | |
that provides a "cookies" or "get" attribute) to provide a "http_session" | |
attribute that behaves like request.session; that is, it's hung off of | |
a per-user session key that is saved in a cookie or passed as the | |
"session_key" GET parameter. | |
It won't automatically create and set a session cookie for users who | |
don't have one - that's what SessionMiddleware is for, this is a simpler | |
read-only version for more low-level code. | |
If a message does not have a session we can inflate, the "session" attribute | |
will be None, rather than an empty session you can write to. | |
Does not allow a new session to be set; that must be done via a view. This | |
is only an accessor for any existing session. | |
""" | |
@functools.wraps(func) | |
def inner(message, *args, **kwargs): | |
# Make sure there's NOT a http_session already | |
try: | |
# We want to parse the WebSocket (or similar HTTP-lite) message | |
# to get cookies and GET, but we need to add in a few things that | |
# might not have been there. | |
if "method" not in message.content: | |
message.content['method'] = "FAKE" | |
request = AsgiRequest(message) | |
except Exception as e: | |
raise ValueError("Cannot parse HTTP message - are you sure this is a HTTP consumer? %s" % e) | |
# Make sure there's a session key | |
user = None | |
auth = None | |
auth_token = request.GET.get("token", None) | |
if auth_token: | |
# comptatibility with rest framework | |
request._request = {} | |
request.META["HTTP_AUTHORIZATION"] = "token {}".format(auth_token) | |
for authenticator in authenticators: | |
try: | |
user_auth_tuple = authenticator.authenticate(request) | |
except AuthenticationFailed: | |
pass | |
if user_auth_tuple is not None: | |
message._authenticator = authenticator | |
user, auth = user_auth_tuple | |
break | |
message.user, message.auth = user, auth | |
# Make sure there's a session key | |
# Run the consumer | |
result = func(message, *args, **kwargs) | |
return result | |
return inner | |
def rest_token_user(func): | |
""" | |
saf Wraps a HTTP or WebSocket consumer (or any consumer of messages | |
that provides a "COOKIES" attribute) to provide both a "session" | |
attribute and a "user" attibute, like AuthMiddleware does. | |
This runs http_session() to get a session to hook auth off of. | |
If the user does not have a session cookie set, both "session" | |
and "user" will be None. | |
""" | |
@rest_auth | |
@functools.wraps(func) | |
def inner(message, *args, **kwargs): | |
# If we didn't get a session, then we don't get a user | |
if not hasattr(message, "auth"): | |
raise ValueError("Did not see a http session to get auth from") | |
return func(message, *args, **kwargs) | |
return inner | |
class RestTokenConsumerMixin(object): | |
rest_user = False | |
def get_handler(self, message, **kwargs): | |
handler = super(RestTokenConsumerMixin, self).get_handler(message, **kwargs) | |
if self.rest_user: | |
handler = rest_token_user(handler) | |
return handler | |
def connect(self, message, **kwargs): | |
if self.rest_user and not self.message.user: | |
self.close() | |
return message |
Thanks! This is exactly what I needed. I forked and made three small edits:
- line 55 must be changed from "pass" -> "user_auth_tuple = None" if you don't want uncaught exceptions thrown when failing to authenticate
- To play with djangorestframework-jwt, 'token' should be 'JWT'. (Better still, add some logic to try both; I didn't bother here.)
- token can be provided in request header instead of query params.
https://gist.github.com/emillundh/1448c8bb2f64a607202212f259f8edbc
I found problem with disconnect after handshaking in your Mixin. Add super().connect(message, **kwargs)
to connect method in the mixin.
@aodarc I'm stuck in the disconnect after handshaking it's not getting me the message.user where should i add the super().connect(message, **kwargs) can you help me in this please ?
@ahmadhaidarahmad you should put in the connect function of the mixin
def connect(self, message, **kwargs):
if self.rest_user and not self.message.user:
self.close()
super().connect(message, **kwargs)
return message
Is there an update for Channel 2.0.2. Request is no longer there and I don't see how we can create a session without request.
https://stackoverflow.com/a/49372334/5243543
For Django-Channels 2 you can write custom authentication middleware https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a
Is this meant to work with the current version of Channels? For the life of me I cannot get it working.