mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	New dependency: sockjs-tornado One known limitation is that we don't clean up sessions for non-websockets transports. This is a bug in Tornado so I'm going to look at upgrading us to the latest version: https://github.com/mrjoes/sockjs-tornado/issues/47 (imported from commit 31cdb7596dd5ee094ab006c31757db17dca8899b)
		
			
				
	
	
		
			153 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from __future__ import absolute_import
 | 
						|
 | 
						|
from django.conf import settings
 | 
						|
from django.utils.importlib import import_module
 | 
						|
from django.utils import timezone
 | 
						|
from django.contrib.sessions.models import Session as djSession
 | 
						|
 | 
						|
import sockjs.tornado
 | 
						|
import tornado.ioloop
 | 
						|
import ujson
 | 
						|
import logging
 | 
						|
import time
 | 
						|
 | 
						|
from zerver.models import UserProfile
 | 
						|
from zerver.lib.queue import queue_json_publish
 | 
						|
 | 
						|
djsession_engine = import_module(settings.SESSION_ENGINE)
 | 
						|
def get_user_profile(session_id):
 | 
						|
    if session_id is None:
 | 
						|
        return None
 | 
						|
 | 
						|
    try:
 | 
						|
        djsession = djSession.objects.get(expire_date__gt=timezone.now(),
 | 
						|
                                          session_key=session_id)
 | 
						|
    except djSession.DoesNotExist:
 | 
						|
        return None
 | 
						|
 | 
						|
    session_store = djsession_engine.SessionStore(djsession.session_key)
 | 
						|
 | 
						|
    try:
 | 
						|
        return UserProfile.objects.get(pk=session_store['_auth_user_id'])
 | 
						|
    except UserProfile.DoesNotExist:
 | 
						|
        return None
 | 
						|
 | 
						|
connections = dict()
 | 
						|
next_connection_seq = 0
 | 
						|
 | 
						|
def get_connection(id):
 | 
						|
    return connections.get(id)
 | 
						|
 | 
						|
def register_connection(conn):
 | 
						|
    global next_connection_seq
 | 
						|
    conn.connection_id = "%s:%s" % (settings.SERVER_GENERATION, next_connection_seq)
 | 
						|
    next_connection_seq = next_connection_seq + 1
 | 
						|
    connections[conn.connection_id] = conn
 | 
						|
 | 
						|
def deregister_connection(conn):
 | 
						|
    del connections[conn.connection_id]
 | 
						|
 | 
						|
def fake_log_line(conn_info, time, ret_code, path, email):
 | 
						|
    # These two functions are copied from our middleware.  At some
 | 
						|
    # point we will just run the middleware directly.
 | 
						|
    def timedelta_ms(timedelta):
 | 
						|
        return timedelta * 1000
 | 
						|
 | 
						|
    def format_timedelta(timedelta):
 | 
						|
        if (timedelta >= 1):
 | 
						|
            return "%.1fs" % (timedelta)
 | 
						|
        return "%.0fms" % (timedelta_ms(timedelta),)
 | 
						|
 | 
						|
    logging.info('%-15s %-7s %3d %5s %s (%s)' %
 | 
						|
                 (conn_info.ip, 'SOCKET', ret_code, format_timedelta(time),
 | 
						|
                  path, email))
 | 
						|
 | 
						|
