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.
|
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)
|
v2.2.0 (2018-09-30)
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|||||||
@ -150,11 +150,18 @@ def set_mopidy_headers(request_handler):
|
|||||||
|
|
||||||
def check_origin(origin, request_headers, allowed_origins):
|
def check_origin(origin, request_headers, allowed_origins):
|
||||||
if origin is None:
|
if origin is None:
|
||||||
logger.debug('Origin was not set')
|
logger.warn('HTTP request denied for missing Origin header')
|
||||||
return False
|
return False
|
||||||
allowed_origins.add(request_headers.get('Host'))
|
allowed_origins.add(request_headers.get('Host'))
|
||||||
parsed_origin = urllib.parse.urlparse(origin).netloc.lower()
|
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):
|
class JsonRpcHandler(tornado.web.RequestHandler):
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
@ -86,3 +87,42 @@ class WebSocketHandlerTest(tornado.testing.AsyncHTTPTestCase):
|
|||||||
for client in handlers.WebSocketHandler.clients:
|
for client in handlers.WebSocketHandler.clients:
|
||||||
client.ws_connection = None
|
client.ws_connection = None
|
||||||
handlers.WebSocketHandler.broadcast('message')
|
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