Требовать входа в сокет Django Channels?

Я тестирую каналы в Django 1.10 и настраиваю несколько пользователей.

Я попробовал создать декоратор login_required для него, который закрывает соединение, прежде чем выполнять его, чтобы гости не вошли в этот приватный сокет. Кроме того, после этого интегрированные модульные тесты проверяют, и они продолжают терпеть неудачу, потому что они продолжают позволять гостям (ошибки AnonymousUser повсюду).

Кроме того, иногда при входе в систему и выходе из нее сеанс не очищается, и он позволяет старому пользователю входить.

Декоратор:

def login_required_websocket(func):
    """
    If user is not logged in, close connection immediately.
    """
    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        if not message.user.is_authenticated():
            message.reply_channel.send({'close': True})
        return func(message, *args, **kwargs)
    return inner

Здесь потребительский код:

def ws_connect(message, slug):
    message.reply_channel.send({ 'accept': True })
    client = message.reply_channel
    client.send(signal.message("Welcome"))
    try:
        # import pdb; pdb.set_trace()
        Room.objects.get(name=slug)
    except Room.DoesNotExist:
        room = Room.objects.create(name=slug)
        room.users.add(message.user)
        room.turn = message.user.id
        room.save()
        story = Story(room=room)
        story.save()


    # We made sure it exists.
    room = Room.objects.get(name=slug)
    message.channel_session['room'] = room.name

    # Check if user is allowed here.
    if not room.user_allowed(message.user):
        # Close the connection. User is not allowed.
        client.send(Signal.error("User isn't allowed in this room."))
        client.send({'close': True})

Странная вещь, когда комментирует всю логику между client.send(signal.message)) вперед, она работает просто отлично, а модульные тесты проходят (что означает, что гости заблокированы, а auth-код не запускается [следовательно, ошибки AnonymousUser]). Любые идеи?

Здесь также тесты:

class RoomsTests(ChannelTestCase):

    def test_reject_guest(self):
        """
        This tests whether the login_required_websocket decorator is rejecting guests.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')

        client.send_and_consume('websocket.connect',
                                path='/rooms/test_room', check_accept=False)
        self.assertEqual(client.receive(), {'close': True})

    def test_accept_logged_in(self):
        """
        This tests whether the connection is accepted when a user is logged in.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')
        client.login(username='test', password='password')

        client.send_and_consume('websocket.connect', path='/rooms/test_room')

Я подхожу к этому неправильно, и если да, то как мне это сделать (требуется auth) правильно?

EDIT: интегрированная система действий, чтобы что-то попробовать, похоже, что каналы Django просто не собирают какие-либо сеансы из HTTP.

@enforce_ordering
@channel_session_user_from_http
def ws_connect(message, slug):
    message.reply_channel.send({'accept': True})
    message.reply_channel.send(Action.info(message.user.is_authenticated()).to_send())

Просто возвращает false.

EDIT2: Я вижу, что он работает сейчас, я попытался изменить localhost на 127.0.0.1 и теперь он работает. Есть ли способ, чтобы он обнаруживал localhost как допустимый домен, чтобы он проходил через сеансы?

EDIT3: Оказывается, я нашел localhost vs 127.0.0.1 cookie issue haha. Чтобы не тратить деньги на награду, как бы вы лично реализовали auth login_required в сообщениях/каналах?

edit4: Пока я до сих пор не знаю, почему это не сработало, вот как я в конце концов изменил приложение вокруг проблемы:

Я создал систему действий. При входе в розетку сокет ничего не делает, пока вы не отправите ему действие AUTHENTICATE через JSON. Я разделял вход в действия в guest_actions и user_actions. После аутентификации он устанавливает сеанс, и вы можете использовать user_actions.

Ответы

Ответ 1

Каналы Django уже поддерживают аутентификацию сеанса:

# In consumers.py
from channels import Channel, Group
from channels.sessions import channel_session
from channels.auth import channel_session_user, channel_session_user_from_http

# Connected to websocket.connect
@channel_session_user_from_http
def ws_add(message):
    # Accept connection
    message.reply_channel.send({"accept": True})
    # Add them to the right group
    Group("chat-%s" % message.user.username[0]).add(message.reply_channel)

# Connected to websocket.receive
@channel_session_user
def ws_message(message):
    Group("chat-%s" % message.user.username[0]).send({
        "text": message['text'],
    })

# Connected to websocket.disconnect
@channel_session_user
def ws_disconnect(message):
    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)

http://channels.readthedocs.io/en/stable/getting-started.html#authentication

Ответ 2

Ваша функция работала "как есть" для меня. Прежде чем я просмотрю детали, появилась ошибка (теперь разрешена), которая препятствовала закрытию сеансов, что может объяснить вашу другую проблему.

Я использую ограниченные кавычки вокруг "as-is", потому что я использовал потребитель на основе класса, поэтому мне пришлось добавить self ко всему стеке декораторов, чтобы проверить его явно:

class MyRouter(WebsocketDemultiplexer):
    # WebsocketDemultiplexer calls raw_connect for websocket.connect
    @channel_session_user_from_http
    @login_required_websocket
    def raw_connect(self, message, **kwargs):
        ...

После добавления некоторых отладочных сообщений для проверки последовательности выполнения:

>>> ws = create_connection("ws://localhost:8085")

# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: AnonymousUser

# client logging
websocket._exceptions.WebSocketBadStatusException: Handshake status 403

>>> ws = create_connection("ws://localhost:8085", cookie='sessionid=43jxki76cdjl97b8krco0ze2lsqp6pcg')

# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: admin

Как вы можете видеть из моего фрагмента, сначала нужно вызвать @channel_session_user_from_http. Для пользователей, основанных на функциях, вы можете упростить это, включив его в свой декоратор:

def login_required_websocket(func):
    @channel_session_user_from_http
    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        ...

В отношении пользователей на основе классов это обрабатывается внутренне (и в правильном порядке), устанавливая http_user_and_session:

class MyRouter(WebsocketDemultiplexer):
    http_user_and_session = True

Здесь приведен полный код для декоратора self, который будет использоваться с ним:

def login_required_websocket(func):
    """
    If user is not logged in, close connection immediately.
    """
    @functools.wraps(func)
    def inner(self, message, *args, **kwargs):
        if not message.user.is_authenticated():
            message.reply_channel.send({'close': True})
        return func(self, message, *args, **kwargs)
    return inner

Ответ 3

Мое предложение заключается в том, что вам может потребоваться сеансовый ключ или, что еще лучше, ввести имя пользователя/пароль, введенные в вашем потребительском методе. Затем вызовите метод authenticate, чтобы проверить, существует ли пользователь. При возврате действительного пользовательского объекта вы можете отправить сообщение или вернуть сообщение с неверными данными входа.

from django.contrib.auth import authenticate

@channel_session_user
def ws_message(message):
    user = authenticate(username=message.username, password=message.password')
     if user is not None: 
        Group("chat-%s" % message.user.username[0]).send({
              "text": message['text'],
              })
     else:
           # User is not authenticated so return an error message.