Adopt a member the portal vouches for

A share link admits somebody at the portal, so this instance first hears of
them when they arrive rather than when a superuser types their code in.
An unmapped portal identity is now checked once against the portal's own
list of who may reach this instance and given an ordinary local account
only if the portal vouches for it.

Asking rather than believing the token is the point: a token stays signed
and valid until it expires, so trusting its claims would let one rebuild
the account somebody deleted here and deleting a user would stop being the
whole of the revocation.

The account-making itself moved out of the route, since both ways in build
the same thing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GPMNwB2mGBP5j7dXRopcPH
This commit is contained in:
2026-09-02 17:50:29 +02:00
co-authored by Claude Opus 5
parent 058f16ec1d
commit 65272a135f
6 changed files with 181 additions and 42 deletions
+39
View File
@@ -14,6 +14,7 @@ import uuid
from dataclasses import replace
from unittest.mock import Mock, patch
import httpx
import jwt
import pytest
from cryptography.hazmat.primitives.asymmetric import rsa
@@ -519,3 +520,41 @@ async def test_a_config_that_cannot_be_read_is_not_dialled(monkeypatch) -> None:
watcher.cancel()
assert not started
def test_a_member_the_portal_vouches_for_is_adopted(
enrolled: User, # noqa: ARG001 (fixture installs the enrolment)
portal_key: rsa.RSAPrivateKey,
db: Session,
) -> None:
"""A share link admits at the portal; the account appears here on arrival."""
theirs = portal_token(portal_key, subject="portal-user-7")
vouched = Mock(
status_code=200,
json=Mock(
return_value={"user_id": "portal-user-7", "email": "invited@example.com"}
),
)
with patch("fluksio.cloud.enroll.httpx.get", return_value=vouched) as get:
adopted = user_from_token(db, theirs)
assert get.call_args.args[0].endswith("/instance-members/portal-user-7")
assert adopted is not None
assert adopted.email == "invited@example.com"
# Never a superuser, and never able to sign in with a password.
assert adopted.is_superuser is False
# Once mapped, no further asking: the local row is the answer.
with patch("fluksio.cloud.enroll.httpx.get") as unused:
assert user_from_token(db, theirs) == adopted
unused.assert_not_called()
# Somebody the portal does not vouch for gets nothing, and neither does a
# portal that cannot be reached.
stranger = portal_token(portal_key, subject="portal-user-8")
with patch("fluksio.cloud.enroll.httpx.get", return_value=Mock(status_code=404)):
assert user_from_token(db, stranger) is None
with patch(
"fluksio.cloud.enroll.httpx.get", side_effect=httpx.ConnectError("no route")
):
assert user_from_token(db, stranger) is None