RigShare 1.3.2: access checks without a live room, refuse Windows alias paths, same-origin login redirect
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user