class SocketConnection(sockjs.tornado.SockJSConnection):
 | 
						|
    def on_open(self, info):
 | 
						|
        self.authenticated = False
 | 
						|
        self.session.user_profile = None
 | 
						|
        self.browser_session_id = info.get_cookie(settings.SESSION_COOKIE_NAME).value
 | 
						|
        self.csrf_token = info.get_cookie(settings.CSRF_COOKIE_NAME).value
 | 
						|
 | 
						|
        ioloop = tornado.ioloop.IOLoop.instance()
 | 
						|
        self.timeout_handle = ioloop.add_timeout(time.time() + 10, self.close)
 | 
						|
 | 
						|
        register_connection(self)
 | 
						|
        fake_log_line(info, 0, 200, 'Connection opened using %s' % (self.session.transport_name,), 'unknown')
 | 
						|
 | 
						|
    def authenticate_client(self, msg):
 | 
						|
        if self.authenticated:
 | 
						|
            self.session.send_message({'client_meta': msg['client_meta'],
 | 
						|
                                       'response': {'result': 'error', 'msg': 'Already authenticated'}})
 | 
						|
            return
 | 
						|
 | 
						|
        user_profile = get_user_profile(self.browser_session_id)
 | 
						|
        if user_profile is None:
 | 
						|
            error_msg = 'Unknown or missing session'
 | 
						|
            fake_log_line(self.session.conn_info, 0, 403, error_msg, 'unknown')
 | 
						|
            self.session.send_message({'client_meta': msg['client_meta'],
 | 
						|
                                       'response': {'result': 'error', 'msg': error_msg}})
 | 
						|
            return
 | 
						|
        self.session.user_profile = user_profile
 | 
						|
 | 
						|
        if msg['request']['csrf_token'] != self.csrf_token:
 | 
						|
            error_msg = 'CSRF token does not match that in cookie'
 | 
						|
            fake_log_line(self.session.conn_info, 0, 403, error_msg, 'unknown')
 | 
						|
            self.session.send_message({'client_meta': msg['client_meta'],
 | 
						|
                                       'response': {'result': 'error', 'msg': error_msg}})
 | 
						|
            return
 | 
						|
 | 
						|
        self.session.send_message({'client_meta': msg['client_meta'],
 | 
						|
                                   'response': {'result': 'success', 'msg': ''}})
 | 
						|
        self.authenticated = True
 | 
						|
        fake_log_line(self.session.conn_info, 0, 200, "Authenticated", user_profile.email)
 | 
						|
        ioloop = tornado.ioloop.IOLoop.instance()
 | 
						|
        ioloop.remove_timeout(self.timeout_handle)
 | 
						|
 | 
						|
    def on_message(self, msg):
 | 
						|
        start_time = time.time()
 | 
						|
        msg = ujson.loads(msg)
 | 
						|
 | 
						|
        if msg['type'] == 'auth':
 | 
						|
            self.authenticate_client(msg)
 | 
						|
            return
 | 
						|
        else:
 | 
						|
            if not self.authenticated:
 | 
						|
                error_msg = 'Not yet authenticated'
 | 
						|
                fake_log_line(self.session.conn_info, 0, 403, error_msg, 'unknown')
 | 
						|
                self.session.send_message({'client_meta': msg['client_meta'],
 | 
						|
                                           'response': {'result': 'error', 'msg': error_msg}})
 | 
						|
                return
 | 
						|
 | 
						|
        req = msg['request']
 | 
						|
        req['sender_id'] = self.session.user_profile.id
 | 
						|
        req['client_name'] = req['client']
 | 
						|
        queue_json_publish("message_sender", dict(request=req,
 | 
						|
                                                  client_meta=msg['client_meta'],
 | 
						|
                                                  server_meta=dict(connection_id=self.connection_id,
 | 
						|
                                                                   return_queue="tornado_return",
 | 
						|
                                                                   start_time=start_time)),
 | 
						|
                           lambda e: None)
 | 
						|
 | 
						|
    def on_close(self):
 | 
						|
        deregister_connection(self)
 | 
						|
        if self.session.user_profile is None:
 | 
						|
            fake_log_line(self.session.conn_info, 0, 408,
 | 
						|
                          'Timeout while waiting for authentication', 'unknown')
 | 
						|
        else:
 | 
						|
            fake_log_line(self.session.conn_info, 0, 200,
 | 
						|
                          'Connection closed', 'unknown')
 | 
						|
 | 
						|
def respond_send_message(chan, method, props, data):
 | 
						|
    connection = get_connection(data['server_meta']['connection_id'])
 | 
						|
    if connection is not None:
 | 
						|
        connection.session.send_message({'client_meta': data['client_meta'], 'response': data['response']})
 | 
						|
        fake_log_line(connection.session.conn_info,
 | 
						|
                      time.time() - data['server_meta']['start_time'],
 | 
						|
                      200, 'send_message', connection.session.user_profile.email)
 | 
						|
 | 
						|
sockjs_router = sockjs.tornado.SockJSRouter(SocketConnection, "/sockjs",
 | 
						|
                                            {'sockjs_url': 'https://%s/static/third/sockjs/sockjs-0.3.4.js' % (settings.EXTERNAL_HOST,)})
 | 
						|
def get_sockjs_router():
 | 
						|
    return sockjs_router
 |