diff options
Diffstat (limited to 'packages/meshbay-node/tests')
| -rw-r--r-- | packages/meshbay-node/tests/test_roster_pairing.py | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py index 88e426d..435cc76 100644 --- a/packages/meshbay-node/tests/test_roster_pairing.py +++ b/packages/meshbay-node/tests/test_roster_pairing.py @@ -914,3 +914,85 @@ async def test_someone_elses_signature_does_not_remove_it(tmp_path, roster): assert _last(session).get("detail") == "Signature verification failed" assert (tmp_path / "shared" / "theirs").is_dir(), ( "a member's signature removed a directory — only the operator may") + + +# ── Removing a member ──────────────────────────────────────────────────────── + +async def test_revoking_needs_an_operator_signature(tmp_path, roster): + from meshbay_common.adminop import admin_transcript + + sk_op, pk_op, pk_x_op = _keypair() + await roster.pin_identity("grenet", "grenet", pk_op, pk_x_op, "code") + await roster.set_member("", "grenet", ROLE_OPERATOR, "active", "local-cli") + + sk_m, pk_m, pk_x_m = _keypair() + await roster.pin_identity("victim", "victim", pk_m, pk_x_m, "code") + await roster.set_member("g1", "victim", ROLE_MEMBER, "active", "grenet") + + session = _session(tmp_path, roster, group_id="g1", gek=generate_gek()) + session._admin_ops = {} + session._ctx["has_admin_authority"] = True + session._ctx["peers"] = {} + + transcript = admin_transcript( + op="member_revoke", node_pk_b64=session._node_pk_b64(), group_id="g1", + subject="victim", nonce=b"\x33" * 32, ts=int(time.time())) + + # A member's own signature is not enough. + await session._admin_exec_member_revoke( + {"op": "member_revoke", "subject": "victim"}, transcript, + sk_m.sign(transcript)) + assert _last(session).get("detail") == "Signature verification failed" + assert (await roster.get_member("g1", "victim"))["status"] == "active" + + # The operator's is. + await session._admin_exec_member_revoke( + {"op": "member_revoke", "subject": "victim"}, transcript, + sk_op.sign(transcript)) + assert _last(session)["type"] == "member_revoke_ack" + assert (await roster.get_member("g1", "victim"))["status"] == "revoked" + + +async def test_revoking_is_confined_to_the_group_it_was_asked_for(tmp_path, roster): + """ + A node hosting two groups must not lose someone from both. Their pinned + identity survives as well — forgetting a key is `member unpin`, and saying + "remove them" should not silently do it. + """ + from meshbay_common.adminop import admin_transcript + + sk_op, pk_op, pk_x_op = _keypair() + await roster.pin_identity("grenet", "grenet", pk_op, pk_x_op, "code") + await roster.set_member("", "grenet", ROLE_OPERATOR, "active", "local-cli") + + _, pk_m, pk_x_m = _keypair() + await roster.pin_identity("both", "both", pk_m, pk_x_m, "code") + await roster.set_member("g1", "both", ROLE_MEMBER, "active", "grenet") + await roster.set_member("g2", "both", ROLE_MEMBER, "active", "grenet") + + session = _session(tmp_path, roster, group_id="g1", gek=generate_gek()) + session._admin_ops = {} + session._ctx["has_admin_authority"] = True + session._ctx["peers"] = {} + + transcript = admin_transcript( + op="member_revoke", node_pk_b64=session._node_pk_b64(), group_id="g1", + subject="both", nonce=b"\x44" * 32, ts=int(time.time())) + await session._admin_exec_member_revoke( + {"op": "member_revoke", "subject": "both"}, transcript, + sk_op.sign(transcript)) + + assert (await roster.get_member("g1", "both"))["status"] == "revoked" + assert (await roster.get_member("g2", "both"))["status"] == "active" + assert await roster.get_identity("both") is not None, ( + "the pinned identity was dropped; that is `member unpin`, not this") + + +async def test_an_operator_cannot_revoke_themselves(tmp_path, roster): + """It would leave the group with nobody able to invite or remove.""" + session = _session(tmp_path, roster, group_id="g1", gek=generate_gek()) + session._admin_ops = {} + session._ctx["has_admin_authority"] = True + + session._do_member_revoke({"user_id": session._user_id}) + assert _last(session).get("detail") == "Cannot revoke yourself" |