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 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 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
8 changes: 4 additions & 4 deletions tests/replication/test_pusher_shard.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _create_pusher_and_send_msg(self, localpart):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "https://push.example.com/push"},
data={"url": "https://push.example.com/_matrix/push/v1/notify"},
)
)

Expand Down Expand Up @@ -109,7 +109,7 @@ def test_send_push_single_worker(self):
http_client_mock.post_json_get_json.assert_called_once()
self.assertEqual(
http_client_mock.post_json_get_json.call_args[0][0],
"https://push.example.com/push",
"https://push.example.com/_matrix/push/v1/notify",
)
self.assertEqual(
event_id,
Expand Down Expand Up @@ -161,7 +161,7 @@ def test_send_push_multiple_workers(self):
http_client_mock2.post_json_get_json.assert_not_called()
self.assertEqual(
http_client_mock1.post_json_get_json.call_args[0][0],
"https://push.example.com/push",
"https://push.example.com/_matrix/push/v1/notify",
)
self.assertEqual(
event_id,
Expand All @@ -183,7 +183,7 @@ def test_send_push_multiple_workers(self):
http_client_mock2.post_json_get_json.assert_called_once()
self.assertEqual(
http_client_mock2.post_json_get_json.call_args[0][0],
"https://push.example.com/push",
"https://push.example.com/_matrix/push/v1/notify",
)
self.assertEqual(
event_id,
Expand Down
2 changes: 1 addition & 1 deletion tests/rest/admin/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,7 +1256,7 @@ def test_get_pushers(self):
device_display_name="pushy push",
pushkey="[email protected]",
lang=None,
data={"url": "example.com"},
data={"url": "https://example.com/_matrix/push/v1/notify"},
)
)

Expand Down