aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py82
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"