mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-26 23:08:57 +03:00
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* Make review user read status consistent with other APIs * Validate URLs for web push endpoint * Validate the role for a custom viewer, rate limit password changing * Cleanup
151 lines
5.4 KiB
Python
151 lines
5.4 KiB
Python
"""Tests for push notification subscription validation."""
|
|
|
|
import unittest
|
|
|
|
from frigate.api.notification import _validate_push_endpoint, _validate_subscription
|
|
|
|
VALID_ENDPOINTS = [
|
|
"https://fcm.googleapis.com/fcm/send/dGhpcy1pcy1hLXRva2Vu",
|
|
"https://updates.push.services.mozilla.com/wpush/v2/dGhpcy1pcy1hLXRva2Vu",
|
|
"https://web.push.apple.com/dGhpcy1pcy1hLXRva2Vu",
|
|
"https://wns2-by3p.notify.windows.com/w/?token=dGhpcy1pcy1hLXRva2Vu",
|
|
"https://fcm.googleapis.com:443/fcm/send/dGhpcy1pcy1hLXRva2Vu",
|
|
]
|
|
|
|
|
|
def _subscription(endpoint: str) -> dict:
|
|
return {
|
|
"endpoint": endpoint,
|
|
"keys": {"p256dh": "cHVibGljLWtleQ", "auth": "YXV0aC1zZWNyZXQ"},
|
|
}
|
|
|
|
|
|
class TestValidatePushEndpoint(unittest.TestCase):
|
|
def test_accepts_real_push_service_endpoints(self):
|
|
for endpoint in VALID_ENDPOINTS:
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_http(self):
|
|
self.assertIsNotNone(
|
|
_validate_push_endpoint("http://fcm.googleapis.com/fcm/send/token")
|
|
)
|
|
|
|
def test_rejects_non_http_schemes(self):
|
|
for endpoint in (
|
|
"file:///etc/passwd",
|
|
"ftp://example.com/token",
|
|
"//example.com/token",
|
|
):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_localhost(self):
|
|
for endpoint in (
|
|
"https://localhost/token",
|
|
"https://localhost:443/token",
|
|
"https://127.0.0.1/token",
|
|
"https://[::1]/token",
|
|
):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_private_addresses(self):
|
|
for endpoint in (
|
|
"https://192.168.1.10/token",
|
|
"https://10.0.0.5/token",
|
|
"https://172.16.0.1/token",
|
|
"https://169.254.169.254/token",
|
|
"https://0.0.0.0/token",
|
|
):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_internal_hostnames(self):
|
|
for endpoint in (
|
|
"https://frigate/token",
|
|
"https://nas.local/token",
|
|
"https://push.internal/token",
|
|
"https://host.home.arpa/token",
|
|
):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_non_default_port(self):
|
|
self.assertIsNotNone(
|
|
_validate_push_endpoint("https://fcm.googleapis.com:8080/fcm/send/token")
|
|
)
|
|
|
|
def test_rejects_embedded_credentials(self):
|
|
self.assertIsNotNone(
|
|
_validate_push_endpoint(
|
|
"https://user:pass@fcm.googleapis.com/fcm/send/token"
|
|
)
|
|
)
|
|
|
|
def test_rejects_endpoint_without_path(self):
|
|
for endpoint in ("https://fcm.googleapis.com", "https://fcm.googleapis.com/"):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_endpoint_that_breaks_audience_parsing(self):
|
|
# webpush.py locates the host by searching for a separator after index
|
|
# 10, which raises ValueError when the url has no path at all
|
|
endpoint = "https://fcm.googleapis.com"
|
|
|
|
with self.assertRaises(ValueError):
|
|
endpoint.index("/", 10)
|
|
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_missing_or_non_string_endpoint(self):
|
|
for endpoint in (None, "", 5, {"url": "https://example.com/token"}):
|
|
with self.subTest(endpoint=endpoint):
|
|
self.assertIsNotNone(_validate_push_endpoint(endpoint))
|
|
|
|
def test_rejects_overlong_endpoint(self):
|
|
self.assertIsNotNone(
|
|
_validate_push_endpoint(f"https://fcm.googleapis.com/{'a' * 4096}")
|
|
)
|
|
|
|
|
|
class TestValidateSubscription(unittest.TestCase):
|
|
def test_accepts_valid_subscription(self):
|
|
self.assertIsNone(_validate_subscription(_subscription(VALID_ENDPOINTS[0])))
|
|
|
|
def test_accepts_extra_fields_sent_by_the_browser(self):
|
|
sub = _subscription(VALID_ENDPOINTS[0])
|
|
sub["expirationTime"] = None
|
|
self.assertIsNone(_validate_subscription(sub))
|
|
|
|
def test_rejects_non_object(self):
|
|
for sub in ("https://fcm.googleapis.com/fcm/send/token", ["endpoint"], 5):
|
|
with self.subTest(sub=sub):
|
|
self.assertIsNotNone(_validate_subscription(sub))
|
|
|
|
def test_rejects_bad_endpoint(self):
|
|
self.assertIsNotNone(
|
|
_validate_subscription(_subscription("https://localhost/t"))
|
|
)
|
|
|
|
def test_rejects_missing_keys(self):
|
|
sub = _subscription(VALID_ENDPOINTS[0])
|
|
del sub["keys"]
|
|
self.assertIsNotNone(_validate_subscription(sub))
|
|
|
|
def test_rejects_incomplete_keys(self):
|
|
for keys in (
|
|
{"p256dh": "cHVibGljLWtleQ"},
|
|
{"auth": "YXV0aC1zZWNyZXQ"},
|
|
{"p256dh": "cHVibGljLWtleQ", "auth": ""},
|
|
{"p256dh": None, "auth": "YXV0aC1zZWNyZXQ"},
|
|
):
|
|
with self.subTest(keys=keys):
|
|
sub = _subscription(VALID_ENDPOINTS[0])
|
|
sub["keys"] = keys
|
|
self.assertIsNotNone(_validate_subscription(sub))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|