feat(drive): fix webdav back-compat
add env variable `OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX`
which is by default:
`OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX="@drive"`
so `/webdav/` -> points to user's personal drive (**backward compatibilit**y)
`/web/dav/@drive/{uuid|drive name}/` points to the respective drive
if admins want directly `/webdav/` pointing to list of drives they need to:
`OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX=""`
+ ensure lock is per user (RFC 4918 §9.11)
fix: #554
This commit is contained in:
@@ -185,6 +185,8 @@ hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test
|
||||
"$API_DIR/cross_drive_move.hurl" \
|
||||
"$API_DIR/cross_drive_copy.hurl" \
|
||||
"$API_DIR/webdav_dead_properties.hurl" \
|
||||
"$API_DIR/webdav_drive_root.hurl" \
|
||||
"$API_DIR/webdav_permissions.hurl" \
|
||||
"$API_DIR/webdav_nested_move_cascade.hurl" \
|
||||
"$API_DIR/wopi_authz.hurl"
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
# =============================================================
|
||||
# OxiCloud — WebDAV drive-root URL scheme
|
||||
# =============================================================
|
||||
# Exercises the native WebDAV URL scheme documented in
|
||||
# `src/interfaces/api/handlers/webdav_handler.rs::resolve_webdav_scope`:
|
||||
#
|
||||
# Default deployment (`OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX="@drive"`):
|
||||
# * `/webdav/` → default drive's contents
|
||||
# * `/webdav/@drive/` → drive listing (per-drive
|
||||
# virtual folders)
|
||||
# * `/webdav/@drive/<uuid>/…` → explicit drive by UUID
|
||||
# * `/webdav/@drive/<name>/…` → explicit drive by name
|
||||
#
|
||||
# Coverage:
|
||||
# 1. Login, capture JWT
|
||||
# 2. Resolve caller's default drive (id + display name)
|
||||
# 3. Create a magic folder under the home root via REST
|
||||
# 4. PROPFIND `/webdav/` — Depth: 1 lists the magic folder as
|
||||
# an immediate child of the default drive. This is the
|
||||
# user-visible bug fix: pre-refactor, `/webdav/` returned a
|
||||
# drive listing instead of the default drive's contents.
|
||||
# 5. PROPFIND `/webdav/@drive/` — Depth: 1 lists each drive as
|
||||
# a virtual child (at least the caller's default is present).
|
||||
# 6. PROPFIND `/webdav/@drive/<uuid>/` — descends into the
|
||||
# selected drive by UUID; magic folder appears here too.
|
||||
# 7. PROPFIND `/webdav/@drive/<name>/` — same via display name.
|
||||
# 8. Cleanup: DELETE the magic folder via REST.
|
||||
#
|
||||
# The magic folder name embeds a run-scoped marker so parallel
|
||||
# `hurl --jobs N` runs don't step on each other and repeat runs
|
||||
# against a shared DB don't collide.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 1 — Login, capture JWT
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
token: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 2 — Resolve caller's default drive (id + display name).
|
||||
# `GET /api/drives` returns rows in a stable order:
|
||||
# the caller's default personal drive first, then by
|
||||
# display name. See `DriveRepository::list_readable_by`.
|
||||
# `default_for_user` on the DTO is present-only for
|
||||
# default rows (`Option<Uuid>` with `skip_serializing_if`),
|
||||
# so `$[0]` — combined with the stable order — is the
|
||||
# default drive for a fresh admin account.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/drives
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
default_drive_id: jsonpath "$[0].id"
|
||||
default_drive_name: jsonpath "$[0].name"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 3 — Resolve the caller's home root folder id.
|
||||
# A default personal drive has exactly one root folder
|
||||
# (the drive-root itself). We need its id to create the
|
||||
# magic folder as its child.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/folders
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
home_folder_id: jsonpath "$[0].id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 4 — Create a magic folder under the home root via REST.
|
||||
# The name is deterministic-yet-unique so PROPFIND
|
||||
# assertions below can find it by exact string match,
|
||||
# and parallel test runs can't collide.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/folders
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"name": "hurl-drive-root-magic-marker",
|
||||
"parent_id": "{{home_folder_id}}"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
magic_folder_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 5 — PROPFIND on `/webdav/` (bare root). The default
|
||||
# deployment maps this to the caller's DEFAULT drive
|
||||
# contents, so Depth: 1 must include the magic folder.
|
||||
#
|
||||
# Pre-refactor this returned a drive listing instead —
|
||||
# the exact regression that broke back-compat with
|
||||
# pre-multi-drive WebDAV clients.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/
|
||||
Authorization: Bearer {{token}}
|
||||
Depth: 1
|
||||
|
||||
HTTP 207
|
||||
[Asserts]
|
||||
xpath "//*[local-name()='response']/*[local-name()='href' and contains(text(), 'hurl-drive-root-magic-marker')]" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 6 — PROPFIND on `/webdav/@drive/`. This is the explicit
|
||||
# drive picker — Depth: 1 returns one virtual child
|
||||
# per drive the caller has Read on. The default drive
|
||||
# must appear (by its display name).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/@drive/
|
||||
Authorization: Bearer {{token}}
|
||||
Depth: 1
|
||||
|
||||
HTTP 207
|
||||
[Asserts]
|
||||
xpath "//*[local-name()='response']/*[local-name()='href' and contains(text(), '{{default_drive_name}}')]" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 7 — PROPFIND on `/webdav/@drive/<uuid>/`. The explicit
|
||||
# by-UUID selector — descends INTO the chosen drive.
|
||||
# Depth: 1 lists that drive's top-level children —
|
||||
# the magic folder must be one of them.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/@drive/{{default_drive_id}}/
|
||||
Authorization: Bearer {{token}}
|
||||
Depth: 1
|
||||
|
||||
HTTP 207
|
||||
[Asserts]
|
||||
xpath "//*[local-name()='response']/*[local-name()='href' and contains(text(), 'hurl-drive-root-magic-marker')]" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 8 — PROPFIND on `/webdav/@drive/<name>/`. The explicit
|
||||
# by-name selector — same result as the UUID form.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/@drive/{{default_drive_name}}/
|
||||
Authorization: Bearer {{token}}
|
||||
Depth: 1
|
||||
|
||||
HTTP 207
|
||||
[Asserts]
|
||||
xpath "//*[local-name()='response']/*[local-name()='href' and contains(text(), 'hurl-drive-root-magic-marker')]" exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 9 — Reject MKCOL at `/webdav/@drive/` (bare pseudo-root).
|
||||
# The drive-listing target has no writable parent
|
||||
# folder — 405 Method Not Allowed. This guard prevents
|
||||
# a client from silently succeeding at "creating a
|
||||
# drive by MKCOL" (the drive-create surface is
|
||||
# `POST /api/drives`, not WebDAV).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MKCOL {{base_url}}/webdav/@drive/
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
HTTP 405
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 10 — Reject MKCOL at `/webdav/@drive/<not-a-drive>`.
|
||||
# `<not-a-drive>` gets interpreted as a drive selector;
|
||||
# no drive with that name/UUID exists → 404. Sits
|
||||
# adjacent to Step 9 so any future maintainer touching
|
||||
# the pseudo-root rejection sees BOTH shapes at once
|
||||
# (bare listing = 405, unknown selector = 404).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MKCOL {{base_url}}/webdav/@drive/hurl-not-a-real-drive
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 11 — Reject PUT at `/webdav/@drive/<not-a-drive>/x.txt`.
|
||||
# Same rejection shape as MKCOL — trying to write a
|
||||
# file into a non-existent drive.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/webdav/@drive/hurl-not-a-real-drive/probe.txt
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: text/plain
|
||||
```
|
||||
probe
|
||||
```
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 11b — Reject PUT at `/webdav/@drive/test.txt`. The URL
|
||||
# segment immediately after `@drive/` is ALWAYS a
|
||||
# drive selector — never a filename. A caller that
|
||||
# bookmarks a file URL under `@drive` with a name
|
||||
# that doesn't match any drive must get 404, not
|
||||
# silently create a file at the drive-listing level.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/webdav/@drive/test.txt
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: text/plain
|
||||
```
|
||||
probe
|
||||
```
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12 — Cleanup: DELETE the magic folder via REST so
|
||||
# subsequent test runs / other hurl files don't see
|
||||
# our marker.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/folders/{{magic_folder_id}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
HTTP 204
|
||||
@@ -0,0 +1,300 @@
|
||||
# =============================================================
|
||||
# OxiCloud — WebDAV per-role permissions + cross-drive MOVE policy
|
||||
# =============================================================
|
||||
# End-to-end coverage for the two WebDAV authz axes exposed by the
|
||||
# `@drive` URL scheme:
|
||||
#
|
||||
# 1. Per-role gates through the drive-scope resolver: a Viewer on a
|
||||
# shared drive can PROPFIND/GET but cannot MKCOL/PUT/MOVE. An
|
||||
# Editor can. AuthZ denials return `NotFound` (anti-enum), so
|
||||
# a probing caller can't tell a genuinely-missing folder from
|
||||
# one they simply lack Create on.
|
||||
#
|
||||
# 2. Drive policy `forbid_cross_drive_move` gates MOVE at the
|
||||
# SOURCE drive (see `DrivePolicies::refuse_cross_drive_move`
|
||||
# in `src/domain/entities/drive.rs`) — even a fully-authorised
|
||||
# Editor can't move content OUT of a drive whose owner has
|
||||
# forbidden cross-drive movement. Rejection is 405
|
||||
# (`ErrorKind::UnsupportedOperation` → `METHOD_NOT_ALLOWED`).
|
||||
#
|
||||
# Assumes the default `OXICLOUD_WEBDAV_DRIVE_LISTING_PREFIX="@drive"` config —
|
||||
# runs alongside the other tests in `tests/api/run.sh`. Uses the
|
||||
# `@drive/<uuid>` selector so the paths don't collide with any
|
||||
# drive-name-collision oddities.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 1 — Login as admin (bootstrapped by `setup.hurl`).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
admin_token: jsonpath "$.access_token"
|
||||
admin_user_id: jsonpath "$.user.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 2 — Create a fresh user "webdav_bob" via the admin
|
||||
# endpoint, log him in.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/admin/users
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"username": "webdav_bob",
|
||||
"password": "WebdavBobPassword1!",
|
||||
"email": "webdav_bob@example.com",
|
||||
"role": "user"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
bob_user_id: jsonpath "$.id"
|
||||
|
||||
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "webdav_bob", "password": "WebdavBobPassword1!" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
bob_token: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# Capture Bob's default personal drive id — used by the cross-drive
|
||||
# MOVE scenario. Bob is not a member of any shared drive yet, so his
|
||||
# `/api/drives` listing has exactly one entry (his own default).
|
||||
GET {{base_url}}/api/drives
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
bob_personal_drive_id: jsonpath "$[0].id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 3 — Admin creates a shared drive owned by admin.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/drives
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"kind": "shared",
|
||||
"name": "webdav-perm-shared",
|
||||
"owner": { "type": "user", "id": "{{admin_user_id}}" }
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
shared_drive_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 4 — Grant Bob VIEWER on the shared drive via /api/grants.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/grants
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"subject": { "type": "user", "id": "{{bob_user_id}}" },
|
||||
"resource": { "type": "drive", "id": "{{shared_drive_id}}" },
|
||||
"role": "viewer"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 5 — Bob (VIEWER) CAN PROPFIND the shared drive root.
|
||||
# Depth 0 to keep the assertion minimal; a 207 with the
|
||||
# drive's own href suffices as "Bob has Read".
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/@drive/{{shared_drive_id}}/
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Depth: 0
|
||||
|
||||
HTTP 207
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 6 — Bob (VIEWER) CANNOT MKCOL on the shared drive.
|
||||
# `authz.require(Create, Folder)` denial returns
|
||||
# `DomainError::not_found` (anti-enum), which maps to 404.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MKCOL {{base_url}}/webdav/@drive/{{shared_drive_id}}/viewer-blocked-folder
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 7 — Bob (VIEWER) CANNOT PUT a file.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/webdav/@drive/{{shared_drive_id}}/viewer-blocked-file.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Content-Type: text/plain
|
||||
```
|
||||
viewer should not upload
|
||||
```
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 8 — Admin creates a probe folder in the shared drive so
|
||||
# the Editor-can-rename step below has a real target.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MKCOL {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 9 — Bob (VIEWER) CANNOT MOVE (rename) the probe folder.
|
||||
# MOVE requires Update on the source, which Viewer
|
||||
# doesn't have. Same anti-enum 404 shape.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MOVE {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Destination: {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder-renamed
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 10 — Promote Bob from VIEWER to EDITOR.
|
||||
# `PATCH /api/drives/{id}/members/{subject-type}/{id}`
|
||||
# mutates the role in-place.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PATCH {{base_url}}/api/drives/{{shared_drive_id}}/members/user/{{bob_user_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "role": "editor" }
|
||||
|
||||
HTTP 200
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 11 — Bob (EDITOR) CAN MKCOL a new folder.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MKCOL {{base_url}}/webdav/@drive/{{shared_drive_id}}/editor-created-folder
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12 — Bob (EDITOR) CAN PUT a file.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/webdav/@drive/{{shared_drive_id}}/editor-created-folder/hello.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Content-Type: text/plain
|
||||
```
|
||||
editor uploaded content
|
||||
```
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 13 — Bob (EDITOR) CAN MOVE (rename) the probe folder.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MOVE {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Destination: {{base_url}}/webdav/@drive/{{shared_drive_id}}/probe-folder-renamed
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 14 — Bob puts a file in his OWN personal drive as the
|
||||
# source for the cross-drive MOVE test below.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/webdav/xdrive-probe.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Content-Type: text/plain
|
||||
```
|
||||
cross-drive probe payload
|
||||
```
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 15 — Admin flips `forbid_cross_drive_move` ON for Bob's
|
||||
# PERSONAL drive. The policy sits on the SOURCE drive
|
||||
# per `DrivePolicies::refuse_cross_drive_move`; only
|
||||
# OxiCloud-admin can PATCH policies.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PATCH {{base_url}}/api/drives/{{bob_personal_drive_id}}/policies
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "forbid_cross_drive_move": true }
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.forbid_cross_drive_move" == true
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 16 — Bob tries to MOVE `xdrive-probe.txt` from his
|
||||
# PERSONAL drive to the SHARED drive. Blocked at the
|
||||
# service layer by the policy — `OperationNotSupported`
|
||||
# maps to 405 Method Not Allowed.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MOVE {{base_url}}/webdav/xdrive-probe.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Destination: {{base_url}}/webdav/@drive/{{shared_drive_id}}/xdrive-probe.txt
|
||||
|
||||
HTTP 405
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 17 — Admin flips the policy OFF.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PATCH {{base_url}}/api/drives/{{bob_personal_drive_id}}/policies
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{ "forbid_cross_drive_move": false }
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.forbid_cross_drive_move" == false
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 18 — Bob retries the same MOVE. Now the policy is off,
|
||||
# Bob has Update on source (his own personal drive) +
|
||||
# Create on dest parent (Editor on shared drive), so
|
||||
# the move succeeds. 201 on rename/move to a new URL,
|
||||
# per `handle_move`'s existing convention.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
MOVE {{base_url}}/webdav/xdrive-probe.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Destination: {{base_url}}/webdav/@drive/{{shared_drive_id}}/xdrive-probe.txt
|
||||
|
||||
HTTP 201
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 19 — Verify the destination now exists and the source
|
||||
# is gone. Both PROPFINDs use Bob's token to also
|
||||
# re-confirm the AuthZ gates on the destination side.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PROPFIND {{base_url}}/webdav/@drive/{{shared_drive_id}}/xdrive-probe.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Depth: 0
|
||||
|
||||
HTTP 207
|
||||
|
||||
|
||||
PROPFIND {{base_url}}/webdav/xdrive-probe.txt
|
||||
Authorization: Bearer {{bob_token}}
|
||||
Depth: 0
|
||||
|
||||
HTTP 404
|
||||
Reference in New Issue
Block a user