RigShare 1.3.2: access checks without a live room, refuse Windows alias paths, same-origin login redirect

This commit is contained in:
Edouard Durand
2026-10-08 10:57:57 +00:00
parent fe45031dcb
commit 3c5e8bfa3a
8 changed files with 119 additions and 30 deletions
+39
View File
@@ -666,3 +666,42 @@ def test_folder_routes_refuse_paths_outside_the_workspace(run, tmp_path):
assert r.status in (400, 403, 404)
assert (outside / "keep.bin").exists()
run(scenario())
def test_access_is_checked_without_a_live_room(run):
async def scenario():
async with rig_server() as rig:
await rig.setup_admin()
alice = await rig.add_user("alice", edit=True)
bob = await rig.add_user("bob", edit=True)
await rig.add_user("viewer")
h = rig.http
r = await h.post(rig.url("/rigshare/api/workspace/mkdir"), json={"parent": "@shared", "name": "Team"}, headers=alice)
assert r.status == 200
rig.hub.workspace.set_acl("shared/Team", {"members": {"alice": "edit"}}, "alice")
path = "workflows/shared/Team/plan.json"
doc = json.dumps({"nodes": [{"id": 1}], "links": []}).encode()
assert (await h.post(rig.url(f"/api/userdata/{enc(path)}"), data=doc, headers=alice)).status == 200
key = "file:" + path
assert key not in rig.hub.rooms, "nobody has it open"
snap_id = rig.store.list_snapshots(key)[0]["id"]
for who_, status in ((alice, 200), (bob, 404)):
r = await h.get(rig.url(f"/rigshare/api/snapshots?room={enc(key)}"), headers=who_)
assert r.status == status, who_
r = await h.get(rig.url(f"/rigshare/api/snapshots/{snap_id}"), headers=who_)
assert r.status == status, who_
# Joining a workflow you cannot open must not create (or seed) a room.
sock = await rig.ws("bob")
await sock.send({"type": "join", "room": key, "name": "plan", "doc": BASE_DOC})
await sock.until("room_denied")
assert key not in rig.hub.rooms
# A viewer cannot be the one who opens a room, so cannot seed it with their own document.
open_key = "file:workflows/common.json"
viewer_sock = await rig.ws("viewer")
await viewer_sock.send({"type": "join", "room": open_key, "name": "common", "doc": BASE_DOC})
assert "editing" in (await viewer_sock.until("room_denied"))["reason"]
assert open_key not in rig.hub.rooms
run(scenario())
+13 -7
View File
@@ -133,9 +133,15 @@ def test_rename_user_moves_private_folder(ws):
assert ws.folders["shared/Team"]["owner"] == "alicia"
@pytest.mark.parametrize("raw", ["C:/models", "C:", "a/D:x/b", r"..\..\x", "c%3A%5Cmodels"])
def test_norm_drops_drive_segments(raw):
assert ":" not in norm(raw) and ".." not in norm(raw).split("/")
@pytest.mark.parametrize("raw", ["C:/models", "C:", "a/D:x/b", "c%3A%5Cmodels"])
def test_norm_refuses_drive_segments(raw):
with pytest.raises(PermissionError):
norm(raw)
def test_norm_still_resolves_dotdot_inside_the_tree():
assert norm(r"..\..\x") == "x"
assert norm("a/../b") == "b"
@pytest.mark.parametrize("bad", ["C:/models", "C:", "..", "a/../..", "x:y"])
@@ -164,11 +170,11 @@ def test_folder_ops_cannot_leave_the_workspace(ws, tmp_path):
assert (outside / "keep.txt").exists()
@pytest.mark.skipif(os.name != "nt", reason="trailing dots, spaces and short names only alias on Windows")
@pytest.mark.parametrize("raw", ["users./bob/a.json", "users /bob", "USERS~1/bob", "users/bob./a.json"])
def test_norm_drops_windows_aliases(raw):
if os.name != "nt" and ("." in raw.split("/")[0] or " " in raw.split("/")[0]):
pytest.skip("trailing dots and spaces only alias on Windows")
assert "~1" not in norm(raw)
def test_norm_refuses_windows_aliases(raw):
with pytest.raises(PermissionError):
norm(raw)
@pytest.mark.skipif(os.name != "nt", reason="case-insensitive filesystem")