async def open(self, path, *args, **kwargs): _, context = _APPS[path] token = self._token if self.selected_subprotocol != 'bokeh': self.close() raise ProtocolError("Subprotocol header is not 'bokeh'") elif token is None: self.close() raise ProtocolError("No token received in subprotocol header") session_id = get_session_id(token) await context.create_session_if_needed(session_id, self.request, token) session = context.get_session(session_id) try: protocol = Protocol() self.receiver = Receiver(protocol) self.handler = ProtocolHandler() self.connection = self.new_connection(protocol, context, session) except ProtocolError as e: self.close() raise e msg = self.connection.protocol.create('ACK') await self.send_message(msg)
async def _async_open(self, token: str) -> None: try: session_id = get_session_id(token) await self.application_context.create_session_if_needed( session_id, self.request, token) session = self.application_context.get_session(session_id) protocol = Protocol() self.receiver = Receiver(protocol) log.debug("Receiver created for %r", protocol) self.handler = ProtocolHandler() log.debug("ProtocolHandler created for %r", protocol) self.connection = self._new_connection(protocol, self, self.application_context, session) log.info("ServerConnection created") except Exception as e: log.error("Could not create new server session, reason: %s", e) self.close() raise e msg = self.connection.protocol.create('ACK') await self._send_bokeh_message(msg)
async def get_session(self): token = self.get_argument("bokeh-token", default=None) session_id = self.get_argument("bokeh-session-id", default=None) if 'Bokeh-Session-Id' in self.request.headers: if session_id is not None: log.debug("Server received session ID in request argument and header, expected only one") raise HTTPError(status_code=403, reason="session ID was provided as an argument and header") session_id = self.request.headers.get('Bokeh-Session-Id') if token is not None: if session_id is not None: log.debug("Server received both token and session ID, expected only one") raise HTTPError(status_code=403, reason="Both token and session ID were provided") session_id = get_session_id(token) elif session_id is None: if self.application.generate_session_ids: session_id = generate_session_id(secret_key=self.application.secret_key, signed=self.application.sign_sessions) else: log.debug("Server configured not to generate session IDs and none was provided") raise HTTPError(status_code=403, reason="No bokeh-session-id provided") if token is None: if self.application.include_headers is None: excluded_headers = (self.application.exclude_headers or []) allowed_headers = [header for header in self.request.headers if header not in excluded_headers] else: allowed_headers = self.application.include_headers headers = {k: v for k, v in self.request.headers.items() if k in allowed_headers} if self.application.include_cookies is None: excluded_cookies = (self.application.exclude_cookies or []) allowed_cookies = [cookie for cookie in self.request.cookies if cookie not in excluded_cookies] else: allowed_cookies = self.application.include_cookies cookies = {k: v.value for k, v in self.request.cookies.items() if k in allowed_cookies} payload = {'headers': headers, 'cookies': cookies} payload.update(self.application_context.application.process_request(self.request)) token = generate_jwt_token(session_id, secret_key=self.application.secret_key, signed=self.application.sign_sessions, expiration=self.application.session_token_expiration, extra_payload=payload) if not check_token_signature(token, secret_key=self.application.secret_key, signed=self.application.sign_sessions): log.error("Session id had invalid signature: %r", session_id) raise HTTPError(status_code=403, reason="Invalid token or session ID") session = await self.application_context.create_session_if_needed(session_id, self.request, token) return session
def test_payload_signed(self): session_id = generate_session_id(signed=True, secret_key="abc") token = generate_jwt_token(session_id, signed=True, secret_key="abc", extra_payload=dict(foo=10)) assert '.' in token decoded = json.loads(_base64_decode(token.split('.')[0], encoding='utf-8')) assert 'session_id' in decoded session_id = get_session_id(token) assert check_token_signature(token, secret_key="abc", signed=True) assert not check_token_signature(token, secret_key="qrs", signed=True) assert decoded['foo'] == 10
async def test__request_in_session_context_has_arguments(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: response = await http_get(server.io_loop, url(server) + "?foo=10") html = response.body token = extract_token_from_json(html) sessionid = get_session_id(token) server_session = server.get_session('/', sessionid) server_doc = server_session.document session_context = server_doc.session_context # test if we can get the argument from the request assert session_context.request.arguments['foo'] == [b'10']
async def test__request_in_session_context(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: response = await http_get(server.io_loop, url(server) + "?foo=10") html = response.body token = extract_token_from_json(html) sessionid = get_session_id(token) server_session = server.get_session('/', sessionid) server_doc = server_session.document session_context = server_doc.session_context # do we have a request assert session_context.request is not None
async def test__autocreate_session_doc(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: sessions = server.get_sessions('/') assert 0 == len(sessions) response = await http_get(server.io_loop, url(server)) html = response.body token = extract_token_from_json(html) sessionid = get_session_id(token) sessions = server.get_sessions('/') assert 1 == len(sessions) assert sessionid == sessions[0].id
async def test__no_request_arguments_in_session_context(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: response = await http_get(server.io_loop, url(server)) html = response.body token = extract_token_from_json(html) sessionid = get_session_id(token) server_session = server.get_session('/', sessionid) server_doc = server_session.document session_context = server_doc.session_context # if we do not pass any arguments to the url, the request arguments # should be empty assert len(session_context.request.arguments) == 0
async def test__autocreate_signed_session_autoload(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application, sign_sessions=True, secret_key='foo') as server: sessions = server.get_sessions('/') assert 0 == len(sessions) response = await http_get(server.io_loop, autoload_url(server)) js = response.body token = extract_token_from_json(js) sessionid = get_session_id(token) sessions = server.get_sessions('/') assert 1 == len(sessions) assert sessionid == sessions[0].id assert check_token_signature(token, signed=True, secret_key='foo')
async def test__use_provided_session_doc(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: sessions = server.get_sessions('/') assert 0 == len(sessions) expected = 'foo' response = await http_get(server.io_loop, url(server) + "?bokeh-session-id=" + expected) html = response.body token = extract_token_from_json(html) sessionid = get_session_id(token) assert expected == sessionid sessions = server.get_sessions('/') assert 1 == len(sessions) assert expected == sessions[0].id
async def test__use_provided_session_header_autoload(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: sessions = server.get_sessions('/') assert 0 == len(sessions) expected = 'foo' response = await http_get(server.io_loop, autoload_url(server), headers={'Bokeh-Session-Id': expected}) js = response.body token = extract_token_from_json(js) sessionid = get_session_id(token) assert expected == sessionid sessions = server.get_sessions('/') assert 1 == len(sessions) assert expected == sessions[0].id
def test_payload_signed(self): session_id = generate_session_id(signed=True, secret_key="abc") token = generate_jwt_token(session_id, signed=True, secret_key="abc", extra_payload=dict(foo=10)) assert '.' in token decoded = json.loads(_b64_to_utf8(token.split('.')[0])) assert _TOKEN_ZLIB_KEY in decoded assert 'session_id' in decoded session_id = get_session_id(token) assert check_token_signature(token, secret_key="abc", signed=True) assert not check_token_signature(token, secret_key="qrs", signed=True) payload = get_token_payload(token) assert _TOKEN_ZLIB_KEY not in payload assert payload['foo'] == 10
async def _async_open(self, token): ''' Perform the specific steps needed to open a connection to a Bokeh session Specifically, this method coordinates: * Getting a session for a session ID (creating a new one if needed) * Creating a protocol receiver and handler * Opening a new ServerConnection and sending it an ACK Args: session_id (str) : A session ID to for a session to connect to If no session exists with the given ID, a new session is made Returns: None ''' try: session_id = get_session_id(token) await self.application_context.create_session_if_needed( session_id, self.request, token) session = self.application_context.get_session(session_id) protocol = Protocol() self.receiver = Receiver(protocol) log.debug("Receiver created for %r", protocol) self.handler = ProtocolHandler() log.debug("ProtocolHandler created for %r", protocol) self.connection = self.application.new_connection( protocol, self, self.application_context, session) log.info("ServerConnection created") except ProtocolError as e: log.error("Could not create new server session, reason: %s", e) self.close() raise e msg = self.connection.protocol.create('ACK') await self.send_message(msg) return None
async def test__use_provided_session_autoload_token(ManagedServerLoop) -> None: application = Application() with ManagedServerLoop(application) as server: sessions = server.get_sessions('/') assert 0 == len(sessions) expected = 'foo' expected_token = generate_jwt_token(expected) response = await http_get(server.io_loop, autoload_url(server) + "&bokeh-token=" + expected_token) js = response.body token = extract_token_from_json(js) assert expected_token == token sessionid = get_session_id(token) assert expected == sessionid sessions = server.get_sessions('/') assert 1 == len(sessions) assert expected == sessions[0].id
async def connect(self): log.info('WebSocket connection opened') subprotocols = self.scope["subprotocols"] if len(subprotocols) != 2 or subprotocols[0] != 'bokeh': self.close() raise RuntimeError("Subprotocol header is not 'bokeh'") token = subprotocols[1] if token is None: self.close() raise RuntimeError("No token received in subprotocol header") now = calendar.timegm(dt.datetime.now().utctimetuple()) payload = get_token_payload(token) if 'session_expiry' not in payload: self.close() raise RuntimeError("Session expiry has not been provided") elif now >= payload['session_expiry']: self.close() raise RuntimeError("Token is expired.") elif not check_token_signature(token, signed=False, secret_key=None): session_id = get_session_id(token) log.error("Token for session %r had invalid signature", session_id) raise RuntimeError("Invalid token signature") def on_fully_opened(future): e = future.exception() if e is not None: # this isn't really an error (unless we have a # bug), it just means a client disconnected # immediately, most likely. log.debug("Failed to fully open connlocksection %r", e) future = self._async_open(token) # rewrite above line using asyncio # this task is scheduled to run soon once context is back to event loop task = asyncio.ensure_future(future) task.add_done_callback(on_fully_opened) await self.accept("bokeh")
def open(self): ''' Initialize a connection to a client. Returns: None ''' log.info('WebSocket connection opened') token = self._token if self.selected_subprotocol != 'bokeh': self.close() raise ProtocolError("Subprotocol header is not 'bokeh'") elif token is None: self.close() raise ProtocolError("No token received in subprotocol header") now = calendar.timegm(dt.datetime.utcnow().utctimetuple()) payload = get_token_payload(token) if 'session_expiry' not in payload: self.close() raise ProtocolError("Session expiry has not been provided") elif now >= payload['session_expiry']: self.close() raise ProtocolError("Token is expired.") elif not check_token_signature(token, signed=self.application.sign_sessions, secret_key=self.application.secret_key): session_id = get_session_id(token) log.error("Token for session %r had invalid signature", session_id) raise ProtocolError("Invalid token signature") try: self.application.io_loop.spawn_callback(self._async_open, self._token) except Exception as e: # this isn't really an error (unless we have a # bug), it just means a client disconnected # immediately, most likely. log.debug("Failed to fully open connection %r", e)