Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC of origin checks for Iris endpoints #958

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ docker/snapserver/meta_mopidy.py
docker/mopidy/mopidy.conf
docker/mopidy/iris/
docker/mopidy/m3u/
docker/mopidy/spotify/
docker/mopidy/icecast2.xml
docker/jellyfin/
docker-compose.yml
Expand Down
9 changes: 2 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,8 @@ RUN git clone --depth 1 --single-branch -b ${IRIS_VERSION} https://github.com/ja
# Copy Version file
&& cp /iris/VERSION /

# Install mopidy-spotify-gstspotify (Hack, not released yet!)
# (https://github.com/kingosticks/mopidy-spotify/tree/gstspotifysrc-hack)
RUN git clone --depth 1 https://github.com/mopidy/mopidy-spotify.git mopidy-spotify \
&& cd mopidy-spotify \
&& python3 setup.py install \
&& cd .. \
&& rm -rf mopidy-spotify
# Install mopidy-spotify
RUN sudo python3 -m pip install --break-system-packages Mopidy-Spotify==5.0.0a2

# Install additional mopidy extensions and Python dependencies via pip
COPY docker/requirements.txt .
Expand Down
44 changes: 41 additions & 3 deletions mopidy_iris/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import tornado.web
import tornado.websocket
import tornado.template
import urllib.parse
import logging
import json
import time
import asyncio
from typing import cast

from .mem import iris

Expand All @@ -20,11 +22,15 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self, core, config):
self.core = core
self.config = config
self.allowed_origins = config["http"]["allowed_origins"]
self.csrf_protection = config["http"]["csrf_protection"]
self.ioloop = tornado.ioloop.IOLoop.current()
iris.ioloop = self.ioloop # Make available elsewhere in the Frontend

def check_origin(self, origin):
return True
def check_origin(self, origin: str) -> bool:
if not self.csrf_protection:
return True
return check_origin(origin, self.request.headers, self.allowed_origins)

def open(self):

Expand Down Expand Up @@ -173,7 +179,6 @@ def handle_result(self, *args, **kwargs):
data["recipient"] = self.connection_id
iris.send_message(data=data)


class HttpHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
Expand All @@ -189,6 +194,19 @@ def initialize(self, core, config):
self.core = core
self.config = config
self.ioloop = tornado.ioloop.IOLoop.current()
self.allowed_origins = config["http"]["allowed_origins"]
self.csrf_protection = config["http"]["csrf_protection"]

def check_origin(self, origin: str) -> bool:
if self.csrf_protection:
origin = cast(str | None, self.request.headers.get("Origin"))
if not check_origin(origin, self.request.headers, self.allowed_origins):
self.set_status(403, f"Access denied for origin {origin}")
return

assert origin
self.set_cors_headers(origin)
return check_origin(origin, self.request.headers, self.allowed_origins)

# Options request
# This is a preflight request for CORS requests
Expand Down Expand Up @@ -343,3 +361,23 @@ def initialize(self, path):

def get(self, path=None, include_body=True):
return super().get(self.path, include_body)


def check_origin(
origin: str,
request_headers: tornado.httputil.HTTPHeaders,
allowed_origins: set[str],
) -> bool:
if origin is None:
logger.warning("HTTP request denied for missing Origin header")
return False
host_header = request_headers.get("Host")
parsed_origin = urllib.parse.urlparse(origin).netloc.lower()
# 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.warning('HTTP request denied for Origin "%s"', origin)
return False
return True