"""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"))