Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Return 200 OK for all OPTIONS requests (#7534)
Browse files Browse the repository at this point in the history
  • Loading branch information
clokep authored May 22, 2020
1 parent 1531b21 commit 4429764
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 11 deletions.
1 change: 1 addition & 0 deletions changelog.d/7534.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
All endpoints now respond with a 200 OK for `OPTIONS` requests.
5 changes: 2 additions & 3 deletions synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from typing_extensions import ContextManager

from twisted.internet import defer, reactor
from twisted.web.resource import NoResource

import synapse
import synapse.events
Expand All @@ -41,7 +40,7 @@
from synapse.federation import send_queue
from synapse.federation.transport.server import TransportLayerServer
from synapse.handlers.presence import BasePresenceHandler, get_interested_parties
from synapse.http.server import JsonResource
from synapse.http.server import JsonResource, OptionsResource
from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.http.site import SynapseSite
from synapse.logging.context import LoggingContext
Expand Down Expand Up @@ -566,7 +565,7 @@ def _listen_http(self, listener_config):
if name == "replication":
resources[REPLICATION_PREFIX] = ReplicationRestResource(self)

root_resource = create_resource_tree(resources, NoResource())
root_resource = create_resource_tree(resources, OptionsResource())

_base.listen_tcp(
bind_addresses,
Expand Down
14 changes: 9 additions & 5 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from twisted.application import service
from twisted.internet import defer, reactor
from twisted.python.failure import Failure
from twisted.web.resource import EncodingResourceWrapper, IResource, NoResource
from twisted.web.resource import EncodingResourceWrapper, IResource
from twisted.web.server import GzipEncoderFactory
from twisted.web.static import File

Expand All @@ -52,7 +52,11 @@
from synapse.config.homeserver import HomeServerConfig
from synapse.federation.transport.server import TransportLayerServer
from synapse.http.additional_resource import AdditionalResource
from synapse.http.server import RootRedirect
from synapse.http.server import (
OptionsResource,
RootOptionsRedirectResource,
RootRedirect,
)
from synapse.http.site import SynapseSite
from synapse.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
Expand Down Expand Up @@ -121,11 +125,11 @@ def _listener_http(self, config, listener_config):

# try to find something useful to redirect '/' to
if WEB_CLIENT_PREFIX in resources:
root_resource = RootRedirect(WEB_CLIENT_PREFIX)
root_resource = RootOptionsRedirectResource(WEB_CLIENT_PREFIX)
elif STATIC_PREFIX in resources:
root_resource = RootRedirect(STATIC_PREFIX)
root_resource = RootOptionsRedirectResource(STATIC_PREFIX)
else:
root_resource = NoResource()
root_resource = OptionsResource()

root_resource = create_resource_tree(resources, root_resource)

Expand Down
23 changes: 20 additions & 3 deletions synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,6 @@ def _get_handler_for_request(self, request):
register_paths, so will return (possibly via Deferred) either
None, or a tuple of (http code, response body).
"""
if request.method == b"OPTIONS":
return _options_handler, "options_request_handler", {}

request_path = request.path.decode("ascii")

# Loop through all the registered callbacks to check if the method
Expand Down Expand Up @@ -448,6 +445,26 @@ def getChild(self, name, request):
return resource.Resource.getChild(self, name, request)


class OptionsResource(resource.Resource):
"""Responds to OPTION requests for itself and all children."""

def render_OPTIONS(self, request):
code, response_json_object = _options_handler(request)

return respond_with_json(
request, code, response_json_object, send_cors=False, canonical_json=False,
)

def getChildWithDefault(self, path, request):
if request.method == b"OPTIONS":
return self # select ourselves as the child to render
return resource.Resource.getChildWithDefault(self, path, request)


class RootOptionsRedirectResource(OptionsResource, RootRedirect):
pass


def respond_with_json(
request,
code,
Expand Down
53 changes: 53 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from synapse.http.server import (
DirectServeResource,
JsonResource,
OptionsResource,
wrap_html_request_handler,
)
from synapse.http.site import SynapseSite, logger
Expand Down Expand Up @@ -168,6 +169,58 @@ def _callback(request, **kwargs):
self.assertEqual(channel.json_body["errcode"], "M_UNRECOGNIZED")


class OptionsResourceTests(unittest.TestCase):
def setUp(self):
self.reactor = ThreadedMemoryReactorClock()

class DummyResource(Resource):
isLeaf = True

def render(self, request):
return request.path

# Setup a resource with some children.
self.resource = OptionsResource()
self.resource.putChild(b"res", DummyResource())

def _make_request(self, method, path):
"""Create a request from the method/path and return a channel with the response."""
request, channel = make_request(self.reactor, method, path, shorthand=False)
request.prepath = [] # This doesn't get set properly by make_request.

# Create a site and query for the resource.
site = SynapseSite("test", "site_tag", {}, self.resource, "1.0")
request.site = site
resource = site.getResourceFor(request)

# Finally, render the resource and return the channel.
render(request, resource, self.reactor)
return channel

def test_unknown_options_request(self):
"""An OPTIONS requests to an unknown URL still returns 200 OK."""
channel = self._make_request(b"OPTIONS", b"/foo/")
self.assertEqual(channel.result["code"], b"200")
self.assertEqual(channel.result["body"], b"{}")

def test_known_options_request(self):
"""An OPTIONS requests to an known URL still returns 200 OK."""
channel = self._make_request(b"OPTIONS", b"/res/")
self.assertEqual(channel.result["code"], b"200")
self.assertEqual(channel.result["body"], b"{}")

def test_unknown_request(self):
"""A non-OPTIONS request to an unknown URL should 404."""
channel = self._make_request(b"GET", b"/foo/")
self.assertEqual(channel.result["code"], b"404")

def test_known_request(self):
"""A non-OPTIONS request to an known URL should query the proper resource."""
channel = self._make_request(b"GET", b"/res/")
self.assertEqual(channel.result["code"], b"200")
self.assertEqual(channel.result["body"], b"/res/")


class WrapHtmlRequestHandlerTests(unittest.TestCase):
class TestResource(DirectServeResource):
callback = None
Expand Down

0 comments on commit 4429764

Please sign in to comment.