GET /subjects/:id/required-actions and PUT…
GET /subjects/:id/required-actions and PUT /subjects/:id/required-actions
The read requires view-users, the write manage-users. Sets a subject's
required actions wholesale — an action left out of the list is one the
caller clears, not one left alone.
If-Match is mandatory here, not optional. This route replaces an
authorization-bearing list whole, so a stale write reinstates exactly what
another administrator has just removed; the header comes from the matching
GET, which answers an ETag over the same list. Absent, the request is
refused with 428 Precondition Required and nothing is changed; stale,
with 412. The list is read under the same lock the replacement runs
under, so two callers sent at once are serialised — the second sees what
the first wrote rather than matching the same pre-write state.
On grace, who was created through POST /subjects and so carries
update-password and nothing else. The read first, for the ETag the write
needs:
curl -sS -D - \
-H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:3000/admin/tenants/demo/subjects/01a0d7ef-2be7-7a2e-bbc5-4646e22c3169/required-actions
HTTP/1.1 200 OK
x-request-id: 01a0d7ef-47de-7e63-b9e4-f21b023efd84
etag: "b6a877586a3e8e6eec8a74fd3ec35d6463a777d4a4f379aa095140acd8edcc10"
content-type: application/json; charset=utf-8
content-length: 31
{"actions":["update-password"]}
(This read predates the cache-control: no-store header and was not re-run.)
The same write twice: without the header, then with it.
curl -sS -X PUT \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"actions": ["configure-totp"]}' \
http://localhost:3000/admin/tenants/demo/subjects/01a0d7ef-2be7-7a2e-bbc5-4646e22c3169/required-actions
curl -sS -D - -X PUT \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-H 'If-Match: "b6a877586a3e8e6eec8a74fd3ec35d6463a777d4a4f379aa095140acd8edcc10"' \
-d '{"actions": ["configure-totp"]}' \
http://localhost:3000/admin/tenants/demo/subjects/01a0d7ef-2be7-7a2e-bbc5-4646e22c3169/required-actions
The reply to the second is the set as it now stands — update-password is
gone, cleared by being left out — and a fresh ETag for the next write:
{"type":"about:blank","title":"Precondition Required","status":428,"detail":"If-Match is required to replace a subject’s required actions","instance":"01a0d7ef-47f4-770e-84d7-b7570de8508e"}
HTTP/1.1 200 OK
x-request-id: 01a0d7ef-62d6-749d-815d-05486997038e
etag: "eef964de74a02f6ce5f1ed1b1aa5e6a4f65aee68bfd955a4db04374af8ec513b"
content-type: application/json; charset=utf-8
content-length: 30
{"actions":["configure-totp"]}
(Not re-run for the same reason as the read above.)
Replaying the first ETag, now one generation stale, changes nothing:
{"type":"about:blank","title":"Precondition Failed","status":412,"detail":"If-Match no longer matches","instance":"01a0d7ef-62f8-7819-a402-6a7987acd136"}