Files
app/backend/tests/test_cloud.py
T
stroblmeandClaude Opus 5 4c8339e643 Pair a wall panel through the portal
A screen somewhere this installation is not reachable from asks the portal for
a code instead, and the portal mints its credential — because a token signed
here is one such a device could never present.

Where it was minted changes nothing about what it may do. The panel gate moved
off the branch that decodes a local panel token and onto whatever claims name
a panel, so the portal's and this installation's are bounded by the same check
against the same panel's dashboards. A token of that scope naming no panel is
refused rather than left holding the account it borrows.

The connector marks what arrives on its socket, since that is the only thing
that makes it true, and the approval screen now names what is holding a code —
approving adopts whatever answers, so it is worth a look first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017F9RnYCJgASuBTcAjxmnsp
2026-08-20 23:42:58 +02:00

175 lines
6.2 KiB
Python

"""Remote access: off by default, and scoped to the account that enabled it.
The property worth pinning down is the one everything else rests on — a
portal's token is worth nothing here until somebody at this installation
enrolled it, and even then it grants exactly the rights of the account that
did.
"""
from __future__ import annotations
import uuid
import jwt
import pytest
from cryptography.hazmat.primitives.asymmetric import rsa
from fastapi.testclient import TestClient
from sqlmodel import Session
from app.api.deps import decode_token, user_from_token
from app.cloud import config as cloud_config
from app.core.config import settings
from app.flow.panels import PanelDef, PanelsConfig, write_config
from app.models import User
from tests.utils.portal import ISSUER, portal_token
def test_portal_token_is_refused_when_not_enrolled(
portal_key: rsa.RSAPrivateKey, tmp_path_factory: pytest.TempPathFactory
) -> None:
"""An installation nobody connected trusts no portal at all."""
original = settings.CLOUD_CONFIG_FILE
settings.CLOUD_CONFIG_FILE = tmp_path_factory.mktemp("empty") / "cloud.json"
try:
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(portal_key))
finally:
settings.CLOUD_CONFIG_FILE = original
def test_portal_token_resolves_to_the_enrolling_user(
enrolled: User, portal_key: rsa.RSAPrivateKey, db: Session
) -> None:
claims = decode_token(portal_token(portal_key))
assert claims["sub"] == str(enrolled.id)
# Who they are on the portal is carried for the record, not for rights.
assert claims["portal_sub"] == "portal-user-1"
assert user_from_token(db, portal_token(portal_key)) == enrolled
def test_token_for_another_installation_is_refused(
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
portal_key: rsa.RSAPrivateKey,
) -> None:
"""The audience is this installation's id, so someone else's is worthless."""
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(portal_key, audience=str(uuid.uuid4())))
def test_token_from_an_unpinned_key_is_refused(
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
) -> None:
"""A different portal, or a hijacked one, cannot sign for this installation."""
impostor = rsa.generate_private_key(public_exponent=65537, key_size=2048)
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(impostor))
def test_non_proxy_scope_is_refused(
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
portal_key: rsa.RSAPrivateKey,
) -> None:
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(portal_key, scope="session"))
def test_disconnecting_ends_remote_access(
enrolled: User, portal_key: rsa.RSAPrivateKey
) -> None:
"""Deleting the config is the whole of the local revocation."""
assert decode_token(portal_token(portal_key))["sub"] == str(enrolled.id)
cloud_config.delete()
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(portal_key))
def test_status_reports_not_enrolled(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
response = client.get(
f"{settings.API_V1_STR}/cloud/status", headers=superuser_token_headers
)
assert response.status_code == 200
assert response.json()["enrolled"] is False
def test_enrolling_needs_a_superuser(
client: TestClient, normal_user_token_headers: dict[str, str]
) -> None:
"""Remote access is an installation-wide grant, not a personal setting."""
response = client.post(
f"{settings.API_V1_STR}/cloud/enroll",
headers=normal_user_token_headers,
json={"portal_url": ISSUER, "claim_code": "ABCD-EFGH"},
)
assert response.status_code == 403
def test_a_panel_scoped_portal_token_reaches_only_its_panel(
client: TestClient,
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
portal_key: rsa.RSAPrivateKey,
superuser_token_headers: dict[str, str],
) -> None:
"""A screen paired through the portal is bounded here, not there.
The portal names the panel; everything about what that means is this
installation's, which is the whole reason it may mint one at all.
"""
write_config(
PanelsConfig(
panels=[
PanelDef(id="hallway", dashboards=["kitchen"]),
PanelDef(id="workshop", dashboards=["bench"]),
]
)
)
for name in ("kitchen", "bench"):
created = client.post(
f"{settings.API_V1_STR}/dashboards/{name}", headers=superuser_token_headers
)
assert created.status_code in (200, 201, 409), created.text
token = portal_token(portal_key, subject="hallway", scope="panel")
headers = {"Authorization": f"Bearer {token}"}
assert (
client.get(f"{settings.API_V1_STR}/panels/hallway", headers=headers).status_code
== 200
)
assert (
client.get(
f"{settings.API_V1_STR}/dashboards/kitchen", headers=headers
).status_code
== 200
)
# Another panel's dashboard, the panel list, and a draft are all refused.
assert (
client.get(
f"{settings.API_V1_STR}/dashboards/bench", headers=headers
).status_code
== 403
)
assert (
client.get(f"{settings.API_V1_STR}/panels/", headers=headers).status_code == 403
)
assert (
client.get(f"{settings.API_V1_STR}/flows/", headers=headers).status_code == 403
)
# Deleting the panel is how the screen is retired, whoever minted its token.
write_config(PanelsConfig(panels=[PanelDef(id="workshop", dashboards=["bench"])]))
assert (
client.get(f"{settings.API_V1_STR}/panels/hallway", headers=headers).status_code
== 401
)
def test_a_panel_token_naming_no_panel_is_refused(
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
portal_key: rsa.RSAPrivateKey,
) -> None:
"""It would otherwise fall through to the account it borrows."""
with pytest.raises(jwt.exceptions.InvalidTokenError):
decode_token(portal_token(portal_key, subject="", scope="panel"))