diff options
Diffstat (limited to 'packages/meshbay-node/src')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index 64df7ac..81db0e9 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -56,6 +56,7 @@ from meshbay_common.handshake import ( ) from meshbay_common.adminop import ( ADMIN_CHALLENGE_TTL, + OP_DIR_DELETE, OP_FILE_DELETE, OP_INVITE_CREATE, admin_transcript, @@ -357,6 +358,8 @@ class WebRTCPeerSession: self._do_file_upload(msg) elif mtype == MNP.DIR_CREATE: self._do_dir_create(msg) + elif mtype == MNP.DIR_DELETE: + self._do_dir_delete(msg) elif mtype == MNP.FILE_DELETE: self._do_file_delete(msg) elif mtype == MNP.ADMIN_RESPONSE: @@ -932,6 +935,70 @@ class WebRTCPeerSession: "dir": str(target.relative_to(shared_root)), }) + def _do_dir_delete(self, msg: dict) -> None: + """ + Remove an empty directory, for the node operator. + + Creating one is not privileged — a member who can add a file may organise + where it goes — but removing one is: it acts on a name other members are + using, and on the operator's disk. Empty is the whole safety property + here. Nothing recursive: refusing a directory with anything in it means + this can never destroy content, whatever the caller intended, so the + operator deletes the files first and sees what they are losing. + """ + ctx = self._group_ctx() + shared_root = ctx.get("shared_root") + if not shared_root: + self._send({"type": "error", "detail": "No shared directory"}) + return + + target = safe_subdir(shared_root, msg.get("dir") or "") + if target is None or target == shared_root: + self._send({"type": "error", "detail": "Invalid directory"}) + return + if not target.is_dir(): + self._send({"type": "error", "detail": "Not a directory"}) + return + if any(target.iterdir()): + self._send({"type": "error", "detail": "Directory is not empty"}) + return + if not self._has_admin_authority(): + self._send({"type": "error", "detail": "No authorized key for deletion"}) + return + + self._issue_admin_challenge( + OP_DIR_DELETE, str(target.relative_to(shared_root))) + + async def _admin_exec_dir_delete( + self, pending: dict, transcript: bytes, sig: bytes, + ) -> None: + rel = pending["subject"] + ctx = self._group_ctx() + shared_root = ctx.get("shared_root") + target = safe_subdir(shared_root, rel) if shared_root else None + if target is None or target == shared_root or not target.is_dir(): + self._send({"type": "error", "detail": "Not a directory"}) + return + + # Operator only. A file has an uploader who may remove their own; a + # directory has none, so there is no second key to accept here. + if not await self._verify_admin_sig(transcript, sig): + self._send({"type": "error", "detail": "Signature verification failed"}) + self._audit("admin_auth_failed", f"dir_delete:{rel}") + return + + # Checked again after the signature: the emptiness test that let this + # through happened before a round trip to the operator's browser, and a + # file could have landed in the meantime. + if any(target.iterdir()): + self._send({"type": "error", "detail": "Directory is not empty"}) + return + + target.rmdir() + log.info("Directory removed by %s: %s", self._user_id[:8], rel) + self._audit("dir_delete", rel) + self._send({"type": MNP.DIR_DELETE_ACK, "v": MNP_VERSION, "dir": rel}) + async def _do_keypair_bundle_delete(self) -> None: """ Withdraw our own key backup from this node. @@ -1472,6 +1539,9 @@ class WebRTCPeerSession: if pending["op"] == OP_FILE_DELETE: asyncio.ensure_future( self._admin_exec_file_delete(pending, transcript, sig_bytes)) + elif pending["op"] == OP_DIR_DELETE: + asyncio.ensure_future( + self._admin_exec_dir_delete(pending, transcript, sig_bytes)) elif pending["op"] == OP_INVITE_CREATE: asyncio.ensure_future( self._admin_exec_invite_create(pending, transcript, sig_bytes)) |