diff options
Diffstat (limited to 'packages/meshbay-node')
17 files changed, 303 insertions, 5678 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py index 4b619d7..f0505f7 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py @@ -719,6 +719,21 @@ class DirectoryIndexer: # The table now; the files when the scan ends. await self.on_change(self) + async def publish_roots(self) -> None: + """ + Tell every connected peer the table, after a root's flags were edited + in place (`ops.update_root`). + + A reload compares the edited set with itself and finds nothing to do, + so without this a directory made writable from the operator's own + machine stayed read-only on every open page until something else + happened to push the index. + """ + self._index.roots = self.roots.describe() + self._index.version = int(time.time()) + if self.on_change: + await self.on_change(self) + def _holds(self, root: Root) -> bool: return any(r.folded == root.folded and r.path == root.path for r in self.roots) diff --git a/packages/meshbay-node/src/meshbay_node/ops/chat.py b/packages/meshbay-node/src/meshbay_node/ops/chat.py index 469e907..539284b 100644 --- a/packages/meshbay-node/src/meshbay_node/ops/chat.py +++ b/packages/meshbay-node/src/meshbay_node/ops/chat.py @@ -17,7 +17,7 @@ log = logging.getLogger("meshbay_node.ops") # # The key a group's chat archive is encrypted under. Generated here, by the # node, and never by a member — the C5b rule is about key material arriving from -# outside, and this is the same rule that lets `gek_rotate` be a signed +# outside, and this is the same rule that lets a group key rotation be an # instruction rather than a delivery. # # An *epoch* rather than a rotation, and the distinction is the whole design: diff --git a/packages/meshbay-node/src/meshbay_node/ops/roots.py b/packages/meshbay-node/src/meshbay_node/ops/roots.py index 480fe63..9dbade4 100644 --- a/packages/meshbay-node/src/meshbay_node/ops/roots.py +++ b/packages/meshbay-node/src/meshbay_node/ops/roots.py @@ -174,11 +174,14 @@ async def update_root(state: dict, group_id: str, root_name: str, *, writable=match.writable, removable=match.removable) # Update the live RootSet so GET /api/groups returns correct data - # immediately, without waiting for the async reload to finish. + # immediately, without waiting for the async reload to finish — and the + # indexer's, which is normally the same object but need not be, since it + # is the one the table pushed to every peer is read from. live_roots: RootSet | None = state.get("groups_ctx", {}).get( group_id, {}).get("roots") - if live_roots: - for lr in live_roots.roots: + indexer = state.get("indexers", {}).get(group_id) + for rootset in {id(x): x for x in (live_roots, indexer and indexer.roots) if x}.values(): + for lr in rootset.roots: lr_name = lr.name or str(Path(lr.path).name) if fold(lr_name) == target: if writable is not None: @@ -187,6 +190,9 @@ async def update_root(state: dict, group_id: str, root_name: str, *, lr.removable = removable break + if indexer: + await indexer.publish_roots() + # Built from config when there is no live set, never returned empty: an # empty list is a *valid answer* meaning "this group has no directories", # and the client cannot tell it from "the node could not say". It would diff --git a/packages/meshbay-node/src/meshbay_node/ops/settings.py b/packages/meshbay-node/src/meshbay_node/ops/settings.py index eade6ca..906cd03 100644 --- a/packages/meshbay-node/src/meshbay_node/ops/settings.py +++ b/packages/meshbay-node/src/meshbay_node/ops/settings.py @@ -182,9 +182,9 @@ async def set_transfer_limits(state: dict, group_id: str, Same shape as every other operator setting: lives on the node (roster.db, not the hub and not node.toml, for the reason change 5 gives — a hub that - decided this would have authority over someone else's machine), signed - (webrtc_server checks the caller's admin authority before this runs), and - live, so the pools are updated in place rather than at the next restart. + decided this would have authority over someone else's machine), set on the + node's own machine (loopback API, CLI), and live, so the pools are updated + in place rather than at the next restart. """ roster = _roster(state) ctx = _group_ctx(state, group_id) diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/admin.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/admin.py index daaee62..9a6cbd1 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/admin.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/admin.py @@ -17,27 +17,20 @@ from meshbay_common.adminop import ( OP_CHAT_LINK_PREVIEW, OP_DIR_DELETE, OP_FILE_DELETE, - OP_GEK_ROTATE, - OP_GROUP_ATTACH, - OP_GROUP_DETACH, OP_INVITE_CANCEL, OP_INVITE_CREATE, OP_INVITE_LINK_CREATE, OP_MEMBER_REVOKE, - OP_MEMBER_UNPIN, OP_MUSICBRAINZ_ENABLED, - OP_ROOT_ADD, OP_ROOT_EJECT, OP_ROOT_PLUG, OP_ROOT_REMOVE, - OP_ROOT_UPDATE, OP_SEARCH_LISTED, OP_SET_SCAN_SETTINGS, OP_TMDB_CONFIG, OP_TMDB_ENABLED, OP_TMDB_OVERRIDE, OP_TMDB_REMATCH, - OP_TRANSFER_LIMITS, admin_transcript, ) from meshbay_common.crypto import pk_to_b64 @@ -62,28 +55,21 @@ _ADMIN_EXECUTORS = { OP_INVITE_CREATE: "_admin_exec_invite_create", OP_INVITE_LINK_CREATE: "_admin_exec_invite_link_create", OP_INVITE_CANCEL: "_admin_exec_invite_cancel", - OP_GEK_ROTATE: "_admin_exec_gek_rotate", - OP_MEMBER_UNPIN: "_admin_exec_member_unpin", OP_APPS_ENABLED: "_admin_exec_apps_enabled", - OP_TRANSFER_LIMITS: "_admin_exec_transfer_limits", OP_SET_SCAN_SETTINGS: "_admin_exec_set_scan_settings", OP_TMDB_CONFIG: "_admin_exec_tmdb_config", OP_TMDB_ENABLED: "_admin_exec_tmdb_enabled", OP_TMDB_OVERRIDE: "_admin_exec_tmdb_override", OP_TMDB_REMATCH: "_admin_exec_tmdb_rematch", OP_MUSICBRAINZ_ENABLED: "_admin_exec_musicbrainz_enabled", - OP_ROOT_ADD: "_admin_exec_root_add", OP_ROOT_REMOVE: "_admin_exec_root_remove", OP_APP_DIRECTORIES: "_admin_exec_app_directories", OP_CHAT_DIRECTORY: "_admin_exec_chat_directory", OP_CHAT_LINK_PREVIEW: "_admin_exec_chat_link_preview", OP_SEARCH_LISTED: "_admin_exec_search_listed", OP_CHAT_EPOCH: "_admin_exec_chat_epoch", - OP_ROOT_UPDATE: "_admin_exec_root_update", OP_ROOT_EJECT: "_admin_exec_root_eject", OP_ROOT_PLUG: "_admin_exec_root_plug", - OP_GROUP_ATTACH: "_admin_exec_group_attach", - OP_GROUP_DETACH: "_admin_exec_group_detach", } @@ -180,49 +166,12 @@ class AdminMixin: says "the hub says you are the owner", which is the one thing NS4 and M3 rule out: a hub that can name the operator can install itself as node administrator. It rides the handshake ack so a client knows whether - to offer the Node page at all, and every operation is gated on - `_operator_device()` below. + to offer operator controls at all; every operation is gated on a + signature (`_verify_admin_sig`). """ node_user_id = self._ctx.get("node_user_id") return bool(node_user_id and self._user_id == node_user_id) - async def _operator_device(self) -> bool: - """ - Whether this connection may run the node's own controls. - - Two things, and the second is the one that cannot be forged: - - - the account is the one this node belongs to (`_is_node_admin`), which - is what keeps node-wide controls with the machine's owner rather than - with every paired operator of every group on it; and - - **the device on this connection proved a key the node pinned as an - operator**. `device_hello` is signed over a transcript naming this - node, this group and this connection's nonce, and `operator_pks()` is - rebuilt from the roster on each call, so an unpinned browser and a - revoked one are both refused at once. - - The second clause is the fix for the door this used to leave open. - `node_status`, `node_settings_set`, `roster_read`, `denylist_read`, - `denylist_clear` and `node_reload` were gated on the account id alone — - a value the hub chooses. An active hub that can also reach the group key - (which §3.5 concedes it can in an open-join group) could therefore mint - a token for the owner's account and read `node_status`, which lists - every group on the node with the operator's **absolute paths**, or clear - the denylist, which is the persisted revocation H4 exists to keep. - - It holds no user keys and cannot countersign anything, so it cannot - produce a `device_hello` — which is the same property device linking - rests on (§3.3), applied to the node's own surface. - """ - if not self._is_node_admin(): - return False - if not self._device_confirmed or not self._pinned_pk: - return False - roster = self._ctx.get("roster") - if roster is None: - return False - return self._pinned_pk in await roster.operator_pks() - def _has_admin_authority(self) -> bool: """ Cheap synchronous pre-check: is there anyone who could authorize this? diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py index 493d7f0..dc43ae2 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/chat.py @@ -198,9 +198,9 @@ class ChatMixin: There is no switch to turn chat encryption on: MNP 2.0 has no plaintext chat to fall back to. What an operator may want to do deliberately is - move the key on — the same instruction as `gek_rotate`, and signed for - the same reason. The removals that matter (member revoke, member unpin, - device revoke, `gek_rotate`) already open one by themselves. + move the key on, which is signed like the rest. The removals that matter + (member revoke, member unpin, device revoke, a group key rotation) + already open one by themselves. """ group_id = str(msg.get("group_id", "")).strip() or self._group_id if not group_id: diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/dispatch.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/dispatch.py index ee2e3a1..4bf9dbb 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/dispatch.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/dispatch.py @@ -48,7 +48,6 @@ _HANDLERS = { MNP.DEVICE_REVOKE: ("_do_device_revoke", SPAWNED), MNP.DEVICE_HELLO: ("_do_device_hello", SPAWNED), MNP.APPS_ENABLED: ("_do_apps_enabled", INLINE), - MNP.TRANSFER_LIMITS: ("_do_transfer_limits", INLINE), MNP.SET_SCAN_SETTINGS: ("_do_set_scan_settings", INLINE), MNP.TMDB_CONFIG: ("_do_tmdb_config", INLINE), MNP.TMDB_ENABLED: ("_do_tmdb_enabled", INLINE), @@ -68,21 +67,9 @@ _HANDLERS = { MNP.MUSIC_META_REQ: ("_do_music_meta_request", SPAWNED), MNP.AUDIO_TRANSCODE_REQ: ("_do_audio_transcode_request", SPAWNED), MNP.SUBTITLE_REQ: ("_do_subtitle_request", SPAWNED), - MNP.MEMBER_UNPIN: ("_do_member_unpin", INLINE), - MNP.GEK_ROTATE: ("_do_gek_rotate", INLINE), - MNP.NODE_STATUS: ("_do_node_status", SPAWNED), - MNP.ROOT_ADD: ("_do_root_add", INLINE), MNP.ROOT_REMOVE: ("_do_root_remove", INLINE), - MNP.ROOT_UPDATE: ("_do_root_update", INLINE), MNP.ROOT_EJECT: ("_do_root_eject", INLINE), MNP.ROOT_PLUG: ("_do_root_plug", INLINE), - MNP.ROSTER_READ: ("_do_roster_read", SPAWNED), - MNP.DENYLIST_READ: ("_do_denylist_read", SPAWNED), - MNP.DENYLIST_CLEAR: ("_do_denylist_clear", SPAWNED), - MNP.GROUP_ATTACH: ("_do_group_attach", INLINE), - MNP.GROUP_DETACH: ("_do_group_detach", INLINE), - MNP.NODE_SETTINGS_SET: ("_do_node_settings_set", SPAWNED), - MNP.NODE_RELOAD: ("_do_node_reload", SPAWNED), MNP.KEYPAIR_BUNDLE_STORE: ("_do_keypair_bundle_store", SPAWNED), MNP.KEYPAIR_BUNDLE_DELETE: ("_do_keypair_bundle_delete", SPAWNED), MNP.USER_BLOB_STORE: ("_do_user_blob_store", SPAWNED), diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/group_ops.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/group_ops.py index 3756eac..a94e2aa 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/group_ops.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/group_ops.py @@ -5,9 +5,7 @@ from meshbay_common import MNP_VERSION from meshbay_common.adminop import ( OP_APP_DIRECTORIES, OP_APPS_ENABLED, - OP_GEK_ROTATE, OP_MEMBER_REVOKE, - OP_MEMBER_UNPIN, OP_SEARCH_LISTED, ) from meshbay_common.groupbox import PURPOSE_ROSTER, seal @@ -41,88 +39,6 @@ class GroupOpsMixin: return self._issue_admin_challenge(OP_MEMBER_REVOKE, user_id) - def _do_gek_rotate(self, msg: dict) -> None: - """ - Ask for a new group key. Operator only, and signed. - - This is what actually removes a revoked member's access: revocation - stops the node serving the *next* key, and they still hold the current - one. The node generates the replacement itself — nothing arriving here - contributes key material, which is what the C5b rule is about. - """ - group_id = str(msg.get("group_id", "")).strip() or self._group_id - if not group_id: - self._send({"type": "error", "detail": "No group on this connection"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - self._issue_admin_challenge(OP_GEK_ROTATE, group_id, group_id=group_id) - - async def _admin_exec_gek_rotate( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", f"gek_rotate:{pending['subject'][:8]}") - return - try: - result = await self._run_op( - ops.set_gek, pending["subject"], rotate=True) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - # The operator is rotating because somebody left, and the chat archive - # key is not derived from the group key — so rotating that one does not - # move this one. Doing both here is what makes "rotate after a removal" - # mean the same thing for chat as it does for files. - await self._new_chat_epoch(pending["subject"], "gek_rotate") - self._audit("gek_rotate", pending["subject"]) - self._send({ - "type": MNP.GEK_ROTATE_ACK, "v": MNP_VERSION, - "group_id": pending["subject"], - "authorized_members": result.get("authorized_members", 0), - # Said plainly, because rotating is the step people skip: content - # already downloaded stays readable to whoever holds it. - "note": "members re-receive the key on their next connect; content " - "already downloaded is unaffected", - }) - - def _do_member_unpin(self, msg: dict) -> None: - """Forget a pinned identity, so someone can pair again with a new key.""" - user_id = str(msg.get("user_id", "")).strip() - if not user_id: - self._send({"type": "error", "detail": "Missing user_id"}) - return - if user_id == self._user_id: - # Unpinning yourself over the connection your pin authorizes would - # end that connection's authority mid-operation. - self._send({"type": "error", "detail": "Cannot unpin yourself"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - self._issue_admin_challenge(OP_MEMBER_UNPIN, user_id) - - async def _admin_exec_member_unpin( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - user_id = pending["subject"] - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", f"member_unpin:{user_id[:8]}") - return - try: - # The new chat epochs and the closed sessions are the op's own - # (`ops.members._after_removal`), for every door alike. - await self._run_op(ops.unpin_member, user_id) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - self._audit("member_unpin", user_id) - self._send({"type": MNP.MEMBER_UNPIN_ACK, "v": MNP_VERSION, - "user_id": user_id}) - # Every "application" a group can show. Photos joins this set (and # apps.js's registry, client-side) when it lands; nothing else about # this handler changes. DEFAULT_APPS (roster.py) deliberately does not diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/node_ops.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/node_ops.py index f7bbbfa..237a359 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/node_ops.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/node_ops.py @@ -1,21 +1,16 @@ -"""The operator's controls over the node itself: status and settings, roster -and denylist, roots, hosted groups, reload, scan pacing and transfer limits.""" +"""The operator's controls over a group's roots and scan pacing that MNP carries: +removing, ejecting and plugging a root. What widens the sharing, and the node's +own status, settings, roster and denylist, are the loopback API's and the CLI's +(MNP 6.0).""" import logging from meshbay_common import MNP_VERSION from meshbay_common.adminop import ( - OP_GROUP_ATTACH, - OP_GROUP_DETACH, - OP_ROOT_ADD, OP_ROOT_EJECT, OP_ROOT_PLUG, OP_ROOT_REMOVE, - OP_ROOT_UPDATE, OP_SET_SCAN_SETTINGS, - OP_TRANSFER_LIMITS, - group_attach_subject, - root_add_subject, ) from meshbay_common.protocol import MNP @@ -60,64 +55,6 @@ class NodeOpsMixin: self._issue_admin_challenge( OP_SET_SCAN_SETTINGS, f"{reconcile:g},{debounce:g}") - MIN_TRANSFER_LIMIT = 1 - MAX_TRANSFER_LIMIT = 32 - - def _do_transfer_limits(self, msg: dict) -> None: - """How many transfers one member may run at once in this group. - - Zero is not "unlimited" and is refused: a member who may not transfer at - all is a member the operator revokes, and reading 0 as no-limit would - make the most dangerous value the easiest to type by accident. - """ - try: - downloads = int(msg.get("downloads")) - uploads = int(msg.get("uploads")) - except (TypeError, ValueError): - self._send({"type": "error", "detail": "Invalid transfer limits"}) - return - for value in (downloads, uploads): - if not (self.MIN_TRANSFER_LIMIT <= value <= self.MAX_TRANSFER_LIMIT): - self._send({"type": "error", - "detail": f"transfer limits must be between " - f"{self.MIN_TRANSFER_LIMIT} and " - f"{self.MAX_TRANSFER_LIMIT}"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - self._issue_admin_challenge(OP_TRANSFER_LIMITS, - f"d={downloads},u={uploads}") - - async def _admin_exec_transfer_limits( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - try: - parts = dict(p.split("=") for p in pending["subject"].split(",")) - downloads, uploads = int(parts["d"]), int(parts["u"]) - except (ValueError, KeyError): - self._send({"type": "error", "detail": "Invalid transfer limits"}) - return - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", f"transfer_limits:{pending['subject']}") - return - try: - result = await self._run_op( - ops.set_transfer_limits, self._group_id or "", downloads, uploads) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - self._audit("transfer_limits", pending["subject"]) - - notice = {"type": MNP.TRANSFER_LIMITS_ACK, "v": MNP_VERSION, - "limits": result["limits"]} - for session in list(self._peer_registry().values()): - try: - session._send(notice) - except Exception: - pass - async def _admin_exec_set_scan_settings( self, pending: dict, transcript: bytes, sig: bytes, ) -> None: @@ -146,245 +83,6 @@ class NodeOpsMixin: except Exception: pass - # ── Node management (D5) ───────────────────────────────────────────────── - - async def _do_node_status(self, msg: dict) -> None: - """All groups, roots, peers — the operator's overview. - - Including every root's absolute path, which is why this is gated on a - proved operator device and not on an account the hub named. - """ - node_uid = self._ctx.get("node_user_id") - log.info("node_status: user=%s node_user=%s owner=%s device=%s", - self._user_id, node_uid, self._is_node_admin(), - "confirmed" if self._device_confirmed else "unidentified") - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - try: - result = await self._run_op(ops.list_groups) - self._send({"type": MNP.NODE_STATUS_ACK, "v": MNP_VERSION, **result}) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - except Exception as e: - log.error("node_status failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - - async def _do_node_settings_set(self, msg: dict) -> None: - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - settings = msg.get("settings", {}) - if not settings: - self._send({"type": "error", "detail": "No settings provided"}) - return - try: - result = await self._run_op(ops.set_node_settings, settings) - self._send({"type": MNP.NODE_SETTINGS_SET_ACK, "v": MNP_VERSION, - **result}) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - except Exception as e: - log.error("node_settings_set failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - - async def _do_roster_read(self, msg: dict) -> None: - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - group_id = str(msg.get("group_id", "")).strip() - try: - result = await self._run_op(ops.read_roster, group_id) - self._send({"type": MNP.ROSTER_READ_ACK, "v": MNP_VERSION, **result}) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - except Exception as e: - log.error("roster_read failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - - async def _do_denylist_read(self, msg: dict) -> None: - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - try: - result = await self._run_op(ops.read_denylist) - self._send({"type": MNP.DENYLIST_READ_ACK, "v": MNP_VERSION, **result}) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - except Exception as e: - log.error("denylist_read failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - - async def _do_denylist_clear(self, msg: dict) -> None: - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - subject = str(msg.get("subject", "")).strip() - try: - result = await self._run_op(ops.clear_denylist, subject=subject) - self._audit("denylist_clear", subject or "all") - self._send({"type": MNP.DENYLIST_CLEAR_ACK, "v": MNP_VERSION, **result}) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - except Exception as e: - log.error("denylist_clear failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - - def _do_group_attach(self, msg: dict) -> None: - name = str(msg.get("name", "")).strip() - shared_dir = str(msg.get("shared_dir", "")).strip() - if not name or not shared_dir: - self._send({"type": "error", "detail": "Missing name or shared_dir"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - # `upload_dir` is not read here any more, and a client still sending it - # is ignored rather than obeyed: on load it forces every other root - # read-only, which is the model the RO/RW one replaced. A second - # writable directory is `root_add` with `writable`. - writable = bool(msg.get("writable", True)) - # The directory being exposed is signed, not only the group's name. - self._issue_admin_challenge( - OP_GROUP_ATTACH, group_attach_subject(name, shared_dir, writable), - payload={"name": name, "shared_dir": shared_dir, "writable": writable}, - group_id="") - - async def _admin_exec_group_attach( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", - f"group_attach:{pending['subject'][:16]}") - return - p = pending.get("payload") or {} - try: - result = await self._run_op( - ops.attach_group, p["name"], p["shared_dir"], - writable=bool(p.get("writable", True))) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - self._audit("group_attach", pending["subject"]) - self._send({"type": MNP.GROUP_ATTACH_ACK, "v": MNP_VERSION, **result}) - state = self._ctx.get("daemon_state") - reload_fn = state.get("reload_fn") if state else None - if reload_fn: - try: - await reload_fn() - except Exception as e: - log.error("Reload after group_attach failed: %s", e) - - def _do_group_detach(self, msg: dict) -> None: - name = str(msg.get("name", "")).strip() - if not name: - self._send({"type": "error", "detail": "Missing group name or id"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - self._issue_admin_challenge( - OP_GROUP_DETACH, name, - payload={"name": name}, - group_id="") - - async def _admin_exec_group_detach( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", - f"group_detach:{pending['subject'][:16]}") - return - p = pending.get("payload") or {} - try: - result = await self._run_op(ops.detach_group, p["name"]) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - self._audit("group_detach", pending["subject"]) - self._send({"type": MNP.GROUP_DETACH_ACK, "v": MNP_VERSION, **result}) - state = self._ctx.get("daemon_state") - reload_fn = state.get("reload_fn") if state else None - if reload_fn: - try: - await reload_fn() - except Exception as e: - log.error("Reload after group_detach failed: %s", e) - - async def _do_node_reload(self, msg: dict) -> None: - if not await self._operator_device(): - self._send({"type": "error", "detail": "Not the node operator", - "code": "not_operator"}) - return - state = self._ctx.get("daemon_state") - reload_fn = state.get("reload_fn") if state else None - if not reload_fn: - self._send({"type": "error", "detail": "Reload not available"}) - return - try: - await reload_fn() - self._send({"type": MNP.NODE_RELOAD_ACK, "v": MNP_VERSION, - "status": "reloaded"}) - except Exception as e: - log.error("node_reload failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Reload failed"}) - - def _do_root_add(self, msg: dict) -> None: - target_group = str(msg.get("group_id", "")).strip() - path = str(msg.get("path", "")).strip() - if not target_group or not path: - self._send({"type": "error", "detail": "Missing group_id or path"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - payload = { - "group_id": target_group, "path": path, - "name": str(msg.get("name", ""))[:128], - "kind": str(msg.get("kind", "generic"))[:16], - "writable": bool(msg.get("writable", msg.get("upload", False))), - "removable": bool(msg.get("removable", False)), - } - # Everything the executor acts on is signed — `writable` decides whether - # every member may write there. The group is in the transcript itself. - self._issue_admin_challenge( - OP_ROOT_ADD, - root_add_subject(path, payload["name"], payload["kind"], - payload["writable"], payload["removable"]), - payload=payload, group_id=target_group) - - async def _admin_exec_root_add( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", f"root_add:{pending['subject'][:24]}") - return - p = pending["payload"] - try: - result = await self._run_op( - ops.add_root, p["group_id"], p["path"], - name=p.get("name", ""), kind=p.get("kind", "generic"), - writable=p.get("writable", False), - removable=p.get("removable", False)) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - except Exception as e: - log.error("root_add failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - return - self._audit("root_add", f"{p['path']}→{p['group_id'][:8]}") - await self._retarget_indexer(p["group_id"]) - self._send({"type": MNP.ROOT_ADD_ACK, "v": MNP_VERSION, **result}) - def _do_root_remove(self, msg: dict) -> None: target_group = str(msg.get("group_id", "")).strip() root_name = str(msg.get("root_name", "")).strip() @@ -421,59 +119,6 @@ class NodeOpsMixin: await self._retarget_indexer(p["group_id"]) self._send({"type": MNP.ROOT_REMOVE_ACK, "v": MNP_VERSION, **result}) - def _do_root_update(self, msg: dict) -> None: - target_group = str(msg.get("group_id", self._group_id or "")).strip() - root_name = str(msg.get("root_name", "")).strip() - if not target_group or not root_name: - self._send({"type": "error", "detail": "Missing group_id or root_name"}) - return - if not self._has_admin_authority(): - self._send({"type": "error", "detail": "No authorized key for this"}) - return - updates = [] - if "writable" in msg: - updates.append(f"rw={'on' if msg['writable'] else 'off'}") - if "removable" in msg: - updates.append(f"rem={'on' if msg['removable'] else 'off'}") - subject = f"{root_name}:{','.join(updates)}" if updates else root_name - self._issue_admin_challenge( - OP_ROOT_UPDATE, subject, - payload={ - "group_id": target_group, "root_name": root_name, - "writable": msg.get("writable"), - "removable": msg.get("removable"), - }, - group_id=target_group) - - async def _admin_exec_root_update( - self, pending: dict, transcript: bytes, sig: bytes, - ) -> None: - if not await self._verify_admin_sig(transcript, sig): - self._send({"type": "error", "detail": "Signature verification failed"}) - self._audit("admin_auth_failed", - f"root_update:{pending['subject'][:24]}") - return - p = pending["payload"] - try: - result = await self._run_op( - ops.update_root, p["group_id"], p["root_name"], - writable=p.get("writable"), removable=p.get("removable")) - except ops.OpError as e: - self._send({"type": "error", "detail": e.message}) - return - except Exception as e: - log.error("root_update failed: %s", e, exc_info=True) - self._send({"type": "error", "detail": "Internal error"}) - return - self._audit("root_update", pending["subject"]) - await self._retarget_indexer(p["group_id"]) - notice = {"type": MNP.ROOT_UPDATE_ACK, "v": MNP_VERSION, **result} - for uid, session in list(self._peer_registry().items()): - try: - session._send(notice) - except Exception: - pass - def _do_root_eject(self, msg: dict) -> None: target_group = str(msg.get("group_id", self._group_id or "")).strip() root_name = str(msg.get("root_name", "")).strip() @@ -558,24 +203,21 @@ class NodeOpsMixin: async def _retarget_indexer(self, group_id: str) -> None: """ - Pick up a root that was just added to or removed from node.toml. + Pick up a root that was just removed from node.toml. Through the daemon's own reload, which is what the loopback API has always done after the same operations (`ui/app.py`). This used to re-point the indexer at `groups_ctx[gid]["roots"]` instead — the very object the op had just edited — so `retarget` diffed a set against - itself, found no new names, scanned nothing, and dropped nothing. A - directory added over MNP reached node.toml and was invisible until a - restart; one removed kept serving its files. + itself, found no new names, scanned nothing, and dropped nothing: a + directory removed over MNP kept serving its files until a restart. Two front doors doing different things is the shape `ops.py` exists to prevent, and this was it: the loopback path worked and the MNP path did - not, which is why it survived until the operator added a directory from - a browser. + not. - Not awaited: a reload rescans, and a new library is minutes. The ack - the caller sends carries the set the node is moving to, and the - `index_sync` that follows the scan carries what it found. + Not awaited: a reload can be long. The ack the caller sends carries the + set the node is moving to. """ state = self._ctx.get("daemon_state") if not state: diff --git a/packages/meshbay-node/src/meshbay_node/ui/app.py b/packages/meshbay-node/src/meshbay_node/ui/app.py index f810903..db11a09 100644 --- a/packages/meshbay-node/src/meshbay_node/ui/app.py +++ b/packages/meshbay-node/src/meshbay_node/ui/app.py @@ -535,10 +535,9 @@ def create_ui_app(state: dict) -> FastAPI: @app.put("/api/groups/{group_id}/transfer-limits") async def set_transfer_limits(group_id: str, payload: dict): - # The same `ops.set_transfer_limits` the signed MNP handler calls. The - # op existed with only that one door, and nothing anywhere opened it — - # so the per-member cap sat at its default of 2 with no way to change - # it, which from outside is indistinguishable from a hardcoded 2. + # The only door to `ops.set_transfer_limits` (the CLI uses it). It once + # had only a signed MNP message, which nothing anywhere sent — so the + # per-member cap sat at its default of 2 with no way to change it. return await _op(lambda: ops.set_transfer_limits( state, group_id, int(payload.get("downloads", 0)), int(payload.get("uploads", 0)))) diff --git a/packages/meshbay-node/tests/golden/dispatch.json b/packages/meshbay-node/tests/golden/dispatch.json index a7bb543..61bbc45 100644 --- a/packages/meshbay-node/tests/golden/dispatch.json +++ b/packages/meshbay-node/tests/golden/dispatch.json @@ -76,30 +76,6 @@ "_admin_exec_file_delete" ] }, - "gek_rotate": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_gek_rotate" - ] - }, - "group_attach": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_group_attach" - ] - }, - "group_detach": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_group_detach" - ] - }, "invite_cancel": { "audit": [], "log": [], @@ -132,14 +108,6 @@ "_admin_exec_member_revoke" ] }, - "member_unpin": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_member_unpin" - ] - }, "musicbrainz_enabled": { "audit": [], "log": [], @@ -160,14 +128,6 @@ ], "spawned": [] }, - "root_add": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_root_add" - ] - }, "root_eject": { "audit": [], "log": [], @@ -192,14 +152,6 @@ "_admin_exec_root_remove" ] }, - "root_update": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_root_update" - ] - }, "search_listed": { "audit": [], "log": [], @@ -247,14 +199,6 @@ "spawned": [ "_admin_exec_tmdb_rematch" ] - }, - "transfer_limits": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_admin_exec_transfer_limits" - ] } }, "dispatch": { @@ -2068,7 +2012,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2391,7 +2335,7 @@ "subject": "gggggggggggggggggggggggggggggggg", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2410,7 +2354,7 @@ "subject": "['x']", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2429,7 +2373,7 @@ "subject": "7", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2448,7 +2392,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2718,7 +2662,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2732,7 +2676,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2746,7 +2690,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2760,7 +2704,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2774,7 +2718,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2788,7 +2732,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2802,7 +2746,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -2816,7 +2760,7 @@ "messages": [], "req_id": 4242, "type": "chat_hist_resp", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -4029,646 +3973,6 @@ "sent": [], "spawned": [] }, - "denylist_clear | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_clear" - ] - }, - "denylist_clear_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_clear_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_clear_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_denylist_read" - ] - }, - "denylist_read_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "denylist_read_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "denylist_read_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "device_add | challenged | bare": { "audit": [], "log": [], @@ -8377,1132 +7681,6 @@ "sent": [], "spawned": [] }, - "gek_rotate | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "gggggggggggggggggggggggggggggggg", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "gek_rotate", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "gggggggggggggggggggggggggggggggg", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "gek_rotate | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "['x']", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "gek_rotate", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "['x']", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "gek_rotate | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "7", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "gek_rotate", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "7", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "gek_rotate | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "x", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "gek_rotate", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "x", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "gek_rotate_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "gek_rotate_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "gek_rotate_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing name or shared_dir", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing name or shared_dir", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_attach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"name\":\"['x']\",\"shared_dir\":\"['x']\",\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_attach | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_attach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"name\":\"7\",\"shared_dir\":\"7\",\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_attach | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_attach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"name\":\"x\",\"shared_dir\":\"x\",\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_attach_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_attach_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_attach_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group name or id", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group name or id", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_detach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "['x']", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_detach | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_detach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "7", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_detach | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "group_detach", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "x", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "group_detach_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "group_detach_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "group_detach_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "group_roster_req | challenged | bare": { "audit": [], "log": [], @@ -11641,7 +9819,7 @@ "subject": "link:gggggggggggggggggggggggggggggggg", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -13740,7 +11918,7 @@ "subject": "['x']", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -13759,7 +11937,7 @@ "subject": "7", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -13778,7 +11956,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -13943,379 +12121,6 @@ "sent": [], "spawned": [] }, - "member_unpin | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing user_id", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing user_id", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "gggggggggggggggggggggggggggggggg", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "member_unpin", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "['x']", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "member_unpin | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "gggggggggggggggggggggggggggggggg", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "member_unpin", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "7", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "member_unpin | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "gggggggggggggggggggggggggggggggg", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "member_unpin", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "x", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "member_unpin_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "member_unpin_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "member_unpin_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "music_meta_req | challenged | bare": { "audit": [], "log": [], @@ -15148,966 +12953,6 @@ "sent": [], "spawned": [] }, - "node_reload | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_reload" - ] - }, - "node_reload_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_reload_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_reload_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_settings_set" - ] - }, - "node_settings_set_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_settings_set_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_settings_set_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_node_status" - ] - }, - "node_status_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "node_status_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "node_status_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "ping | challenged | bare": { "audit": [], "log": [], @@ -16212,7 +13057,7 @@ "req_id": 4242, "token": null, "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16227,7 +13072,7 @@ "x" ], "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16240,7 +13085,7 @@ "req_id": 4242, "token": 7, "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16253,7 +13098,7 @@ "req_id": 4242, "token": "x", "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16266,7 +13111,7 @@ "req_id": 4242, "token": null, "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16281,7 +13126,7 @@ "x" ], "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16294,7 +13139,7 @@ "req_id": 4242, "token": 7, "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16307,7 +13152,7 @@ "req_id": 4242, "token": "x", "type": "pong", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -16472,379 +13317,6 @@ "sent": [], "spawned": [] }, - "root_add | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group_id or path", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group_id or path", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "['x']", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_add", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"kind\":\"['x']\",\"name\":\"['x']\",\"path\":\"['x']\",\"removable\":true,\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_add | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "7", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_add", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"kind\":\"7\",\"name\":\"7\",\"path\":\"7\",\"removable\":true,\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_add | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "x", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_add", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "{\"kind\":\"x\",\"name\":\"x\",\"path\":\"x\",\"removable\":true,\"writable\":true}", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_add_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_add_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_add_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "root_eject | challenged | bare": { "audit": [], "log": [], @@ -17015,7 +13487,7 @@ "subject": "['x']", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17034,7 +13506,7 @@ "subject": "7", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17053,7 +13525,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17388,7 +13860,7 @@ "subject": "['x']", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17407,7 +13879,7 @@ "subject": "7", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17426,7 +13898,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17761,7 +14233,7 @@ "subject": "['x']", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17780,7 +14252,7 @@ "subject": "7", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17799,7 +14271,7 @@ "subject": "x", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -17964,699 +14436,6 @@ "sent": [], "spawned": [] }, - "root_update | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group_id or root_name", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Missing group_id or root_name", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "['x']", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_update", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "['x']:rw=on,rem=on", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_update | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "7", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_update", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "7:rw=on,rem=on", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_update | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "x", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "root_update", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "x:rw=on,rem=on", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "root_update_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "root_update_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "root_update_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read | member | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | member | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | member | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | member | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | operator | bare": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | operator | lists": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | operator | numbers": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read | operator | strings": { - "audit": [], - "log": [], - "sent": [], - "spawned": [ - "_do_roster_read" - ] - }, - "roster_read_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "roster_read_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "roster_read_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "search_listed | challenged | bare": { "audit": [], "log": [], @@ -21119,7 +16898,7 @@ "subject": "{\"language\":null,\"token\":null}", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -21162,7 +16941,7 @@ "subject": "{\"language\":\"x\",\"token\":\"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881\"}", "ts": "<volatile>", "type": "admin_challenge", - "v": "5.0" + "v": "6.0" } ], "spawned": [] @@ -22867,365 +18646,6 @@ "sent": [], "spawned": [] }, - "transfer_limits | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | member | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | member | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | member | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "No authorized key for this", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | member | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | operator | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | operator | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits | operator | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "group_id": "gggggggggggggggggggggggggggggggg", - "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=", - "nonce": "<volatile>", - "op": "transfer_limits", - "op_id": "<volatile>", - "req_id": 4242, - "subject": "d=7,u=7", - "ts": "<volatile>", - "type": "admin_challenge", - "v": "5.0" - } - ], - "spawned": [] - }, - "transfer_limits | operator | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Invalid transfer limits", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | challenged | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | challenged | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | challenged | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | challenged | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | fresh | bare": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | fresh | lists": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | fresh | numbers": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | fresh | strings": { - "audit": [], - "log": [], - "sent": [ - { - "detail": "Handshake required", - "req_id": 4242, - "type": "error" - } - ], - "spawned": [] - }, - "transfer_limits_ack | member | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | member | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | member | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | member | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | operator | bare": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | operator | lists": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | operator | numbers": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, - "transfer_limits_ack | operator | strings": { - "audit": [], - "log": [ - "WARNING Unknown MNP message type on DataChannel: %s" - ], - "sent": [], - "spawned": [] - }, "transfer_open | challenged | bare": { "audit": [], "log": [], diff --git a/packages/meshbay-node/tests/test_admin_challenge_bounds.py b/packages/meshbay-node/tests/test_admin_challenge_bounds.py index fd3b2f1..9dcb750 100644 --- a/packages/meshbay-node/tests/test_admin_challenge_bounds.py +++ b/packages/meshbay-node/tests/test_admin_challenge_bounds.py @@ -3,8 +3,9 @@ What a connection may leave waiting for a signature (docs/MESHBAY_DESIGN.md §13 Anyone authenticated can ask for an admin challenge — the signature is checked later — so a member who never answers must not make the node keep every request. -Measured before the bound: 200 `root_add` of 1 MiB each from a plain member held -200 pending operations and ~400 MiB for the life of the connection. +Measured before the bound: 200 `root_add` (an op since removed) of 1 MiB each +from a plain member held 200 pending operations and ~400 MiB for the life of +the connection. """ import struct @@ -48,23 +49,24 @@ def _member_session(): return s -def _root_add(s, path: str) -> dict: - s._dispatch_message({"type": "root_add", "group_id": GROUP, "path": path}) +def _ask(s, path: str) -> dict: + """A signed op whose subject is whatever the caller sends.""" + s._dispatch_message({"type": "chat_directory", "path": path}) return s._channel.sent[-1] def test_a_member_cannot_pile_up_challenges(): s = _member_session() for i in range(MAX_PENDING_ADMIN_OPS): - assert _root_add(s, f"/srv/{i}")["type"] == "admin_challenge" - refused = _root_add(s, "/srv/one-too-many") + assert _ask(s, f"/srv/{i}")["type"] == "admin_challenge" + refused = _ask(s, "/srv/one-too-many") assert refused["type"] == "error" and refused["code"] == "too_many_pending" assert len(s._admin_ops) == MAX_PENDING_ADMIN_OPS def test_an_oversized_request_is_not_kept(): s = _member_session() - refused = _root_add(s, "x" * (MAX_ADMIN_OP_BYTES + 1)) + refused = _ask(s, "x" * (MAX_ADMIN_OP_BYTES + 1)) assert refused["type"] == "error" and refused["code"] == "too_large" assert s._admin_ops == {} @@ -72,21 +74,21 @@ def test_an_oversized_request_is_not_kept(): def test_an_expired_challenge_frees_its_place(): s = _member_session() for i in range(MAX_PENDING_ADMIN_OPS): - _root_add(s, f"/srv/{i}") + _ask(s, f"/srv/{i}") for pending in s._admin_ops.values(): pending["ts"] -= 10_000 - assert _root_add(s, "/srv/after-expiry")["type"] == "admin_challenge" + assert _ask(s, "/srv/after-expiry")["type"] == "admin_challenge" assert len(s._admin_ops) == 1 def test_answering_a_challenge_frees_its_place(): s = _member_session() for i in range(MAX_PENDING_ADMIN_OPS): - _root_add(s, f"/srv/{i}") + _ask(s, f"/srv/{i}") op_id = next(iter(s._admin_ops)) s._dispatch_message({"type": "admin_response", "op_id": op_id, "signature": "!!"}) assert len(s._admin_ops) == MAX_PENDING_ADMIN_OPS - 1 - assert _root_add(s, "/srv/next")["type"] == "admin_challenge" + assert _ask(s, "/srv/next")["type"] == "admin_challenge" assert all(time.time() - p["ts"] < 5 for p in s._admin_ops.values()) @@ -95,27 +97,6 @@ def test_answering_a_challenge_frees_its_place(): # The signature covers the subject and nothing else of a request, so every value # the executor acts on has to be in it. -def test_root_add_signs_whether_members_may_write(): - from meshbay_common.adminop import root_add_subject - s = _member_session() - s._dispatch_message({"type": "root_add", "group_id": GROUP, "path": "/srv/drop", - "name": "Drop", "writable": True, "removable": False}) - challenge = s._channel.sent[-1] - assert challenge["subject"] == root_add_subject("/srv/drop", "Drop", "generic", - True, False) - assert challenge["subject"] != root_add_subject("/srv/drop", "Drop", "generic", - False, False) - - -def test_group_attach_signs_the_directory_it_exposes(): - from meshbay_common.adminop import group_attach_subject - s = _member_session() - s._dispatch_message({"type": "group_attach", "name": "photos", - "shared_dir": "/home/me/Photos"}) - assert s._channel.sent[-1]["subject"] == group_attach_subject( - "photos", "/home/me/Photos", True) - - def test_invite_create_signs_the_name_it_records(): from meshbay_common.adminop import invite_create_subject s = _member_session() diff --git a/packages/meshbay-node/tests/test_admin_ops_mnp.py b/packages/meshbay-node/tests/test_admin_ops_mnp.py index 7fd1c2b..898228e 100644 --- a/packages/meshbay-node/tests/test_admin_ops_mnp.py +++ b/packages/meshbay-node/tests/test_admin_ops_mnp.py @@ -1,17 +1,14 @@ """ -`gek_rotate` and `member_unpin` over MNP. +Rotating the group key and forgetting a pinned identity — `ops.set_gek` and +`ops.unpin_member`, which the Node page and the CLI reach over loopback — and +the H5 rule every signed MNP operation lives under. -Both are destructive and both are new, so the tests are negative assertions: -nobody without the operator's pinned key can reach them, a signature over the -wrong transcript does not count, and the operation cannot be triggered by the -request message alone. - -The rule these live under is worth restating, because it is easy to read -draft-v5 §5.1 as forbidding them: **"nothing arriving over MNP can activate a -GEK" is about key material arriving from outside** (C5b — a member handing the -node a key of their choosing). An operator-signed instruction where the node -generates the key with its own CSPRNG is a different shape, and it is the only -thing that finishes a revocation: the ex-member still holds the current key. +`gek_rotate` and `member_unpin` were MNP messages until 6.0. No client sent +them, so they went; what they did is still tested here, at the door that is +used. **"Nothing arriving over MNP can activate a GEK" is about key material +arriving from outside** (C5b): the node generates the new key with its own +CSPRNG, which is the only thing that finishes a revocation — the ex-member +still holds the current key. """ import base64 @@ -19,14 +16,10 @@ from pathlib import Path import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from meshbay_common.adminop import ( - OP_GEK_ROTATE, - OP_MEMBER_UNPIN, - admin_transcript, -) +from meshbay_common.adminop import OP_CHAT_EPOCH, admin_transcript from meshbay_common.crypto import pk_to_b64 from meshbay_common.join import ROLE_MEMBER, ROLE_OPERATOR -from meshbay_common.protocol import MNP +from meshbay_node import ops from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roster import open_roster from meshbay_node.transport.webrtc_server import WebRTCPeerSession @@ -127,58 +120,7 @@ async def _drain(session): session.spawned.clear() -async def _sign_and_exec(session, op: str, subject: str, sk, exec_fn): - challenge = _last(session) - assert challenge["type"] == "admin_challenge", challenge - transcript = admin_transcript( - op=op, node_pk_b64=session._node_pk_b64(), group_id=GROUP, - subject=subject, nonce=base64.b64decode(challenge["nonce"]), - ts=challenge["ts"]) - pending = session._admin_ops.get(challenge["op_id"]) or { - "op": op, "subject": subject} - await exec_fn(pending, transcript, sk.sign(transcript)) - - -# ── gek_rotate ─────────────────────────────────────────────────────────────── - -async def test_rotation_needs_an_operator(tmp_path, roster): - """Without a paired operator there is nobody who could sign, so the node - fails closed and says why rather than issuing a challenge nobody can meet.""" - session = await _session(tmp_path, roster, operator=False) - - session._do_gek_rotate({}) - - assert _last(session)["type"] == "error" - assert "authorized key" in _last(session)["detail"] - assert not session._admin_ops - - -async def test_the_request_alone_rotates_nothing(tmp_path, roster): - """The message asks; only a signature acts. A node that rotated here would - let any member lock the group out.""" - session = await _session(tmp_path, roster, operator=True) - before = session._ctx["groups"][GROUP]["gek"] - - session._do_gek_rotate({}) - - assert _last(session)["type"] == "admin_challenge" - assert session._ctx["groups"][GROUP]["gek"] == before - - -async def test_a_members_signature_does_not_rotate(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True) - sk_mallory, pk_mallory = _keypair() - await roster.pin_identity("mallory", "mallory", pk_mallory, pk_mallory, "code") - await roster.set_member(GROUP, "mallory", ROLE_MEMBER, "active", "grenet") - before = session._ctx["groups"][GROUP]["gek"] - - session._do_gek_rotate({}) - await _sign_and_exec(session, OP_GEK_ROTATE, GROUP, sk_mallory, - session._admin_exec_gek_rotate) - - assert _last(session)["type"] == "error" - assert session._ctx["groups"][GROUP]["gek"] == before - +# ── H5: a signature is for one operation ───────────────────────────────────── async def test_a_signature_over_another_operation_does_not_count(tmp_path, roster): """ @@ -191,15 +133,18 @@ async def test_a_signature_over_another_operation_does_not_count(tmp_path, roste control, and the test would pass while proving nothing. """ session = await _session(tmp_path, roster, operator=True) - before = session._ctx["groups"][GROUP]["gek"] + _, pk_bob = _keypair() + await roster.pin_identity("bob", "bob", pk_bob, pk_bob, "code") + await roster.set_member(GROUP, "bob", ROLE_MEMBER, "active", "code") - session._do_gek_rotate({}) + session._do_member_revoke({"user_id": "bob"}) challenge = _last(session) + assert challenge["type"] == "admin_challenge", challenge - # Signed over member_unpin, presented against the pending gek_rotate. + # Signed over chat_epoch, presented against the pending member_revoke. wrong = admin_transcript( - op=OP_MEMBER_UNPIN, node_pk_b64=session._node_pk_b64(), group_id=GROUP, - subject=GROUP, nonce=base64.b64decode(challenge["nonce"]), + op=OP_CHAT_EPOCH, node_pk_b64=session._node_pk_b64(), group_id=GROUP, + subject="bob", nonce=base64.b64decode(challenge["nonce"]), ts=challenge["ts"]) session._do_admin_response({ "op_id": challenge["op_id"], @@ -208,19 +153,18 @@ async def test_a_signature_over_another_operation_does_not_count(tmp_path, roste await _drain(session) assert _last(session)["type"] == "error" - assert session.state["groups_ctx"][GROUP]["gek"] == before + assert (await roster.get_member(GROUP, "bob"))["status"] == "active" -async def test_the_operator_rotates_and_the_node_makes_the_key(tmp_path, roster): +# ── Rotating the group key ─────────────────────────────────────────────────── + +async def test_rotating_makes_a_new_key_on_the_node(tmp_path, roster): session = await _session(tmp_path, roster, operator=True) - before = session._ctx["groups"][GROUP]["gek"] + before = session.state["groups_ctx"][GROUP]["gek"] - session._do_gek_rotate({}) - await _sign_and_exec(session, OP_GEK_ROTATE, GROUP, session.sk_op, - session._admin_exec_gek_rotate) + result = await ops.set_gek(session.state, GROUP, rotate=True) - ack = _last(session) - assert ack["type"] == MNP.GEK_ROTATE_ACK, ack + assert result["rotated"] is True after = session.state["groups_ctx"][GROUP]["gek"] assert after != before, "the key did not change" assert len(after) == 32 @@ -234,54 +178,21 @@ async def test_rotation_reaches_the_index(tmp_path, roster): serve members a listing they cannot open.""" session = await _session(tmp_path, roster, operator=True) - session._do_gek_rotate({}) - await _sign_and_exec(session, OP_GEK_ROTATE, GROUP, session.sk_op, - session._admin_exec_gek_rotate) + await ops.set_gek(session.state, GROUP, rotate=True) assert session.state["indexes"][GROUP].gek == \ session.state["groups_ctx"][GROUP]["gek"] -# ── member_unpin ───────────────────────────────────────────────────────────── - -async def test_unpinning_needs_an_operator(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False) - session._do_member_unpin({"user_id": "bob"}) - assert _last(session)["type"] == "error" - - -async def test_unpinning_yourself_is_refused(tmp_path, roster): - """It would end the authority of the connection performing the operation, - halfway through it.""" - session = await _session(tmp_path, roster, operator=True) - session._do_member_unpin({"user_id": "grenet"}) - assert _last(session)["detail"] == "Cannot unpin yourself" - - -async def test_a_members_signature_does_not_unpin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True) - sk_bob, pk_bob = _keypair() - await roster.pin_identity("bob", "bob", pk_bob, pk_bob, "code") - - session._do_member_unpin({"user_id": "bob"}) - await _sign_and_exec(session, OP_MEMBER_UNPIN, "bob", sk_bob, - session._admin_exec_member_unpin) - - assert _last(session)["type"] == "error" - assert await roster.get_identity("bob") is not None, ( - "a member removed their own pin — only the operator may") - +# ── Forgetting a pinned identity ───────────────────────────────────────────── -async def test_the_operator_unpins(tmp_path, roster): +async def test_unpinning_forgets_the_identity_and_its_bundle(tmp_path, roster): session = await _session(tmp_path, roster, operator=True) _, pk_bob = _keypair() await roster.pin_identity("bob", "bob", pk_bob, pk_bob, "code") - session._do_member_unpin({"user_id": "bob"}) - await _sign_and_exec(session, OP_MEMBER_UNPIN, "bob", session.sk_op, - session._admin_exec_member_unpin) + await ops.unpin_member(session.state, "bob") - assert _last(session)["type"] == MNP.MEMBER_UNPIN_ACK assert await roster.get_identity("bob") is None # The stored keypair bundle goes too — left behind it blocks the re-join # the unpin exists to enable. @@ -290,8 +201,21 @@ async def test_the_operator_unpins(tmp_path, roster): async def test_unpinning_someone_unknown_says_so(tmp_path, roster): session = await _session(tmp_path, roster, operator=True) - session._do_member_unpin({"user_id": "nobody"}) - await _sign_and_exec(session, OP_MEMBER_UNPIN, "nobody", session.sk_op, - session._admin_exec_member_unpin) - assert _last(session)["type"] == "error" - assert "No such pinned identity" in _last(session)["detail"] + with pytest.raises(ops.OpError, match="No such pinned identity"): + await ops.unpin_member(session.state, "nobody") + + +# ── What MNP no longer carries ─────────────────────────────────────────────── + +@pytest.mark.parametrize("op", ["gek_rotate", "member_unpin", "transfer_limits", + "group_detach"]) +def test_operations_no_client_sent_are_not_signed_ops_any_more(op): + """Gone with MNP 6.0: a door nobody uses is an untested way in. The Node + page and the CLI do the same work over loopback.""" + from meshbay_common import adminop + from meshbay_node.transport.webrtc.admin import _ADMIN_EXECUTORS + from meshbay_node.transport.webrtc.dispatch import _HANDLERS + + assert op not in _HANDLERS + assert op not in _ADMIN_EXECUTORS + assert op not in vars(adminop).values() diff --git a/packages/meshbay-node/tests/test_node_status.py b/packages/meshbay-node/tests/test_node_status.py index 91c07b6..45a9710 100644 --- a/packages/meshbay-node/tests/test_node_status.py +++ b/packages/meshbay-node/tests/test_node_status.py @@ -1,24 +1,19 @@ """ -node_status, root_add, root_remove over MNP. +The node management panel's server-side behaviour. -These test the D5 node management panel's server-side behaviour: the admin -identity check on node_status, the list_groups operation, and the root -add/remove flows through the MNP handlers. +The `_is_node_admin` hint, root removal over MNP, and the operations the Node +page and the CLI reach over loopback: listing groups, adding and updating +roots, the roster, the denylist and unpinning. Their own MNP messages +(`node_status`, `root_add`, `roster_read`, …) are gone since MNP 6.0. """ -import base64 from dataclasses import asdict from pathlib import Path import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from meshbay_common.adminop import ( - OP_MEMBER_UNPIN, - admin_transcript, -) from meshbay_common.crypto import pk_to_b64 from meshbay_common.join import ROLE_OPERATOR -from meshbay_common.protocol import MNP from meshbay_node import ops from meshbay_node.indexer.group_index import GroupIndex from meshbay_node.roots import RootSet @@ -133,19 +128,6 @@ async def _session( return session -async def _sign_and_exec(session, op: str, subject: str, sk, exec_fn, - group_id: str = GROUP): - challenge = _last(session) - assert challenge["type"] == "admin_challenge", challenge - transcript = admin_transcript( - op=op, node_pk_b64=session._node_pk_b64(), group_id=group_id, - subject=subject, nonce=base64.b64decode(challenge["nonce"]), - ts=challenge["ts"]) - pending = session._admin_ops.get(challenge["op_id"]) or { - "op": op, "subject": subject} - await exec_fn(pending, transcript, sk.sign(transcript)) - - # ── _is_node_admin ────────────────────────────────────────────────────────── async def test_is_node_admin_matches_user_id(tmp_path, roster): @@ -167,62 +149,6 @@ async def test_is_node_admin_rejects_missing_node_user_id(tmp_path, roster): assert not session._is_node_admin() -# ── node_status ───────────────────────────────────────────────────────────── - -async def test_node_status_returns_groups_for_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._spawn(session._do_node_status({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == MNP.NODE_STATUS_ACK - assert len(msg["groups"]) == 1 - assert msg["groups"][0]["id"] == GROUP - - -async def test_node_status_refused_for_non_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False, - node_user_id="grenet") - session._spawn(session._do_node_status({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" - assert "operator" in msg["detail"].lower() - - -async def test_node_status_refused_when_user_is_operator_but_ids_mismatch( - tmp_path, roster, -): - """A paired operator who is not the node owner cannot see node_status.""" - session = await _session(tmp_path, roster, operator=True, - node_user_id="someone-else") - session._spawn(session._do_node_status({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" - - -async def test_node_status_catches_send_failure(tmp_path, roster): - """If _send itself throws (e.g. msgpack encoding fails), the error must - not silently vanish — it used to, because _send was outside the try block.""" - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - sent = [] - call_count = [0] - - def _exploding_send(msg): - call_count[0] += 1 - if msg.get("type") == "node_status_ack": - raise TypeError("msgpack cannot encode this") - sent.append(msg) - - session._send = _exploding_send - session._spawn(session._do_node_status({})) - await _drain(session) - # The try/except around _send should catch the error and send an error reply - assert any(m.get("type") == "error" for m in sent) - - # ── ops.list_groups ───────────────────────────────────────────────────────── async def test_list_groups_returns_group_metadata(tmp_path): @@ -479,32 +405,6 @@ async def test_remove_root_succeeds_with_two_roots(tmp_path): assert cfg.roots[0].name == "dir1" -# ── root_add MNP handler ─────────────────────────────────────────────────── - -async def test_root_add_issues_challenge(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._do_root_add({"group_id": GROUP, "path": "/tmp/test"}) - msg = _last(session) - assert msg["type"] == "admin_challenge" - - -async def test_root_add_refuses_without_authority(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False) - session._do_root_add({"group_id": GROUP, "path": "/tmp/test"}) - msg = _last(session) - assert msg["type"] == "error" - assert "authorized" in msg["detail"].lower() - - -async def test_root_add_refuses_missing_fields(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._do_root_add({"group_id": GROUP}) - assert _last(session)["type"] == "error" - assert "Missing" in _last(session)["detail"] - - # ── root_remove MNP handler ──────────────────────────────────────────────── async def test_root_remove_issues_challenge(tmp_path, roster): @@ -530,52 +430,32 @@ async def test_root_remove_refuses_missing_fields(tmp_path, roster): assert "Missing" in _last(session)["detail"] -# ── roster_read MNP handler ────────────────────────────────────────────── +# ── The roster, the denylist and unpinning (loopback, CLI) ────────────────── +# +# Their MNP messages are gone (MNP 6.0, no client sent them); the operations are +# what the Node page and the CLI call. -async def test_roster_read_returns_members_for_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._spawn(session._do_roster_read({"group_id": GROUP})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "roster_read_ack" - assert "members" in msg - assert "identities" in msg - - -async def test_roster_read_refused_for_non_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False, - node_user_id="grenet") - session._spawn(session._do_roster_read({"group_id": GROUP})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" - - -async def test_roster_read_filters_ghost_members(tmp_path, roster): +async def test_read_roster_filters_ghost_members(tmp_path, roster): """Members whose identity was deleted (revoked then unpinned) are filtered out by read_roster — the LEFT JOIN returns them with pk_ed25519 = NULL but they should never reach the UI.""" session = await _session(tmp_path, roster, operator=True, node_user_id="grenet") - sk2, pk2 = _keypair() + _, pk2 = _keypair() await roster.pin_identity("ghost", "ghost", pk2, pk2, "code") await roster.set_member(GROUP, "ghost", "member", "revoked", "local-cli") await roster._db.execute("DELETE FROM identities WHERE user_id = 'ghost'") await roster._db.commit() # Also add a real member so the roster isn't empty - sk3, pk3 = _keypair() + _, pk3 = _keypair() await roster.pin_identity("real", "real", pk3, pk3, "code") await roster.set_member(GROUP, "real", "member", "active", "local-cli") - session._spawn(session._do_roster_read({"group_id": GROUP})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "roster_read_ack" - ghost = [m for m in msg["members"] if m["user_id"] == "ghost"] - assert len(ghost) == 0, "ghost members must be filtered out" - real = [m for m in msg["members"] if m["user_id"] == "real"] - assert len(real) == 1 + result = await ops.read_roster(session.state, GROUP) + assert "identities" in result + assert not [m for m in result["members"] if m["user_id"] == "ghost"], ( + "ghost members must be filtered out") + assert len([m for m in result["members"] if m["user_id"] == "real"]) == 1 async def test_unpin_fails_for_ghost_member(tmp_path, roster): @@ -583,145 +463,35 @@ async def test_unpin_fails_for_ghost_member(tmp_path, roster): session = await _session(tmp_path, roster, operator=True, node_user_id="grenet") await roster.set_member(GROUP, "ghost", "member", "revoked", "local-cli") - # ghost has no identity row - session._do_member_unpin({"user_id": "ghost"}) - challenge = _last(session) - assert challenge["type"] == "admin_challenge" + with pytest.raises(ops.OpError, match="No such pinned identity"): + await ops.unpin_member(session.state, "ghost") - await _sign_and_exec(session, OP_MEMBER_UNPIN, "ghost", - session.sk_op, session._admin_exec_member_unpin) - msg = _last(session) - assert msg["type"] == "error" - assert "No such pinned identity" in msg["detail"] - - -async def test_unpin_succeeds_for_real_identity(tmp_path, roster): - """Full unpin flow: challenge → sign → exec → identity deleted.""" - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - sk2, pk2 = _keypair() - await roster.pin_identity("target", "target", pk2, pk2, "code") - await roster.set_member(GROUP, "target", "member", "active", "local-cli") - - session._do_member_unpin({"user_id": "target"}) - challenge = _last(session) - assert challenge["type"] == "admin_challenge" - - await _sign_and_exec(session, OP_MEMBER_UNPIN, "target", - session.sk_op, session._admin_exec_member_unpin) - msg = _last(session) - assert msg["type"] == "member_unpin_ack" - assert msg["user_id"] == "target" - - idents = await roster.list_identities() - assert not any(i["user_id"] == "target" for i in idents) - - -# ── denylist_read MNP handler ──────────────────────────────────────────── -async def test_denylist_read_returns_entries_for_admin(tmp_path, roster): +async def test_read_denylist_lists_entries(tmp_path, roster): session = await _session(tmp_path, roster, operator=True, node_user_id="grenet") session.denylist.deny_user("bad-user") - session._spawn(session._do_denylist_read({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "denylist_read_ack" - assert msg["count"] == 1 - assert "bad-user" in msg["users"] + result = await ops.read_denylist(session.state) + assert result["count"] == 1 + assert "bad-user" in result["users"] -async def test_denylist_read_refused_for_non_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False, - node_user_id="grenet") - session._spawn(session._do_denylist_read({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" - - -# ── denylist_clear MNP handler ─────────────────────────────────────────── - -async def test_denylist_clear_removes_entry_for_admin(tmp_path, roster): +async def test_clear_denylist_removes_one_entry(tmp_path, roster): session = await _session(tmp_path, roster, operator=True, node_user_id="grenet") session.denylist.deny_user("bad-user") session.denylist.deny_user("other-user") - session._spawn(session._do_denylist_clear({"subject": "bad-user"})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "denylist_clear_ack" - assert msg["removed"] == 1 + result = await ops.clear_denylist(session.state, subject="bad-user") + assert result["removed"] == 1 assert "bad-user" not in session.denylist.entries()["users"] assert "other-user" in session.denylist.entries()["users"] -async def test_denylist_clear_all_for_admin(tmp_path, roster): +async def test_clear_denylist_all(tmp_path, roster): session = await _session(tmp_path, roster, operator=True, node_user_id="grenet") session.denylist.deny_user("a") session.denylist.deny_user("b") session.denylist.deny_jti("j") - session._spawn(session._do_denylist_clear({"subject": ""})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "denylist_clear_ack" - assert msg["removed"] == 3 - - -async def test_denylist_clear_refused_for_non_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False, - node_user_id="grenet") - session._spawn(session._do_denylist_clear({"subject": "bad-user"})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" - - -# ── group_attach MNP handler ──────────────────────────────────────────── - -async def test_group_attach_issues_challenge(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._do_group_attach({"name": "test-group", "shared_dir": "/tmp/share"}) - msg = _last(session) - assert msg["type"] == "admin_challenge" - - -async def test_group_attach_refused_without_authority(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False) - session._do_group_attach({"name": "test-group", "shared_dir": "/tmp/share"}) - msg = _last(session) - assert msg["type"] == "error" - assert "authorized" in msg["detail"].lower() - - -async def test_group_attach_refuses_missing_fields(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._do_group_attach({"name": "test-group"}) - msg = _last(session) - assert msg["type"] == "error" - assert "Missing" in msg["detail"] - - -# ── node_reload MNP handler ───────────────────────────────────────────── - -async def test_node_reload_runs_for_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=True, - node_user_id="grenet") - session._spawn(session._do_node_reload({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "node_reload_ack" - assert msg["status"] == "reloaded" - assert len(session.reload_called) == 1 - - -async def test_node_reload_refused_for_non_admin(tmp_path, roster): - session = await _session(tmp_path, roster, operator=False, - node_user_id="grenet") - session._spawn(session._do_node_reload({})) - await _drain(session) - msg = _last(session) - assert msg["type"] == "error" + result = await ops.clear_denylist(session.state, subject="") + assert result["removed"] == 3 diff --git a/packages/meshbay-node/tests/test_root_writable_policy.py b/packages/meshbay-node/tests/test_root_writable_policy.py index 0cd9af8..0dc5d6d 100644 --- a/packages/meshbay-node/tests/test_root_writable_policy.py +++ b/packages/meshbay-node/tests/test_root_writable_policy.py @@ -12,8 +12,9 @@ The properties this holds: A member with an old tab open, or one speaking MNP directly, gets the same answer. That half is pinned in `test_security_regressions.py`, next to the overwrite properties it belongs with; -* the setting is changed by a **signed** operator instruction, or it is a - suggestion any member can undo; +* the setting is changed **on the node's own machine** — the desktop + application over loopback, or the CLI — and never over MNP, where a signature + proves only that the operator's key signed (`test_sharing_is_local_only.py`); * it is stored on the **node**, never the hub. A hub that could decide who writes to the operator's disk would have authority over the node. @@ -26,7 +27,7 @@ from pathlib import Path import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from meshbay_common.adminop import OP_ROOT_EJECT, OP_ROOT_PLUG, OP_ROOT_UPDATE +from meshbay_common.adminop import OP_ROOT_EJECT, OP_ROOT_PLUG from meshbay_common.crypto import generate_gek from meshbay_common.protocol import MNP from meshbay_node.indexer.group_index import GroupIndex @@ -163,34 +164,6 @@ def _capture_challenges(session) -> list[tuple[str, str]]: return issued -async def test_changing_a_roots_flags_needs_a_signature(tmp_path): - """The flags are not applied by the request — only by the signed response.""" - session = _session(tmp_path, "the-operator", operator="the-operator") - issued = _capture_challenges(session) - - session._do_root_update({"group_id": "g" * 32, "root_name": "shared", - "writable": False}) - - assert [op for op, _ in issued] == [OP_ROOT_UPDATE] - assert session._ctx["roots"].roots[0].writable is True, ( - "applied before it was signed") - - -async def test_the_subject_names_the_outcome_not_the_operation(tmp_path): - """ - The operator is shown the subject before signing, so it has to say what will - be true afterwards. "shared" alone would have them authorize a change they - cannot see the direction of. - """ - session = _session(tmp_path, "op", operator="op") - issued = _capture_challenges(session) - - session._do_root_update({"group_id": "g" * 32, "root_name": "shared", - "writable": True, "removable": True}) - - assert issued == [(OP_ROOT_UPDATE, "shared:rw=on,rem=on")] - - async def test_eject_and_plug_are_signed_too(tmp_path): """ Hiding a group's whole library from every member is not a lesser act than @@ -214,8 +187,7 @@ async def test_a_request_with_nobody_to_authorize_it_is_refused(tmp_path): issued = _capture_challenges(session) session._has_admin_authority = lambda: False - session._do_root_update({"group_id": "g" * 32, "root_name": "shared", - "writable": True}) + session._do_root_eject({"group_id": "g" * 32, "root_name": "shared"}) assert issued == [] assert [m for m in session.sent if m.get("type") == "error"] diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py index 43e88c2..26a3b1d 100644 --- a/packages/meshbay-node/tests/test_security_regressions.py +++ b/packages/meshbay-node/tests/test_security_regressions.py @@ -842,14 +842,11 @@ def test_node_control_api_serves_no_html(): # ── NS4 / M3: the node's own controls take no authority from the hub ───────── -async def _owner_session(tmp_path, roster, *, device: str = "", - confirmed: bool = False): +async def _owner_session(tmp_path, roster): """A session whose token says it is the account this node belongs to. Which is all a hub can decide: `_user_id` is the `sub` of a JWT it issued, - so this is what an active hub forging a token arrives holding. Whether the - *device* on the connection is one the node pinned as an operator is the - other half, and no token can assert it. + so this is what an active hub forging a token arrives holding. """ from meshbay_node.indexer.group_index import GroupIndex @@ -865,9 +862,6 @@ async def _owner_session(tmp_path, roster, *, device: str = "", } session._group_id = "g" * 32 session._user_id = "the-owner" - session._username = "the-owner" - session._pinned_pk = device - session._device_confirmed = confirmed session._pk_user = "" session.sent = [] session._send = session.sent.append @@ -876,18 +870,18 @@ async def _owner_session(tmp_path, roster, *, device: str = "", @pytest.mark.asyncio -async def test_a_token_naming_the_owner_is_not_node_authority(tmp_path): +@pytest.mark.parametrize("mtype", ["node_status", "node_settings_set", "roster_read", + "denylist_read", "denylist_clear", "node_reload"]) +async def test_a_token_naming_the_owner_reaches_no_node_control(tmp_path, mtype): """ - The hub holds no user keys, so it cannot countersign a device — but it does - choose what a token says. Six node-wide controls used to be gated on the - account id alone, which is the hub's to decide: `node_status` (every group - on the machine, with the operator's absolute paths), `node_settings_set`, - `roster_read`, `denylist_read`, `denylist_clear` (the persisted revocation - H4 exists to keep) and `node_reload`. - - An active hub reaches a completed handshake wherever it can also obtain the - group key, which §3.5 concedes it can in an open-join group. From there, - "the hub says you are the owner" was the whole of the check. + The hub holds no user keys, but it does choose what a token says. Six + node-wide controls were once gated on the account id alone: `node_status` + (every group on the machine, with the operator's absolute paths), + `node_settings_set`, `roster_read`, `denylist_read`, `denylist_clear` (the + persisted revocation H4 exists to keep) and `node_reload`. A device proof + closed that; MNP 6.0 removed the messages, since no client sent them. They + are the loopback API's and the CLI's — on the operator's own machine — and a + peer naming one over MNP is answered by nobody. """ from meshbay_node.roster import Roster @@ -895,67 +889,8 @@ async def test_a_token_naming_the_owner_is_not_node_authority(tmp_path): await roster.open() try: forged = await _owner_session(tmp_path, roster) - await forged._do_node_status({}) - assert forged.sent[-1]["type"] == "error" - assert forged.sent[-1]["code"] == "not_operator" - - forged.sent.clear() - await forged._do_denylist_read({}) - assert forged.sent[-1]["type"] == "error" - - forged.sent.clear() - await forged._do_denylist_clear({"subject": ""}) - assert forged.sent[-1]["type"] == "error", ( - "clearing the denylist undoes a revocation every node enforces") - finally: - await roster.close() - - -@pytest.mark.asyncio -async def test_an_identified_device_that_is_not_an_operator_is_refused(tmp_path): - """Being a pinned member of the group is not being the node's operator. - - The device proof is real here; what it proves is an ordinary member's key, - and `operator_pks()` is rebuilt from the roster on every call so a revoked - one stops working at once. - """ - from meshbay_common.crypto import pk_to_b64 - from meshbay_node.roster import Roster - - roster = Roster(db_path=tmp_path / "roster.db") - await roster.open() - try: - sk = Ed25519PrivateKey.generate() - member_pk = pk_to_b64(sk.public_key()) - await roster.pin_identity("the-owner", "the-owner", member_pk, - member_pk, "code") - session = await _owner_session(tmp_path, roster, device=member_pk, - confirmed=True) - await session._do_node_status({}) - assert session.sent[-1]["type"] == "error" - finally: - await roster.close() - - -@pytest.mark.asyncio -async def test_a_paired_operator_device_is_what_opens_it(tmp_path): - """The positive case, so the test above is about authority and not about - everything being refused.""" - from meshbay_common.crypto import pk_to_b64 - from meshbay_common.join import ROLE_OPERATOR - from meshbay_node.roster import Roster - - roster = Roster(db_path=tmp_path / "roster.db") - await roster.open() - try: - sk = Ed25519PrivateKey.generate() - pk = pk_to_b64(sk.public_key()) - await roster.pin_identity("the-owner", "the-owner", pk, pk, "code") - await roster.set_member("", "the-owner", ROLE_OPERATOR, "active", - "local-cli") - session = await _owner_session(tmp_path, roster, device=pk, - confirmed=True) - await session._do_denylist_read({}) - assert session.sent[-1]["type"] != "error", session.sent[-1] + forged._dispatch_message({"type": mtype, "subject": "", "group_id": "g" * 32, + "settings": {"max_peers": 1}}) + assert forged.sent == [], f"{mtype} answered over MNP: {forged.sent}" finally: await roster.close() diff --git a/packages/meshbay-node/tests/test_sharing_is_local_only.py b/packages/meshbay-node/tests/test_sharing_is_local_only.py new file mode 100644 index 0000000..ac8bebb --- /dev/null +++ b/packages/meshbay-node/tests/test_sharing_is_local_only.py @@ -0,0 +1,109 @@ +""" +What of the operator's disk is shared, and who may write there, is decided on +the node's own machine — never over MNP (MNP 6.0). + +A signed operation proves that the operator's key signed, not that the operator +meant it: in a browser the key is driven by code the hub serves, and in the +desktop application by a renderer that parses content from nodes. `root_add`, +`root_update` and `group_attach` let either of them share any folder on the +machine, or open one to writes, from anywhere. They are gone; the loopback API +(the desktop application) and the CLI remain. + +And the consequence that has to hold for the operator's list to stay true: a +flag changed through the loopback API reaches every connected page, which the +MNP ack used to do. +""" + +from dataclasses import asdict +from pathlib import Path +from types import SimpleNamespace + +import pytest +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from meshbay_common import adminop +from meshbay_common.protocol import MNP +from meshbay_node import ops +from meshbay_node.config import GroupConfig, RootSpec +from meshbay_node.indexer.indexer import DirectoryIndexer +from meshbay_node.roots import RootSet +from meshbay_node.transport.webrtc.admin import _ADMIN_EXECUTORS +from meshbay_node.transport.webrtc.dispatch import _HANDLERS +from meshbay_node.transport.webrtc_server import WebRTCPeerSession +from test_admin_challenge_bounds import _PC, _Channel + +GROUP = "g" * 32 +GONE = ("root_add", "root_update", "group_attach") + + +@pytest.mark.parametrize("mtype", GONE) +def test_no_message_widens_what_the_node_shares(mtype): + assert mtype not in _HANDLERS, f"{mtype} is dispatched again" + assert mtype not in _ADMIN_EXECUTORS, f"{mtype} can be executed again" + assert mtype not in vars(MNP).values(), f"{mtype} is back in the protocol" + assert mtype not in vars(adminop).values(), f"{mtype} is a signed op again" + + +@pytest.mark.parametrize("mtype", GONE) +def test_an_older_client_asking_is_issued_nothing_to_sign(mtype): + """A 5.x client still sends these. Nothing it sends may come back as a + challenge — a challenge is what a compromised page needs signed.""" + ctx = {"sk_node": Ed25519PrivateKey.from_private_bytes(b"\x01" * 32), + "groups": {GROUP: {}}, "has_admin_authority": True} + s = WebRTCPeerSession(_PC(), ctx, peer_id="peer") + s._channel = _Channel() + s._audit = lambda *a, **k: None + s._user_id, s._group_id = "operator", GROUP + + s._dispatch_message({"type": mtype, "group_id": GROUP, "path": "/home/someone", + "shared_dir": "/home/someone", "name": "x", + "root_name": "x", "writable": True}) + + assert s._admin_ops == {} + assert not [m for m in s._channel.sent if m.get("type") == "admin_challenge"] + + +def _state(tmp_path: Path, indexer_roots: RootSet | None = None): + (tmp_path / "media").mkdir() + cfg = GroupConfig(id=GROUP, name="plop", + roots=[RootSpec(path=str(tmp_path / "media"), name="media")]) + conf = tmp_path / "node.toml" + conf.write_text( + f'[[groups]]\nid = "{GROUP}"\nname = "plop"\n\n' + f' [[groups.roots]]\n path = "{(tmp_path / "media").as_posix()}"\n' + f' name = "media"\n', encoding="utf-8") + live = RootSet.build([asdict(r) for r in cfg.roots]) + pushed: list[list[dict]] = [] + + async def on_change(indexer): + pushed.append(indexer.index.roots) + + indexer = DirectoryIndexer(indexer_roots or live, GROUP, + Ed25519PrivateKey.generate(), None, + on_change=on_change) + state = {"config": SimpleNamespace(groups=[cfg]), "config_path": str(conf), + "groups_ctx": {GROUP: {"roots": live}}, + "indexers": {GROUP: indexer}} + return state, pushed + + +async def test_a_flag_changed_on_the_node_reaches_every_open_page(tmp_path): + state, pushed = _state(tmp_path) + + await ops.update_root(state, GROUP, "media", writable=True) + + assert pushed, "nothing was pushed — open pages keep the old flag" + assert pushed[-1][0]["writable"] is True + + +async def test_the_pushed_table_is_right_when_the_indexer_holds_its_own_set(tmp_path): + """The indexer and the group context normally share one RootSet; when they + do not, the table peers receive is the indexer's, and must carry the flag.""" + (tmp_path / "media").mkdir() + own = RootSet.build([{"path": str(tmp_path / "media"), "name": "media"}]) + (tmp_path / "media").rmdir() + state, pushed = _state(tmp_path, indexer_roots=own) + + await ops.update_root(state, GROUP, "media", removable=True) + + assert pushed[-1][0]["removable"] is True + assert state["groups_ctx"][GROUP]["roots"].by_name("media").removable is True |