""" The subjects of multi-value admin operations are byte-identical in the browser and in Python. The subject is what the operator's signature covers of a request, and each side builds it on its own — the node from the request it stored, the client from what the person asked for. A one-byte disagreement does not weaken anything (the client refuses to sign), but it makes the operation impossible from a browser, and nothing else in the suite crosses this boundary. Skipped when node is unavailable; that is a coverage gap, not a pass. """ import json import shutil import subprocess from pathlib import Path import pytest from meshbay_common.adminop import ( invite_create_subject, secret_digest, structured_subject, tmdb_config_subject, ) CRYPTO_JS = (Path(__file__).resolve().parents[2] / "meshbay-hub" / "src" / "meshbay_hub" / "static" / "crypto.js") pytestmark = pytest.mark.skipif( shutil.which("node") is None or not CRYPTO_JS.exists(), reason="node or crypto.js unavailable — parity cannot be checked", ) # The canonical JSON itself, on the values that are hard to get identical: # separators, quotes, control characters, non-ASCII, and null/""/false. STRUCTURED = [ {"path": "/srv/Films", "name": "", "writable": False, "n": None}, {"path": "C:\\Users\\me\\Share", "name": "Partagé", "kind": "photo"}, {"path": '/srv/a "quoted", odd:name|x', "name": "名前", "removable": True}, {"path": "/srv/tab\there\nnewline\x01ctl", "name": "é", "z": "", "a": 0}, ] INVITE_CREATE = [ ("0f8fad5b-d9cb-469f-a165-70867728950e", ""), ("0f8fad5b-d9cb-469f-a165-70867728950e", "Élodie \"E\" 🙂"), ] TMDB_CONFIG = [ (None, None), ("", None), (None, ""), ("", ""), ("eyJhbGciOiJIUzI1NiJ9.token", "fr-FR"), ("abc", "keep"), ] _HARNESS = r""" const fs = require('fs'); globalThis.window = {}; const src = fs.readFileSync(process.argv[2], 'utf8'); const M = new Function(src + '\nreturn { adminSubject, ' + 'inviteCreateSubject, tmdbConfigSubject };')(); const v = JSON.parse(fs.readFileSync(process.argv[3], 'utf8')); (async () => { const out = { structured: v.structured.map((f) => M.adminSubject(f)), invite_create: v.invite_create.map((a) => M.inviteCreateSubject(...a)), tmdb_config: [], }; for (const a of v.tmdb_config) out.tmdb_config.push(await M.tmdbConfigSubject(...a)); process.stdout.write(JSON.stringify(out)); })(); """ @pytest.fixture(scope="module") def js(tmp_path_factory): d = tmp_path_factory.mktemp("subject-parity") (d / "harness.js").write_text(_HARNESS, encoding="utf-8") (d / "vectors.json").write_text(json.dumps({ "structured": STRUCTURED, "invite_create": INVITE_CREATE, "tmdb_config": TMDB_CONFIG, }), encoding="utf-8") proc = subprocess.run( ["node", str(d / "harness.js"), str(CRYPTO_JS), str(d / "vectors.json")], capture_output=True, text=True, encoding="utf-8", timeout=60) if proc.returncode != 0: pytest.fail(f"node harness failed:\n{proc.stderr}") return json.loads(proc.stdout) def _bytes(s: str) -> bytes: return s.encode("utf-8") @pytest.mark.parametrize("i,fields", list(enumerate(STRUCTURED))) def test_structured_subject_parity(i, fields, js): assert _bytes(js["structured"][i]) == _bytes(structured_subject(fields)) @pytest.mark.parametrize("i,args", list(enumerate(INVITE_CREATE))) def test_invite_create_subject_parity(i, args, js): assert _bytes(js["invite_create"][i]) == _bytes(invite_create_subject(*args)) @pytest.mark.parametrize("i,args", list(enumerate(TMDB_CONFIG))) def test_tmdb_config_subject_parity(i, args, js): assert _bytes(js["tmdb_config"][i]) == _bytes(tmdb_config_subject(*args)) def test_every_value_changes_the_subject(): base = ("0f8fad5b-d9cb-469f-a165-70867728950e", "Someone") variants = {invite_create_subject(*base)} for i, other in enumerate(("6a2f41a3-c54c-fce8-32d2-0324e1c32e22", "Somebody")): args = list(base) args[i] = other variants.add(invite_create_subject(*args)) assert len(variants) == 3 def test_unchanged_cleared_and_set_are_three_subjects(): assert len({tmdb_config_subject(None, None), tmdb_config_subject("", None), tmdb_config_subject("t", None)}) == 3 assert len({tmdb_config_subject(None, None), tmdb_config_subject(None, ""), tmdb_config_subject(None, "fr-FR")}) == 3 def test_the_token_is_never_written_into_the_subject(): token = "eyJhbGciOiJIUzI1NiJ9.a-real-looking-secret" assert token not in tmdb_config_subject(token, "fr-FR") assert secret_digest(token).startswith("sha256:") def test_a_crafted_field_cannot_impersonate_another(): # Under a naive "path|name" join these two would collide. a = structured_subject({"path": "/a|name=b", "name": ""}) b = structured_subject({"path": "/a", "name": "b"}) assert a != b