http: allow local files to access websocket (Fixes #1711)
check_origin() still ensures the Origin header is set but now only blocks when missing from the allowed list *if* a network location was extracted from the header. This prevents websocket connections originating from local files (common in Apache Cordova apps such as Mopidy-Mobile) from being blocked; these files don't really have a sensible value for Origin so the client browser sets the header to something like 'file://' or 'null'. Also added some tests for check_origin().
This commit is contained in:
parent
e025f04160
commit
6e9ed9e8a9
@ -5,6 +5,14 @@ Changelog
|
||||
This changelog is used to track all major changes to Mopidy.
|
||||
|
||||
|
||||
v2.2.1 (UNRELEASED)
|
||||
===================
|
||||
|
||||
- HTTP: Stop blocking connections where the network location part of the Origin
|
||||
header is empty, such as websocket connections originating from local files.
|
||||
(Fixes: :issue:`1711`, PR: :issue:`1712`)
|
||||
|
||||
|
||||
v2.2.0 (2018-09-30)
|
||||
===================
|
||||
|
||||
|
||||
@ -150,11 +150,18 @@ def set_mopidy_headers(request_handler):
|
||||
|
||||
def check_origin(origin, request_headers, allowed_origins):
|
||||
if origin is None:
|
||||
logger.debug('Origin was not set')
|
||||
logger.warn('HTTP request denied for missing Origin header')
|
||||
return False
|
||||
allowed_origins.add(request_headers.get('Host'))
|
||||
parsed_origin = urllib.parse.urlparse(origin).netloc.lower()
|
||||
return parsed_origin and parsed_origin in allowed_origins
|
||||
# Some frameworks (e.g. Apache Cordova) use local files. Requests from
|
||||
# these files don't really have a sensible Origin so the browser sets the
|
||||
# header to something like 'file://' or 'null'. This results here in an
|
||||
# empty parsed_origin which we choose to allow.
|
||||
if parsed_origin and parsed_origin not in allowed_origins:
|
||||
logger.warn('HTTP request denied for Origin "%s"', origin)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class JsonRpcHandler(tornado.web.RequestHandler):
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
@ -86,3 +87,42 @@ class WebSocketHandlerTest(tornado.testing.AsyncHTTPTestCase):
|
||||
for client in handlers.WebSocketHandler.clients:
|
||||
client.ws_connection = None
|
||||
handlers.WebSocketHandler.broadcast('message')
|
||||
|
||||
|
||||
class CheckOriginTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.headers = {'Host': 'localhost:6680'}
|
||||
self.allowed = set()
|
||||
|
||||
def test_missing_origin_blocked(self):
|
||||
self.assertFalse(handlers.check_origin(
|
||||
None, self.headers, self.allowed))
|
||||
|
||||
def test_empty_origin_allowed(self):
|
||||
self.assertTrue(handlers.check_origin('', self.headers, self.allowed))
|
||||
|
||||
def test_chrome_file_origin_allowed(self):
|
||||
self.assertTrue(handlers.check_origin(
|
||||
'file://', self.headers, self.allowed))
|
||||
|
||||
def test_firefox_null_origin_allowed(self):
|
||||
self.assertTrue(handlers.check_origin(
|
||||
'null', self.headers, self.allowed))
|
||||
|
||||
def test_same_host_origin_allowed(self):
|
||||
self.assertTrue(handlers.check_origin(
|
||||
'http://localhost:6680', self.headers, self.allowed))
|
||||
|
||||
def test_different_host_origin_blocked(self):
|
||||
self.assertFalse(handlers.check_origin(
|
||||
'http://other:6680', self.headers, self.allowed))
|
||||
|
||||
def test_different_port_blocked(self):
|
||||
self.assertFalse(handlers.check_origin(
|
||||
'http://localhost:80', self.headers, self.allowed))
|
||||
|
||||
def test_extra_origin_allowed(self):
|
||||
self.allowed.add('other:6680')
|
||||
self.assertTrue(handlers.check_origin(
|
||||
'http://other:6680', self.headers, self.allowed))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user