snapshot of https://github.com/ActivityWatch/aw-server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 3m6s
Lint / lint (push) Successful in 22s
Build / typecheck (ubuntu-latest, 3.9) (push) Successful in 1m49s
Lint / format (push) Successful in 42s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1m31s

This commit is contained in:
upstream-mirror
2026-09-16 00:20:00 +05:00
commit 07fb811788
40 changed files with 4688 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
"""Tests for moz-extension CORS endpoint scoping (extension_cors module)."""
import pytest
from aw_server.extension_cors import (
_is_allowed,
_is_allowed_path,
_matches_configured_origin,
)
from aw_server.server import AWFlask
_EXT_ORIGIN = "moz-extension://aabbccddeeff00112233445566778899"
_HOST = "127.0.0.1"
@pytest.fixture(scope="module")
def client():
app = AWFlask(_HOST, testing=True)
return app.test_client()
# ---------------------------------------------------------------------------
# Unit tests for the path-matching helpers
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"method,path,expected",
[
("GET", "/api/0/info", True),
("POST", "/api/0/buckets/aw-watcher-web-hostname", True),
("POST", "/api/0/buckets/aw-watcher-web-hostname/heartbeat", True),
# Blocked paths
("GET", "/api/0/export", False),
("POST", "/api/0/export", False),
("GET", "/api/0/buckets/", False),
("GET", "/api/0/buckets/aw-watcher-web-hostname/events", False),
("POST", "/api/0/query/", False),
("GET", "/api/0/settings", False),
# Non-watcher bucket
("POST", "/api/0/buckets/aw-watcher-window-hostname", False),
# Heartbeat on non-watcher bucket
("POST", "/api/0/buckets/aw-watcher-afk-hostname/heartbeat", False),
# Percent-encoding should not bypass via raw path (segments are used)
("GET", "/api/0/%65xport", False), # %65 = 'e', decodes to 'export'
],
)
def test_is_allowed_unit(method, path, expected):
segments = [s for s in path.split("/") if s]
assert _is_allowed(method, segments) is expected
@pytest.mark.parametrize(
"path,expected",
[
("/api/0/info", True),
("/api/0/buckets/aw-watcher-web-hostname", True),
("/api/0/buckets/aw-watcher-web-hostname/heartbeat", True),
("/api/0/export", False),
("/api/0/buckets/", False),
],
)
def test_is_allowed_path_unit(path, expected):
segments = [s for s in path.split("/") if s]
assert _is_allowed_path(segments) is expected
# ---------------------------------------------------------------------------
# Integration tests via Flask test client
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"method,path",
[
("GET", "/api/0/info"),
# Bucket creation: 400 without body is expected but NOT 403
("POST", "/api/0/buckets/aw-watcher-web-testhost"),
# Heartbeat: 404 (bucket doesn't exist) is expected but NOT 403
("POST", "/api/0/buckets/aw-watcher-web-testhost/heartbeat"),
],
)
def test_extension_allowed(client, method, path):
"""moz-extension origins are permitted at aw-watcher-web's endpoints."""
headers = {"Origin": _EXT_ORIGIN}
r = client.open(path, method=method, headers=headers)
assert (
r.status_code != 403
), f"{method} {path} should not be blocked; got {r.status_code}"
@pytest.mark.parametrize(
"method,path",
[
("GET", "/api/0/export"),
("GET", "/api/0/buckets/"),
("POST", "/api/0/import"),
("POST", "/api/0/query/"),
("GET", "/api/0/settings"),
# Events read from a watcher bucket
("GET", "/api/0/buckets/aw-watcher-web-testhost/events"),
# Non-watcher bucket
("POST", "/api/0/buckets/aw-watcher-window-testhost"),
("POST", "/api/0/buckets/aw-watcher-afk-testhost/heartbeat"),
],
)
def test_extension_blocked(client, method, path):
"""moz-extension origins are blocked at endpoints beyond aw-watcher-web's needs."""
headers = {"Origin": _EXT_ORIGIN}
r = client.open(path, method=method, headers=headers)
assert (
r.status_code == 403
), f"{method} {path} should be blocked (403); got {r.status_code}"
@pytest.mark.parametrize(
"pattern,origin,expected",
[
("moz-extension://aabbcc", "moz-extension://aabbcc", True),
("MOZ-EXTENSION://AABBCC", "moz-extension://aabbcc", True),
(r"moz-extension://.*", "moz-extension://aabbcc", True),
(r"moz-extension://[a-f0-9]+", "moz-extension://aabbcc", True),
(r"moz-extension://[0-9]+", "moz-extension://aabbcc", False),
("moz-extension://other", "moz-extension://aabbcc", False),
],
)
def test_matches_configured_origin(pattern, origin, expected):
assert _matches_configured_origin(origin, pattern) is expected
def test_regex_configured_extension_origin_bypasses_scope_guard():
"""Owner-configured regex origins retain unrestricted endpoint access."""
app = AWFlask(_HOST, testing=False, cors_origins=[r"moz-extension://.*"])
client = app.test_client()
response = client.get("/api/0/export", headers={"Origin": _EXT_ORIGIN})
assert response.status_code != 403
def test_non_extension_origin_passthrough(client):
"""Non-moz-extension origins are not affected by the scope guard."""
headers = {"Origin": "http://127.0.0.1:27180"}
r = client.get("/api/0/info", headers=headers)
assert r.status_code != 403
def test_no_origin_passthrough(client):
"""Requests without an Origin header (native watchers, curl) are not blocked."""
r = client.get("/api/0/info")
assert r.status_code != 403
def test_options_allowed_path(client):
"""OPTIONS preflight on an allowed path is permitted."""
headers = {
"Origin": _EXT_ORIGIN,
"Access-Control-Request-Method": "GET",
}
r = client.options("/api/0/info", headers=headers)
assert r.status_code != 403
def test_options_blocked_path(client):
"""OPTIONS preflight on a blocked path is rejected."""
headers = {
"Origin": _EXT_ORIGIN,
"Access-Control-Request-Method": "GET",
}
r = client.options("/api/0/export", headers=headers)
assert r.status_code == 403