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

Add additional validation to pusher URLs. #8865

Merged
merged 2 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 changelog.d/8865.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add additional validation to pusher URLs to be compliant with the specification.
3 changes: 1 addition & 2 deletions synapse/push/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@


class PusherConfigException(Exception):
def __init__(self, msg):
super().__init__(msg)
"""An error occurred when creating a pusher."""
16 changes: 15 additions & 1 deletion synapse/push/httppusher.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import urllib.parse

from prometheus_client import Counter

Expand Down Expand Up @@ -97,9 +98,22 @@ def __init__(self, hs, pusherdict):
if self.data is None:
raise PusherConfigException("data can not be null for HTTP pusher")

# Validate that there's a URL and it is of the proper form.
if "url" not in self.data:
raise PusherConfigException("'url' required in data for HTTP pusher")
self.url = self.data["url"]

url = self.data["url"]
if not isinstance(url, str):
raise PusherConfigException("'url' must be a string")
url_parts = urllib.parse.urlparse(url)
# Note that the specification also says the scheme must be HTTPS, but
# it isn't up to the homeserver to verify that.
if url_parts.path != "/_matrix/push/v1/notify":
raise PusherConfigException(
"'url' must have a path of '/_matrix/push/v1/notify'"
)

self.url = url
self.http_client = hs.get_proxied_blacklisted_http_client()
self.data_minus_url = {}
self.data_minus_url.update(self.data)
Expand Down
106 changes: 84 additions & 22 deletions tests/push/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import synapse.rest.admin
from synapse.logging.context import make_deferred_yieldable
from synapse.push import PusherConfigException
from synapse.rest.client.v1 import login, room
from synapse.rest.client.v2_alpha import receipts

Expand All @@ -34,6 +35,11 @@ class HTTPPusherTests(HomeserverTestCase):
user_id = True
hijack_auth = False

def default_config(self):
config = super().default_config()
config["start_pushers"] = True
return config

def make_homeserver(self, reactor, clock):
self.push_attempts = []

Expand All @@ -46,14 +52,48 @@ def post_json_get_json(url, body):

m.post_json_get_json = post_json_get_json

config = self.default_config()
config["start_pushers"] = True
hs = self.setup_test_homeserver(proxied_blacklisted_http_client=m)

return hs

hs = self.setup_test_homeserver(
config=config, proxied_blacklisted_http_client=m
def test_invalid_configuration(self):
"""Invalid push configurations should be rejected."""
# Register the user who gets notified
user_id = self.register_user("user", "pass")
access_token = self.login("user", "pass")

# Register the pusher
user_tuple = self.get_success(
self.hs.get_datastore().get_user_by_access_token(access_token)
)
token_id = user_tuple.token_id

return hs
def test_data(data):
self.get_failure(
self.hs.get_pusherpool().add_pusher(
user_id=user_id,
access_token=token_id,
kind="http",
app_id="m.http",
app_display_name="HTTP Push Notifications",
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data=data,
),
PusherConfigException,
)

# Data must be provided with a URL.
test_data(None)
test_data({})
test_data({"url": 1})
# A bare domain name isn't accepted.
test_data({"url": "example.com"})
# A URL without a path isn't accepted.
test_data({"url": "http://example.com"})
# A url with an incorrect path isn't accepted.
test_data({"url": "http://example.com/foo"})

def test_sends_http(self):
"""
Expand Down Expand Up @@ -84,7 +124,7 @@ def test_sends_http(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand Down Expand Up @@ -119,7 +159,9 @@ def test_sends_http(self):

# One push was attempted to be sent -- it'll be the first message
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(
self.push_attempts[0][2]["notification"]["content"]["body"], "Hi!"
)
Expand All @@ -139,7 +181,9 @@ def test_sends_http(self):

# Now it'll try and send the second push message, which will be the second one
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(self.push_attempts[1][1], "example.com")
self.assertEqual(
self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(
self.push_attempts[1][2]["notification"]["content"]["body"], "There!"
)
Expand Down Expand Up @@ -196,7 +240,7 @@ def test_sends_high_priority_for_encrypted(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand Down Expand Up @@ -232,7 +276,9 @@ def test_sends_high_priority_for_encrypted(self):

# Check our push made it with high priority
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")

# Add yet another person — we want to make this room not a 1:1
Expand Down Expand Up @@ -270,7 +316,9 @@ def test_sends_high_priority_for_encrypted(self):
# Advance time a bit, so the pusher will register something has happened
self.pump()
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(self.push_attempts[1][1], "example.com")
self.assertEqual(
self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "high")

def test_sends_high_priority_for_one_to_one_only(self):
Expand Down Expand Up @@ -312,7 +360,7 @@ def test_sends_high_priority_for_one_to_one_only(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand All @@ -328,7 +376,9 @@ def test_sends_high_priority_for_one_to_one_only(self):

# Check our push made it with high priority — this is a one-to-one room
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")

# Yet another user joins
Expand All @@ -347,7 +397,9 @@ def test_sends_high_priority_for_one_to_one_only(self):
# Advance time a bit, so the pusher will register something has happened
self.pump()
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(self.push_attempts[1][1], "example.com")
self.assertEqual(
self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
)

# check that this is low-priority
self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
Expand Down Expand Up @@ -394,7 +446,7 @@ def test_sends_high_priority_for_mention(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand All @@ -410,7 +462,9 @@ def test_sends_high_priority_for_mention(self):

# Check our push made it with high priority
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")

# Send another event, this time with no mention
Expand All @@ -419,7 +473,9 @@ def test_sends_high_priority_for_mention(self):
# Advance time a bit, so the pusher will register something has happened
self.pump()
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(self.push_attempts[1][1], "example.com")
self.assertEqual(
self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
)

# check that this is low-priority
self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
Expand Down Expand Up @@ -467,7 +523,7 @@ def test_sends_high_priority_for_atroom(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand All @@ -487,7 +543,9 @@ def test_sends_high_priority_for_atroom(self):

# Check our push made it with high priority
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)
self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")

# Send another event, this time as someone without the power of @room
Expand All @@ -498,7 +556,9 @@ def test_sends_high_priority_for_atroom(self):
# Advance time a bit, so the pusher will register something has happened
self.pump()
self.assertEqual(len(self.push_attempts), 2)
self.assertEqual(self.push_attempts[1][1], "example.com")
self.assertEqual(
self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
)

# check that this is low-priority
self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
Expand Down Expand Up @@ -572,7 +632,7 @@ def _test_push_unread_count(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "http://example.com/_matrix/push/v1/notify"},
)
)

Expand All @@ -591,7 +651,9 @@ def _test_push_unread_count(self):

# Check our push made it
self.assertEqual(len(self.push_attempts), 1)
self.assertEqual(self.push_attempts[0][1], "example.com")
self.assertEqual(
self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
)

# Check that the unread count for the room is 0
#
Expand Down