fix(nc): login OIDC + drive picker
ensure OIDC is supported during nextcloud login
flow is:
1. nextcloud
2. oxicloud login ( direct pass or OIDC according config)
3. drive picker (if user has multiple drive)
4. success page + backchannel login to nextcloud
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# =============================================================
|
||||
# OxiCloud — NC Login Flow v2 — password path, multi-drive picker
|
||||
# =============================================================
|
||||
# Sibling to `nc_login_flow_v2.hurl` (protocol init + poll edge
|
||||
# cases). Where that file exercises the wire shape, THIS file
|
||||
# exercises the multi-drive fork — the branch in
|
||||
# `handle_login_submit` (login_v2_handler.rs) that renders the
|
||||
# drive picker template when `list_folders_with_perms` returns
|
||||
# ≥ 2 rows, then defers completion until the user picks.
|
||||
#
|
||||
# The OIDC equivalent lives at `tests/oidc/oidc.hurl` Step 12 and
|
||||
# regression-pins the customer-reported bug where OIDC callback
|
||||
# skipped the picker. This file pins the SAME multi-drive fork
|
||||
# for the classic password path so a refactor of the shared
|
||||
# `resolve_drive_or_complete` helper can't silently regress
|
||||
# either channel.
|
||||
#
|
||||
# Coverage (end-to-end simulation of the NC desktop client's
|
||||
# browser leg + backchannel):
|
||||
#
|
||||
# A. Admin password login → JWT for creating fixtures.
|
||||
# B. Create a shared drive owned by admin so admin has
|
||||
# exactly 2 drives (default personal + this shared).
|
||||
# C. NC LFv2 initiate → capture flow_token + poll_token.
|
||||
# D. Pre-completion poll → 404 baseline.
|
||||
# E. Submit login form POST /login/v2/flow/{token} with
|
||||
# user + password → picker HTML (200), NOT a redirect,
|
||||
# because the user has ≥ 2 drives.
|
||||
# F. Poll AGAIN → still 404. Proves the submit did NOT
|
||||
# complete the flow — regression against a future change
|
||||
# that accidentally shortcuts past the picker.
|
||||
# G. Submit picker → POST /login/v2/flow/{token}/drive.
|
||||
# H. Post-picker poll → 200 with composite `admin~<uuid>`
|
||||
# loginName. This is the load-bearing assertion: the
|
||||
# picker choice must round-trip into the app-password's
|
||||
# login name so NC uploads land on the chosen drive.
|
||||
# I. Poll again → 404 (single-use consumed).
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step A — Admin password login for fixture creation.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
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 B — Create a shared drive owned by admin. The admin's
|
||||
# default personal drive is already there; this second
|
||||
# drive triggers the multi-drive picker branch on the
|
||||
# next login (`list_folders_with_perms` returns 2 rows).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/drives
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"kind": "shared",
|
||||
"name": "admin-picker-fixture",
|
||||
"owner": { "type": "user", "id": "{{admin_user_id}}" }
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
fixture_drive_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step C — NC LFv2 initiate. Public endpoint at
|
||||
# `/index.php/login/v2` (the bare `/login/v2` alias
|
||||
# only exists for the poll surface, not initiate —
|
||||
# nc_routes.rs:50 vs :79).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/index.php/login/v2
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
poll_token: jsonpath "$.poll.token"
|
||||
# Regex-extract flow_token from the login URL.
|
||||
# Shape: http://<host>/login/v2/flow/<hex>
|
||||
flow_token: jsonpath "$.login" regex "/login/v2/flow/([a-f0-9]+)"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step D — Baseline poll. No submission yet, so the flow has
|
||||
# no `completed` result. MUST 404.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: {{poll_token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step E — Submit the login form. `handle_login_submit`
|
||||
# verifies credentials, calls list_folders_with_perms,
|
||||
# sees ≥ 2 drives, and returns the picker template
|
||||
# (HTTP 200 with HTML body). Pre-picker era this
|
||||
# would have been a redirect straight to
|
||||
# `/nextcloud/success`; that regression is what this
|
||||
# assertion pins.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/flow/{{flow_token}}
|
||||
[FormParams]
|
||||
user: {{username}}
|
||||
password: {{password}}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
# Two drives in the picker — home is `loop.first` (index [1] in
|
||||
# XPath 1-based), shared is [2]. We submit the shared value in
|
||||
# Step G so the composite marker actually differs from the
|
||||
# bare login name, exercising the ~<uuid> path (Step H asserts
|
||||
# on it). Local-name XPath so DAV/HTML namespace scoping doesn't
|
||||
# interfere.
|
||||
shared_folder_id: xpath "string((//input[@name='drive']/@value)[2])"
|
||||
[Asserts]
|
||||
# Picker markers — distinguish the picker template from any
|
||||
# other 200 response.
|
||||
body contains "Choose a drive"
|
||||
body contains "name=\"drive\""
|
||||
# Load-bearing regression: the picker's <form action> must
|
||||
# target the drive endpoint. A wrong action would ship the user
|
||||
# into an unrelated flow and only surface at the next request.
|
||||
body contains "action=\"/login/v2/flow/{{flow_token}}/drive\""
|
||||
# No `nc://` frontchannel URL should ever appear on the
|
||||
# response — NC clients pick up credentials via the poll
|
||||
# endpoint, not via a URL redirect. This mirrors the OIDC path
|
||||
# fix from tests/oidc/oidc.hurl Step 12F.
|
||||
body not contains "nc://login"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step F — Poll AGAIN. Still 404 — the picker has been
|
||||
# rendered but not submitted, so no `complete_flow`
|
||||
# call has run. Guards against a future refactor that
|
||||
# accidentally auto-completes the flow at the submit
|
||||
# step (e.g. re-introducing the pre-picker shortcut
|
||||
# the OIDC arm used to have).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: {{poll_token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step G — Submit the picker choice. handle_drive_pick reads
|
||||
# `pending_user_id` from the flow (stashed by
|
||||
# resolve_drive_or_complete when we rendered the
|
||||
# picker), validates the folder is visible, and
|
||||
# calls complete_flow(..., Some(folder_id)).
|
||||
#
|
||||
# Response redirects to /nextcloud/success — that's
|
||||
# where NC clients that don't use the poll backchannel
|
||||
# would land visually. NC clients that DO use the poll
|
||||
# (standard) have credentials in-hand by the time this
|
||||
# redirect fires, courtesy of `login_flow.complete()`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/flow/{{flow_token}}/drive
|
||||
[FormParams]
|
||||
drive: {{shared_folder_id}}
|
||||
|
||||
# 3xx redirect to /nextcloud/success. Not `nc://` — that's the
|
||||
# whole point of the earlier "friendly success page" fix.
|
||||
HTTP *
|
||||
[Asserts]
|
||||
status >= 300
|
||||
status < 400
|
||||
header "Location" == "/nextcloud/success"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step H — Post-picker poll. NOW the credentials appear.
|
||||
#
|
||||
# The composite `admin~<folder_id>` login name proves
|
||||
# the picker choice round-tripped into the app
|
||||
# password's login name (basic_auth_middleware.rs
|
||||
# treats the `~<uuid>` suffix as a chroot marker for
|
||||
# subsequent WebDAV / NC requests). Without the
|
||||
# composite, the sync client would target the home
|
||||
# drive regardless of what the user picked.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: {{poll_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
# Loose host match — the server derives base_url from its bind
|
||||
# config (which lands on `127.0.0.1` when neither
|
||||
# OXICLOUD_BASE_URL nor the host env is set), while test.env
|
||||
# uses `localhost` for its own variable. Both resolve to the
|
||||
# same address for a client; pin the port, not the host.
|
||||
jsonpath "$.server" matches "^https?://[^/]+:8087$"
|
||||
jsonpath "$.appPassword" isString
|
||||
# Load-bearing composite-marker assertion. Pre-fix (or if a
|
||||
# refactor ever drops the picker branch) this would show the
|
||||
# bare `admin` with no `~`.
|
||||
jsonpath "$.loginName" matches "^{{username}}~[0-9a-f-]{36}$"
|
||||
# Belt-and-braces: the exact folder id we picked in Step G is
|
||||
# what got wired into the login name. Catches a hypothetical
|
||||
# drive/folder id swap in `handle_drive_pick`.
|
||||
jsonpath "$.loginName" contains "{{shared_folder_id}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step I — Poll again — MUST 404. The completed result is
|
||||
# single-use (poll() removes it from the state map
|
||||
# on read); a regression that failed to remove would
|
||||
# leak credentials to any subsequent poll with the
|
||||
# same token, effectively a replay window.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
[FormParams]
|
||||
token: {{poll_token}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Teardown — remove the fixture shared drive.
|
||||
#
|
||||
# CRITICAL: individual Hurl files inside `tests/api/run.sh`
|
||||
# share DB state within a single run (postgres restarts once
|
||||
# per run.sh, not per file). Leaving this drive around inflates
|
||||
# admin's `list_folders_with_perms` result from 1 to 2, which
|
||||
# breaks any downstream file that assumes admin has exactly one
|
||||
# root folder (files-folders.hurl:43, favorites.hurl:59,
|
||||
# recent.hurl:47, and any future test using `/api/folders`).
|
||||
# Every hurl that creates a persistent drive/folder MUST clean
|
||||
# it up here, not rely on the next run.sh invocation to reset.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/drives/{{fixture_drive_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 204
|
||||
@@ -150,6 +150,7 @@ hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test
|
||||
"$API_DIR/registration.hurl" \
|
||||
"$API_DIR/nc_status_capabilities.hurl" \
|
||||
"$API_DIR/nc_login_flow_v2.hurl" \
|
||||
"$API_DIR/nc_login_flow_v2_drive_picker.hurl" \
|
||||
"$API_DIR/nc_ocs_user_info.hurl" \
|
||||
"$API_DIR/nc_avatar_preview.hurl" \
|
||||
"$API_DIR/files-folders.hurl" \
|
||||
|
||||
@@ -403,3 +403,306 @@ Content-Type: application/json
|
||||
{ "code": "{{oidc_code}}" }
|
||||
|
||||
HTTP 403
|
||||
|
||||
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# Step 12 — Nextcloud Login Flow v2 via OIDC — MULTI-DRIVE PATH
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
# Regression coverage for the customer-reported bug where OIDC
|
||||
# users were never shown the drive picker: the OIDC callback in
|
||||
# `auth_handler.rs::oidc_callback` used to mint the app password
|
||||
# inline and complete the flow with the bare username. Customers
|
||||
# with ≥ 2 drives had no way to pick a non-home drive under SSO,
|
||||
# and the deprecated `nc://` redirect broke NC clients that had
|
||||
# already picked up credentials via the poll backchannel
|
||||
# (`Impossible de valider la requête`).
|
||||
#
|
||||
# The fix routes the OIDC callback through the shared
|
||||
# `handle_oidc_login_completion` in `login_v2_handler.rs`, which
|
||||
# lists drives, renders the picker template on ≥ 2, and calls
|
||||
# `complete_flow(...)` only after the picker submit. Same
|
||||
# multi-drive fork the password path uses.
|
||||
#
|
||||
# What this section exercises (post-fix expected behaviour):
|
||||
#
|
||||
# A. Local admin logs in with password to get a JWT for
|
||||
# administrative operations (creating the shared drive
|
||||
# below — the OIDC user has no local password).
|
||||
# B. Admin creates a NEW shared drive owned by `oidc_user`. That
|
||||
# makes the OIDC user's drive count = 2 (their JIT-provisioned
|
||||
# personal + this shared), which is the multi-drive branch
|
||||
# trigger.
|
||||
# C. NC LFv2 initiate — anonymous, returns { poll_token, login_url }.
|
||||
# login_url embeds the flow_token that identifies this flow.
|
||||
# D. Pre-completion poll — MUST return 404. Baseline regression:
|
||||
# if a future change ever accidentally auto-completes the flow
|
||||
# before the picker submit, this catches it.
|
||||
# E. Kick off the NC OIDC branch → GET /login/v2/flow/{token}/oidc.
|
||||
# Server sets `nc_flow_token` on the OIDC state and 307s to
|
||||
# the IdP.
|
||||
# F. Follow the entire IdP → callback chain with `location: true`.
|
||||
# Post-fix, the callback returns the PICKER HTML (200), NOT a
|
||||
# `nc://` redirect. Pre-fix it would have 307'd to nc://.
|
||||
# G. Poll AGAIN — still 404 (picker not yet submitted). Proves
|
||||
# the callback did NOT call `login_flow.complete(...)` — the
|
||||
# exact regression the fix prevents.
|
||||
# H. Submit the picker with the shared-drive folder id.
|
||||
# I. Post-picker poll — 200 with `loginName` matching
|
||||
# `oidc_user~<uuid>` (composite marker → chroot-bound app
|
||||
# password). This is the load-bearing assertion: pre-fix
|
||||
# loginName was the bare `oidc_user`.
|
||||
# ═════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12A — Local admin password login.
|
||||
# The OIDC-provisioned `oidc_user` (auto-promoted to
|
||||
# admin via the group claim) has NO local password;
|
||||
# only local admin (created in Step 1) can authenticate
|
||||
# with `username/password`. Use JWT (Bearer) instead of
|
||||
# cookies so we skip CSRF ceremony for the drive-create
|
||||
# call below.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
admin_token: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12B — Admin creates a shared drive owned by `oidc_user`.
|
||||
# After this, list_folders_with_perms(oidc_user) returns
|
||||
# 2 rows (JIT-provisioned personal + this shared). The
|
||||
# picker template ties the composite `~<folder_id>`
|
||||
# marker to the FOLDER id (root of the drive), not the
|
||||
# drive id — that's the identifier the picker's radio
|
||||
# buttons carry and what `handle_drive_pick` looks up.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/drives
|
||||
Authorization: Bearer {{admin_token}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"kind": "shared",
|
||||
"name": "oidc-user-shared",
|
||||
"owner": { "type": "user", "id": "{{oidc_user_id}}" }
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
fixture_drive_id: jsonpath "$.id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12C — NC client initiates LFv2. Public endpoint, no auth.
|
||||
# Response carries the flow token (embedded in the
|
||||
# login URL) and the poll token (used by the NC
|
||||
# client's backchannel).
|
||||
#
|
||||
# Note: the initiate endpoint lives at
|
||||
# `/index.php/login/v2` (nc_routes.rs:50) — the bare
|
||||
# `/login/v2` variant only exists for the poll
|
||||
# surface, not for initiate. NC clients build the URL
|
||||
# from the `/index.php` convention.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/index.php/login/v2
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
nc_poll_token: jsonpath "$.poll.token"
|
||||
# Regex-extract the flow_token from the login URL. Shape is
|
||||
# `http://localhost:8087/login/v2/flow/<hex>`. The trailing hex is
|
||||
# what /login/v2/flow/{token}/... routes bind on.
|
||||
nc_flow_token: jsonpath "$.login" regex "/login/v2/flow/([a-f0-9]+)"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12D — Baseline poll. No user has authenticated yet, so the
|
||||
# flow has no `completed` result. MUST be 404.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
`token={{nc_poll_token}}`
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12E — Kick off the NC OIDC branch. Server prepares an OIDC
|
||||
# authorize with the NC flow token attached to state
|
||||
# (auth_application_service::prepare_oidc_authorize_for_nextcloud)
|
||||
# and 307s to the IdP. `location: false` so we can
|
||||
# capture the exact IdP URL for the manual chain follow
|
||||
# below.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/login/v2/flow/{{nc_flow_token}}/oidc
|
||||
[Options]
|
||||
location: false
|
||||
|
||||
HTTP 307
|
||||
[Captures]
|
||||
nc_idp_url: header "Location"
|
||||
[Asserts]
|
||||
# The IdP URL must carry `state` (which encodes nc_flow_token
|
||||
# server-side) and the PKCE challenge — same shape as the SPA
|
||||
# path in Step 3, just prepared through a different code path.
|
||||
header "Location" matches "^{{oidc_authorize_endpoint}}\\?"
|
||||
header "Location" contains "state="
|
||||
header "Location" contains "code_challenge_method=S256"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12F — Follow the full IdP → callback chain. The fake IdP
|
||||
# auto-approves (the earlier flow left a session cookie
|
||||
# for `oidc-test-user` — this exercises the realistic
|
||||
# "user already signed into their IdP" flow), the IdP
|
||||
# 302s back to /api/auth/oidc/callback?code=…&state=…,
|
||||
# and the callback routes into the NextcloudLogin arm.
|
||||
#
|
||||
# POST-FIX EXPECTED: the callback returns the drive
|
||||
# picker template (HTTP 200, HTML body) because
|
||||
# `handle_oidc_login_completion` saw ≥ 2 drives.
|
||||
# PRE-FIX would have been a 307 to
|
||||
# `nc://login/server:…&user:oidc_user&password:…` —
|
||||
# the very redirect the fix drops.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{nc_idp_url}}
|
||||
[Options]
|
||||
location: true
|
||||
location-trusted: true
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
# The picker HTML has one radio input per drive. Two drives here,
|
||||
# so two `value=` attributes on `<input name="drive">`. Home is
|
||||
# first (loop.first in the template); the shared drive is second.
|
||||
# Local-name XPath so the DAV/HTML namespace doesn't matter.
|
||||
shared_folder_id: xpath "string((//input[@name='drive']/@value)[2])"
|
||||
[Asserts]
|
||||
# Picker markers — proves this is the picker template and not
|
||||
# some other 200 response. Uses `contains` on distinctive strings
|
||||
# from the template.
|
||||
body contains "Choose a drive"
|
||||
body contains "name=\"drive\""
|
||||
# The picker's form MUST post to /login/v2/flow/{nc_flow_token}/drive.
|
||||
# A regression that generated a wrong action would ship users
|
||||
# into an unrelated flow and this pins the wire target.
|
||||
body contains "action=\"/login/v2/flow/{{nc_flow_token}}/drive\""
|
||||
# Load-bearing regression guard for the exact bug this fix
|
||||
# closes: pre-fix, the OIDC callback body would have been empty
|
||||
# and the Location header would have carried the nc:// URL. Now
|
||||
# there's no nc:// anywhere in the response.
|
||||
body not contains "nc://login"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12G — Poll AGAIN. Still 404 — the picker has not been
|
||||
# submitted, so `complete_flow` hasn't run and the
|
||||
# flow has no `completed` result.
|
||||
#
|
||||
# Pre-fix regression this catches: the OIDC callback
|
||||
# used to call `login_flow.complete(...)` inline before
|
||||
# the picker step. If a future change ever reintroduces
|
||||
# that shortcut, this 404 assertion flips to 200 and
|
||||
# the CI red flag lights up.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
`token={{nc_poll_token}}`
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12H — Submit the picker choice. Payload is form-encoded
|
||||
# (the picker's <form> is a POST HTML form). The
|
||||
# `drive` field is the folder UUID captured from the
|
||||
# picker's radio buttons.
|
||||
#
|
||||
# handle_drive_pick reads `pending_user_id` from the
|
||||
# flow (stashed by `resolve_drive_or_complete` when we
|
||||
# rendered the picker), validates the folder is
|
||||
# visible, resolves home vs non-home, and calls
|
||||
# complete_flow(..., Some(folder_id)).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/flow/{{nc_flow_token}}/drive
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
`drive={{shared_folder_id}}`
|
||||
|
||||
# The completion path redirects the browser to the friendly
|
||||
# success page. NOT a nc:// URL — the poll below is what
|
||||
# delivers credentials.
|
||||
HTTP *
|
||||
[Asserts]
|
||||
status >= 300
|
||||
status < 400
|
||||
header "Location" == "/nextcloud/success"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12I — Post-picker poll. NOW the credentials are ready.
|
||||
#
|
||||
# The composite `oidc_user~<folder_id>` login name is
|
||||
# the whole point of this test — it proves the OIDC
|
||||
# path honoured the drive pick and produced a
|
||||
# chroot-bound app-password credential. Pre-fix,
|
||||
# loginName here was the bare `oidc_user`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
`token={{nc_poll_token}}`
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
# Loose host match — the server derives base_url from its bind
|
||||
# config (which lands on `127.0.0.1` when neither
|
||||
# OXICLOUD_BASE_URL nor the host env is set), while test.env
|
||||
# uses `localhost` for its own variable. Both resolve to the
|
||||
# same address for a client; pin the port, not the host.
|
||||
jsonpath "$.server" matches "^https?://[^/]+:8087$"
|
||||
jsonpath "$.appPassword" isString
|
||||
# Composite marker present — this is the load-bearing regression
|
||||
# assertion. A pre-fix run would show `"oidc_user"` with no `~`.
|
||||
jsonpath "$.loginName" matches "^oidc_user~[0-9a-f-]{36}$"
|
||||
# Belt-and-braces: assert the folder id echoed back matches the
|
||||
# picker's radio value we submitted (no accidental drive/folder
|
||||
# swap in `handle_drive_pick`).
|
||||
jsonpath "$.loginName" contains "{{shared_folder_id}}"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12J — Poll again — MUST 404. The completed result is
|
||||
# single-use (poll() removes it from the map). A
|
||||
# regression that failed to remove would leak
|
||||
# credentials to any subsequent poll with the same
|
||||
# token.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/login/v2/poll
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
`token={{nc_poll_token}}`
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# No teardown for the fixture drive.
|
||||
#
|
||||
# The drive was created with `oidc_user` as SOLE owner (Step
|
||||
# 12B). Local `admin` created it via the admin-only
|
||||
# `POST /api/drives` but isn't a grant-holder — deleting the
|
||||
# drive requires `manage` on the drive resource, which admin's
|
||||
# Bearer token doesn't carry. Cleanup would have to happen as
|
||||
# `oidc_user`, but `oidc_user` has no local password and
|
||||
# running a second OIDC dance mid-file would pollute the
|
||||
# session cookies the earlier steps depend on.
|
||||
#
|
||||
# Safe to skip: `tests/oidc/run.sh` spawns a fresh DB per
|
||||
# invocation (`bash "$COMMON/spawn-db.sh"`), so nothing
|
||||
# downstream sees the leftover. The API-suite sibling
|
||||
# (`tests/api/nc_login_flow_v2_drive_picker.hurl`) DOES clean
|
||||
# up because that version creates the drive owned by admin —
|
||||
# and its runner IS multi-file. See
|
||||
# `feedback_hurl_teardown_shared_db` for the general rule.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user