""" MHP federation — what a registered peer hub may and may not do. A peer is trusted enough to advertise its own public groups into our directory and to withdraw them. It is not trusted to speak for a third hub, to shadow a local group, to revoke our users, or to replay a state-changing request. """ import base64 import hashlib import time import uuid import jwt import pytest from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from meshbay_hub.api.deps import set_admin_usernames def _auth_key(password: str, username: str) -> str: salt = hashlib.sha256(f"meshbay:auth:v1:{username}".encode()).digest() return base64.b64encode( hashlib.pbkdf2_hmac("sha512", password.encode(), salt, 600_000, 32)).decode() async def _admin(client, username="root"): pw = "a-long-enough-passphrase" await client.post("/v1/users/register", json={ "username": username, "email": f"{username}@example.com", "auth_key": _auth_key(pw, username)}) set_admin_usernames([username]) r = await client.post("/v1/users/login", json={ "username": username, "auth_key": _auth_key(pw, username)}) return {"Authorization": f"Bearer {r.json()['access_token']}"} class Peer: def __init__(self, hub_id: str): self.hub_id = hub_id self._sk = Ed25519PrivateKey.generate() self.pk_pem = self._sk.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo).decode() def _sk_pem(self) -> bytes: return self._sk.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) def envelope(self, jti: str | None = None) -> str: now = int(time.time()) return jwt.encode( {"iss": self.hub_id, "sub": self.hub_id, "jti": jti or str(uuid.uuid4()), "iat": now, "exp": now + 300}, self._sk_pem(), algorithm="EdDSA") def revocation(self, target: str, target_id: str) -> str: return jwt.encode( {"type": "revocation", "target": target, "target_id": target_id, "iss": self.hub_id, "iat": int(time.time())}, self._sk_pem(), algorithm="EdDSA") def header(self, **kw) -> dict: return {"Authorization": f"Bearer {self.envelope(**kw)}"} async def _register_peer(client, admin, peer: Peer): r = await client.post("/mhp/peers", headers=admin, json={ "hub_id": peer.hub_id, "hub_url": f"https://{peer.hub_id}", "pk_hub_pem": peer.pk_pem}) assert r.status_code == 201, r.text # ── receive_directory ────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_unknown_peer_is_refused(client): stranger = Peer("nobody.example") r = await client.post("/mhp/directory", headers=stranger.header(), json={"hub_id": "nobody.example", "groups": []}) assert r.status_code == 401 @pytest.mark.asyncio async def test_source_hub_is_the_signer_not_the_body(client): admin = await _admin(client) peer = Peer("peer-a.example") await _register_peer(client, admin, peer) r = await client.post("/mhp/directory", headers=peer.header(), json={ "hub_id": "peer-b.example", # claims to relay another hub "groups": [{"id": "g-1", "name": "Shared", "join_policy": "open"}]}) assert r.status_code == 202 listing = (await client.get("/v1/groups")).json()["groups"] row = next(g for g in listing if g["id"] == "g-1") assert row["source"] == "peer-a.example" # the signer, not "peer-b.example" @pytest.mark.asyncio async def test_a_federated_id_cannot_shadow_a_local_group(client): admin = await _admin(client) peer = Peer("peer-a.example") await _register_peer(client, admin, peer) owner = await _admin(client, "owner") r = await client.post("/v1/groups", headers=owner, json={ "name": "mine", "visibility": "public", "join_policy": "open"}) local_id = r.json()["group_id"] r = await client.post("/mhp/directory", headers=peer.header(), json={ "hub_id": peer.hub_id, "groups": [{"id": local_id, "name": "evil twin", "join_policy": "open"}]}) assert r.status_code == 202 assert r.json()["accepted"] == 0 @pytest.mark.asyncio async def test_a_state_changing_token_cannot_be_replayed(client): admin = await _admin(client) peer = Peer("peer-a.example") await _register_peer(client, admin, peer) env = peer.envelope(jti="fixed-jti") h = {"Authorization": f"Bearer {env}"} body = {"hub_id": peer.hub_id, "groups": [{"id": "g-9", "name": "Once", "join_policy": "open"}]} assert (await client.post("/mhp/directory", headers=h, json=body)).status_code == 202 assert (await client.post("/mhp/directory", headers=h, json=body)).status_code == 401 # ── receive_revocation ───────────────────────────────────────────────────── @pytest.mark.asyncio async def test_a_peer_may_withdraw_its_own_group(client): admin = await _admin(client) peer = Peer("peer-a.example") await _register_peer(client, admin, peer) await client.post("/mhp/directory", headers=peer.header(), json={ "hub_id": peer.hub_id, "groups": [{"id": "g-77", "name": "Bye", "join_policy": "open"}]}) assert any(g["id"] == "g-77" for g in (await client.get("/v1/groups")).json()["groups"]) r = await client.post("/mhp/revoke", headers=peer.header(), json={"token": peer.revocation("group", "g-77")}) assert r.status_code == 202 and r.json()["pruned"] == 1 assert not any(g["id"] == "g-77" for g in (await client.get("/v1/groups")).json()["groups"]) @pytest.mark.asyncio async def test_a_peer_cannot_withdraw_another_hubs_group(client): admin = await _admin(client) a, b = Peer("peer-a.example"), Peer("peer-b.example") await _register_peer(client, admin, a) await _register_peer(client, admin, b) await client.post("/mhp/directory", headers=a.header(), json={ "hub_id": a.hub_id, "groups": [{"id": "g-a", "name": "A's", "join_policy": "open"}]}) # b signs a revocation for a's group and presents it under b's envelope. r = await client.post("/mhp/revoke", headers=b.header(), json={"token": b.revocation("group", "g-a")}) assert r.status_code == 202 and r.json()["pruned"] == 0 assert any(g["id"] == "g-a" for g in (await client.get("/v1/groups")).json()["groups"]) @pytest.mark.asyncio async def test_federation_cannot_revoke_a_user(client): admin = await _admin(client) peer = Peer("peer-a.example") await _register_peer(client, admin, peer) r = await client.post("/mhp/revoke", headers=peer.header(), json={"token": peer.revocation("user", "some-user-id")}) assert r.status_code == 202 and r.json()["pruned"] == 0