Browse Source
Boots a throwaway instance against a seeded SQLite database and exercises every member and group write endpoint end to end, asserting on the resulting state rather than just the status code. Covers the guards that protect organization integrity: the last confirmed owner cannot be demoted, revoked or deleted; collections, groups and members from another organization are rejected; ids belonging to another organization return 404; and writes require a token. It also pins two behaviours that are easy to regress: a group update leaves member assignments alone, and every write is recorded in the event log with no acting user and no device type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>pull/7569/head
1 changed files with 454 additions and 0 deletions
@ -0,0 +1,454 @@ |
|||
#!/usr/bin/env bash |
|||
# |
|||
# Smoke test for the organization Public API member and group write endpoints. |
|||
# |
|||
# Boots a throwaway Vaultwarden instance against a temporary SQLite database |
|||
# seeded with two organizations, mints an organization API token for the first |
|||
# org, then exercises every write endpoint and asserts on the resulting state. |
|||
# |
|||
# Beyond the happy paths it asserts the guards that protect organization |
|||
# integrity: the last confirmed owner cannot be demoted, revoked or deleted; |
|||
# collections, groups and members from another organization are rejected; ids |
|||
# belonging to the second organization return HTTP 404; and a request with no |
|||
# token returns HTTP 401. It also asserts that a group update leaves member |
|||
# assignments alone, and that every write is recorded in the event log with no |
|||
# acting user, since a Public API client is not a user. |
|||
# |
|||
# The script exits non-zero if any assertion fails, so it is usable as a check. |
|||
# |
|||
# Requirements: bash, curl, jq, sqlite3, and either a prebuilt binary passed via |
|||
# the VW_BIN environment variable or a cargo toolchain to build one. |
|||
# |
|||
# Usage: |
|||
# scripts/smoke_public_api_write.sh |
|||
# VW_BIN=/path/to/vaultwarden PORT=8123 scripts/smoke_public_api_write.sh |
|||
|
|||
set -euo pipefail |
|||
|
|||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) |
|||
REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd) |
|||
cd "$REPO_ROOT" |
|||
|
|||
PORT="${PORT:-8082}" |
|||
VW_BIN="${VW_BIN:-$REPO_ROOT/target/debug/vaultwarden}" |
|||
API="http://127.0.0.1:$PORT" |
|||
|
|||
# ---- fixtures ------------------------------------------------------------- |
|||
ORG=22222222-2222-4222-8222-222222222222 |
|||
ORG2=99999999-9999-4999-8999-999999999999 |
|||
USER=11111111-1111-4111-8111-111111111111 |
|||
USER2=88888888-8888-4888-8888-888888888888 |
|||
USER3=cccccccc-cccc-4ccc-8ccc-cccccccccccc |
|||
# MEMBER is the only confirmed owner, so it is the one the guards protect. |
|||
MEMBER=33333333-3333-4333-8333-333333333333 |
|||
MEMBER2=aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa |
|||
MEMBER3=dddddddd-dddd-4ddd-8ddd-dddddddddddd |
|||
GROUP=44444444-4444-4444-8444-444444444444 |
|||
GROUP2=bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb |
|||
COLLECTION=55555555-5555-4555-8555-555555555555 |
|||
COLLECTION2=66666666-6666-4666-8666-666666666666 |
|||
APIKEYUUID=77777777-7777-4777-8777-777777777777 |
|||
APIKEY=smoketestapikey1234567890 |
|||
|
|||
NEW_EMAIL=newmember@example.com |
|||
|
|||
# ---- prerequisites -------------------------------------------------------- |
|||
for tool in curl jq sqlite3; do |
|||
if ! command -v "$tool" >/dev/null 2>&1; then |
|||
echo "ERROR: required tool '$tool' is not installed" >&2 |
|||
exit 2 |
|||
fi |
|||
done |
|||
|
|||
if [ ! -x "$VW_BIN" ]; then |
|||
if command -v cargo >/dev/null 2>&1; then |
|||
echo "Building vaultwarden (sqlite feature); this can take a while..." |
|||
cargo build --features sqlite |
|||
else |
|||
echo "ERROR: no binary at '$VW_BIN' and no cargo toolchain to build one." >&2 |
|||
echo "Set VW_BIN to a prebuilt binary or install a Rust toolchain." >&2 |
|||
exit 2 |
|||
fi |
|||
fi |
|||
|
|||
# ---- workspace + cleanup -------------------------------------------------- |
|||
TMP=$(mktemp -d) |
|||
SERVER_PID="" |
|||
cleanup() { |
|||
if [ -n "$SERVER_PID" ]; then |
|||
kill "$SERVER_PID" >/dev/null 2>&1 || true |
|||
wait "$SERVER_PID" 2>/dev/null || true |
|||
fi |
|||
rm -rf "$TMP" |
|||
} |
|||
trap cleanup EXIT |
|||
|
|||
export DATA_FOLDER="$TMP" |
|||
export DATABASE_URL="sqlite://$TMP/db.sqlite3" |
|||
export ADMIN_TOKEN="smoketestadmintoken" |
|||
export ORG_GROUPS_ENABLED=true |
|||
export ORG_EVENTS_ENABLED=true |
|||
export INVITATIONS_ALLOWED=true |
|||
export WEB_VAULT_ENABLED=false |
|||
export ROCKET_PORT="$PORT" |
|||
export ROCKET_ADDRESS=127.0.0.1 |
|||
export DOMAIN="http://localhost:$PORT" |
|||
|
|||
# ---- server helpers ------------------------------------------------------- |
|||
start_server() { |
|||
local logfile="$1" |
|||
"$VW_BIN" >"$logfile" 2>&1 & |
|||
SERVER_PID=$! |
|||
local i |
|||
for i in $(seq 1 90); do |
|||
if grep -q "Rocket has launched" "$logfile" 2>/dev/null; then |
|||
return 0 |
|||
fi |
|||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then |
|||
echo "ERROR: server exited during startup. Log:" >&2 |
|||
cat "$logfile" >&2 |
|||
return 1 |
|||
fi |
|||
sleep 1 |
|||
done |
|||
echo "ERROR: server did not launch within 90s. Log:" >&2 |
|||
cat "$logfile" >&2 |
|||
return 1 |
|||
} |
|||
|
|||
stop_server() { |
|||
if [ -n "$SERVER_PID" ]; then |
|||
kill "$SERVER_PID" >/dev/null 2>&1 || true |
|||
wait "$SERVER_PID" 2>/dev/null || true |
|||
SERVER_PID="" |
|||
fi |
|||
} |
|||
|
|||
# ---- assertion helpers ---------------------------------------------------- |
|||
FAILS=0 |
|||
pass() { printf 'PASS: %s\n' "$1"; } |
|||
fail() { printf 'FAIL: %s\n' "$1"; FAILS=$((FAILS + 1)); } |
|||
|
|||
check_eq() { # label actual expected |
|||
if [ "$2" = "$3" ]; then |
|||
pass "$1" |
|||
else |
|||
fail "$1 (expected [$3], got [$2])" |
|||
fi |
|||
} |
|||
|
|||
# req METHOD PATH [TOKEN] -> sets HTTP_CODE, body written to $TMP/body |
|||
req() { |
|||
local method="$1" path="$2" token="${3:-}" |
|||
if [ -n "$token" ]; then |
|||
HTTP_CODE=$(curl -sS -o "$TMP/body" -w '%{http_code}' \ |
|||
-X "$method" -H "Authorization: Bearer $token" "$API$path") |
|||
else |
|||
HTTP_CODE=$(curl -sS -o "$TMP/body" -w '%{http_code}' -X "$method" "$API$path") |
|||
fi |
|||
} |
|||
|
|||
# reqj METHOD PATH TOKEN JSON -> same, with a JSON request body |
|||
reqj() { |
|||
local method="$1" path="$2" token="$3" body="$4" |
|||
if [ -n "$token" ]; then |
|||
HTTP_CODE=$(curl -sS -o "$TMP/body" -w '%{http_code}' \ |
|||
-X "$method" -H "Authorization: Bearer $token" \ |
|||
-H 'Content-Type: application/json' -d "$body" "$API$path") |
|||
else |
|||
HTTP_CODE=$(curl -sS -o "$TMP/body" -w '%{http_code}' \ |
|||
-X "$method" -H 'Content-Type: application/json' -d "$body" "$API$path") |
|||
fi |
|||
} |
|||
|
|||
jqval() { jq -r "$1" "$TMP/body"; } |
|||
|
|||
jqcheck() { # label filter expected |
|||
check_eq "$1" "$(jqval "$2")" "$3" |
|||
} |
|||
|
|||
sqlcheck() { # label sql expected |
|||
check_eq "$1" "$(sqlite3 "$TMP/db.sqlite3" "$2")" "$3" |
|||
} |
|||
|
|||
# ---- boot once to run migrations, then seed, then boot to serve ----------- |
|||
echo "== Booting once to create the database schema ==" |
|||
start_server "$TMP/boot1.log" |
|||
stop_server |
|||
|
|||
echo "== Seeding two organizations with members, groups and collections ==" |
|||
sqlite3 "$TMP/db.sqlite3" <<SQL |
|||
INSERT INTO users (uuid,enabled,created_at,updated_at,login_verify_count,email,name,password_hash,salt,password_iterations,akey,security_stamp,equivalent_domains,excluded_globals,client_kdf_type,client_kdf_iter) |
|||
VALUES |
|||
('$USER',1,'2026-01-01 00:00:00','2026-01-01 00:00:00',0,'alice@example.com','Alice Example',X'00',X'00',100000,'','stamp-1','[]','[]',0,100000), |
|||
('$USER2',1,'2026-01-01 00:00:00','2026-01-01 00:00:00',0,'bob@example.com','Bob Other',X'00',X'00',100000,'','stamp-2','[]','[]',0,100000), |
|||
('$USER3',1,'2026-01-01 00:00:00','2026-01-01 00:00:00',0,'carol@example.com','Carol Example',X'00',X'00',100000,'','stamp-3','[]','[]',0,100000); |
|||
|
|||
INSERT INTO organizations (uuid,name,billing_email,private_key,public_key) VALUES |
|||
('$ORG','Test Org','billing@example.com',NULL,NULL), |
|||
('$ORG2','Other Org','other@example.com',NULL,NULL); |
|||
|
|||
INSERT INTO organization_api_key (uuid,org_uuid,atype,api_key,revision_date) VALUES |
|||
('$APIKEYUUID','$ORG',0,'$APIKEY','2026-01-01 00:00:00'); |
|||
|
|||
-- atype 0 is Owner and 2 is User; status 2 is Confirmed. |
|||
INSERT INTO users_organizations (uuid,user_uuid,org_uuid,invited_by_email,access_all,akey,status,atype,reset_password_key,external_id) VALUES |
|||
('$MEMBER','$USER','$ORG',NULL,0,'',2,0,NULL,'ext-member-1'), |
|||
('$MEMBER2','$USER2','$ORG2',NULL,0,'',2,2,NULL,'ext-member-2'), |
|||
('$MEMBER3','$USER3','$ORG',NULL,0,'',2,2,NULL,'ext-member-3'); |
|||
|
|||
INSERT INTO groups (uuid,organizations_uuid,name,access_all,external_id,creation_date,revision_date) VALUES |
|||
('$GROUP','$ORG','Engineering',0,'ext-group-1','2026-01-01 00:00:00','2026-01-01 00:00:00'), |
|||
('$GROUP2','$ORG2','Other Group',0,'ext-group-2','2026-01-01 00:00:00','2026-01-01 00:00:00'); |
|||
|
|||
INSERT INTO collections (uuid,org_uuid,name,external_id) VALUES |
|||
('$COLLECTION','$ORG','2.encryptedCiphertextName==','ext-collection-1'), |
|||
('$COLLECTION2','$ORG2','2.otherOrgCiphertext==','ext-collection-2'); |
|||
SQL |
|||
|
|||
echo "== Booting to serve ==" |
|||
start_server "$TMP/boot2.log" |
|||
|
|||
echo "== Minting an organization API token ==" |
|||
TOKEN=$(curl -sS -X POST "$API/identity/connect/token" \ |
|||
-d 'grant_type=client_credentials' \ |
|||
-d "client_id=organization.$ORG" \ |
|||
-d "client_secret=$APIKEY" \ |
|||
-d 'scope=api.organization' \ |
|||
-d 'device_identifier=eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee' \ |
|||
-d 'device_name=smoketest' \ |
|||
-d 'device_type=14' | jq -r '.access_token // empty') |
|||
|
|||
if [ -z "$TOKEN" ]; then |
|||
echo "FAIL: could not mint an organization API token" >&2 |
|||
exit 1 |
|||
fi |
|||
pass "minted organization API token" |
|||
|
|||
echo "" |
|||
echo "== Create a group ==" |
|||
|
|||
reqj POST "/api/public/groups" "$TOKEN" \ |
|||
"{\"name\":\"Platform\",\"externalId\":\"ext-new-group\",\"collections\":[{\"id\":\"$COLLECTION\",\"readOnly\":true,\"hidePasswords\":false,\"manage\":false}]}" |
|||
check_eq "create group -> 200" "$HTTP_CODE" "200" |
|||
jqcheck "created group discriminator" '.object' "group" |
|||
jqcheck "created group name" '.name' "Platform" |
|||
jqcheck "created group externalId" '.externalId' "ext-new-group" |
|||
jqcheck "created group accessAll defaults to false" '.accessAll' "false" |
|||
NEWGROUP=$(jqval '.id') |
|||
|
|||
req GET "/api/public/groups/$NEWGROUP" "$TOKEN" |
|||
check_eq "created group is readable -> 200" "$HTTP_CODE" "200" |
|||
jqcheck "created group kept its collection grant" '.collections | length' "1" |
|||
jqcheck "created group collection id" '.collections[0].id' "$COLLECTION" |
|||
jqcheck "created group collection readOnly" '.collections[0].readOnly' "true" |
|||
|
|||
echo "" |
|||
echo "== Group input validation ==" |
|||
|
|||
reqj POST "/api/public/groups" "$TOKEN" \ |
|||
"{\"name\":\"Bad\",\"collections\":[{\"id\":\"$COLLECTION2\"}]}" |
|||
check_eq "group with another org's collection -> 400" "$HTTP_CODE" "400" |
|||
|
|||
echo "" |
|||
echo "== Update a group ==" |
|||
|
|||
reqj PUT "/api/public/groups/$NEWGROUP" "$TOKEN" \ |
|||
"{\"name\":\"Platform Team\",\"externalId\":\"ext-new-group-2\",\"collections\":[]}" |
|||
check_eq "update group -> 200" "$HTTP_CODE" "200" |
|||
jqcheck "updated group name" '.name' "Platform Team" |
|||
jqcheck "updated group externalId" '.externalId' "ext-new-group-2" |
|||
|
|||
req GET "/api/public/groups/$NEWGROUP" "$TOKEN" |
|||
jqcheck "update replaced the collection grants" '.collections | length' "0" |
|||
|
|||
echo "" |
|||
echo "== Group member ids ==" |
|||
|
|||
reqj PUT "/api/public/groups/$NEWGROUP/member-ids" "$TOKEN" "{\"memberIds\":[\"$MEMBER3\"]}" |
|||
check_eq "set group member-ids -> 200" "$HTTP_CODE" "200" |
|||
|
|||
req GET "/api/public/groups/$NEWGROUP/member-ids" "$TOKEN" |
|||
jqcheck "group has one member" 'length' "1" |
|||
jqcheck "group member is the expected membership" '.[0]' "$MEMBER3" |
|||
|
|||
reqj PUT "/api/public/groups/$NEWGROUP/member-ids" "$TOKEN" "{\"memberIds\":[\"$MEMBER2\"]}" |
|||
check_eq "group member-ids from another org -> 400" "$HTTP_CODE" "400" |
|||
|
|||
# Members are owned by the member-ids endpoint, so a group update must leave |
|||
# them alone. The internal endpoint clears them, this one deliberately does not. |
|||
reqj PUT "/api/public/groups/$NEWGROUP" "$TOKEN" "{\"name\":\"Platform Team\",\"collections\":[]}" |
|||
check_eq "update group again -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/groups/$NEWGROUP/member-ids" "$TOKEN" |
|||
jqcheck "group update left member assignments intact" 'length' "1" |
|||
|
|||
echo "" |
|||
echo "== Create a member ==" |
|||
|
|||
reqj POST "/api/public/members" "$TOKEN" \ |
|||
"{\"email\":\"$NEW_EMAIL\",\"type\":2,\"externalId\":\"ext-new-member\",\"collections\":[{\"id\":\"$COLLECTION\",\"readOnly\":true,\"hidePasswords\":false,\"manage\":false}],\"groups\":[\"$GROUP\"]}" |
|||
check_eq "create member -> 200" "$HTTP_CODE" "200" |
|||
jqcheck "created member discriminator" '.object' "member" |
|||
jqcheck "created member email" '.email' "$NEW_EMAIL" |
|||
jqcheck "created member type" '.type' "2" |
|||
jqcheck "created member externalId" '.externalId' "ext-new-member" |
|||
jqcheck "created member is invited" '.status' "0" |
|||
NEWMEMBER=$(jqval '.id') |
|||
|
|||
req GET "/api/public/members/$NEWMEMBER" "$TOKEN" |
|||
jqcheck "created member kept its collection grant" '.collections | length' "1" |
|||
jqcheck "created member collection readOnly" '.collections[0].readOnly' "true" |
|||
|
|||
req GET "/api/public/members/$NEWMEMBER/group-ids" "$TOKEN" |
|||
jqcheck "created member joined the group" 'length' "1" |
|||
jqcheck "created member group id" '.[0]' "$GROUP" |
|||
|
|||
echo "" |
|||
echo "== Member input validation ==" |
|||
|
|||
reqj POST "/api/public/members" "$TOKEN" "{\"email\":\"$NEW_EMAIL\",\"type\":2}" |
|||
check_eq "duplicate member email -> 400" "$HTTP_CODE" "400" |
|||
|
|||
reqj POST "/api/public/members" "$TOKEN" \ |
|||
"{\"email\":\"other@example.com\",\"type\":2,\"groups\":[\"$GROUP2\"]}" |
|||
check_eq "member with another org's group -> 400" "$HTTP_CODE" "400" |
|||
|
|||
reqj POST "/api/public/members" "$TOKEN" "{\"email\":\"bad@example.com\",\"type\":99}" |
|||
check_eq "member with an unknown type -> 400" "$HTTP_CODE" "400" |
|||
|
|||
echo "" |
|||
echo "== Update a member ==" |
|||
|
|||
reqj PUT "/api/public/members/$NEWMEMBER" "$TOKEN" \ |
|||
"{\"type\":2,\"externalId\":\"ext-updated\",\"collections\":[{\"id\":\"$COLLECTION\",\"readOnly\":false,\"hidePasswords\":true,\"manage\":false}],\"groups\":[]}" |
|||
check_eq "update member -> 200" "$HTTP_CODE" "200" |
|||
jqcheck "updated member externalId" '.externalId' "ext-updated" |
|||
|
|||
req GET "/api/public/members/$NEWMEMBER" "$TOKEN" |
|||
jqcheck "update replaced the collection grants" '.collections | length' "1" |
|||
jqcheck "updated collection readOnly" '.collections[0].readOnly' "false" |
|||
jqcheck "updated collection hidePasswords" '.collections[0].hidePasswords' "true" |
|||
|
|||
req GET "/api/public/members/$NEWMEMBER/group-ids" "$TOKEN" |
|||
jqcheck "update cleared the group assignments" 'length' "0" |
|||
|
|||
echo "" |
|||
echo "== Member group ids ==" |
|||
|
|||
reqj PUT "/api/public/members/$NEWMEMBER/group-ids" "$TOKEN" "{\"groupIds\":[\"$GROUP\"]}" |
|||
check_eq "set member group-ids -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/members/$NEWMEMBER/group-ids" "$TOKEN" |
|||
jqcheck "member group-ids applied" '.[0]' "$GROUP" |
|||
|
|||
reqj PUT "/api/public/members/$NEWMEMBER/group-ids" "$TOKEN" "{\"groupIds\":[\"$GROUP2\"]}" |
|||
check_eq "member group-ids from another org -> 400" "$HTTP_CODE" "400" |
|||
|
|||
echo "" |
|||
echo "== Reinvite ==" |
|||
|
|||
req POST "/api/public/members/$NEWMEMBER/reinvite" "$TOKEN" |
|||
check_eq "reinvite an invited member -> 200" "$HTTP_CODE" "200" |
|||
|
|||
echo "" |
|||
echo "== Revoke and restore ==" |
|||
|
|||
req POST "/api/public/members/$MEMBER3/revoke" "$TOKEN" |
|||
check_eq "revoke a member -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/members/$MEMBER3" "$TOKEN" |
|||
jqcheck "revoked member has a revoked status" '.status < 0' "true" |
|||
|
|||
req POST "/api/public/members/$MEMBER3/revoke" "$TOKEN" |
|||
check_eq "revoking twice -> 400" "$HTTP_CODE" "400" |
|||
|
|||
req POST "/api/public/members/$MEMBER3/restore" "$TOKEN" |
|||
check_eq "restore a member -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/members/$MEMBER3" "$TOKEN" |
|||
jqcheck "restored member is confirmed again" '.status' "2" |
|||
|
|||
req POST "/api/public/members/$MEMBER3/restore" "$TOKEN" |
|||
check_eq "restoring an active member -> 400" "$HTTP_CODE" "400" |
|||
|
|||
echo "" |
|||
echo "== The last confirmed owner is protected ==" |
|||
|
|||
reqj PUT "/api/public/members/$MEMBER" "$TOKEN" "{\"type\":2}" |
|||
check_eq "demoting the last owner -> 400" "$HTTP_CODE" "400" |
|||
|
|||
req DELETE "/api/public/members/$MEMBER" "$TOKEN" |
|||
check_eq "deleting the last owner -> 400" "$HTTP_CODE" "400" |
|||
|
|||
req POST "/api/public/members/$MEMBER/revoke" "$TOKEN" |
|||
check_eq "revoking the last owner -> 400" "$HTTP_CODE" "400" |
|||
|
|||
req GET "/api/public/members/$MEMBER" "$TOKEN" |
|||
jqcheck "the last owner is untouched, type" '.type' "0" |
|||
jqcheck "the last owner is untouched, status" '.status' "2" |
|||
|
|||
echo "" |
|||
echo "== Organization scoping boundary ==" |
|||
|
|||
reqj PUT "/api/public/members/$MEMBER2" "$TOKEN" "{\"type\":2}" |
|||
check_eq "updating a member of another org -> 404" "$HTTP_CODE" "404" |
|||
req DELETE "/api/public/members/$MEMBER2" "$TOKEN" |
|||
check_eq "deleting a member of another org -> 404" "$HTTP_CODE" "404" |
|||
req POST "/api/public/members/$MEMBER2/revoke" "$TOKEN" |
|||
check_eq "revoking a member of another org -> 404" "$HTTP_CODE" "404" |
|||
reqj PUT "/api/public/groups/$GROUP2" "$TOKEN" "{\"name\":\"Hijacked\"}" |
|||
check_eq "updating a group of another org -> 404" "$HTTP_CODE" "404" |
|||
req DELETE "/api/public/groups/$GROUP2" "$TOKEN" |
|||
check_eq "deleting a group of another org -> 404" "$HTTP_CODE" "404" |
|||
|
|||
echo "" |
|||
echo "== Delete ==" |
|||
|
|||
req DELETE "/api/public/members/$NEWMEMBER" "$TOKEN" |
|||
check_eq "delete member -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/members/$NEWMEMBER" "$TOKEN" |
|||
check_eq "deleted member is gone -> 404" "$HTTP_CODE" "404" |
|||
|
|||
req DELETE "/api/public/groups/$NEWGROUP" "$TOKEN" |
|||
check_eq "delete group -> 200" "$HTTP_CODE" "200" |
|||
req GET "/api/public/groups/$NEWGROUP" "$TOKEN" |
|||
check_eq "deleted group is gone -> 404" "$HTTP_CODE" "404" |
|||
|
|||
echo "" |
|||
echo "== Authentication required ==" |
|||
|
|||
reqj POST "/api/public/groups" "" "{\"name\":\"NoToken\"}" |
|||
check_eq "create group with no token -> 401" "$HTTP_CODE" "401" |
|||
req DELETE "/api/public/members/$MEMBER3" |
|||
check_eq "delete member with no token -> 401" "$HTTP_CODE" "401" |
|||
req GET "/api/public/members/$MEMBER3" "$TOKEN" |
|||
check_eq "the unauthenticated delete changed nothing" "$HTTP_CODE" "200" |
|||
|
|||
echo "" |
|||
echo "== Writes are recorded in the event log without an acting user ==" |
|||
|
|||
stop_server |
|||
|
|||
# 1400 GroupCreated, 1401 GroupUpdated, 1402 GroupDeleted, |
|||
# 1500 OrganizationUserInvited, 1502 OrganizationUserUpdated, |
|||
# 1503 OrganizationUserRemoved, 1511 Revoked, 1512 Restored. |
|||
for pair in "1400:group created" "1401:group updated" "1402:group deleted" \ |
|||
"1500:member invited" "1502:member updated" "1503:member removed" \ |
|||
"1511:member revoked" "1512:member restored"; do |
|||
code="${pair%%:*}" |
|||
label="${pair#*:}" |
|||
got=$(sqlite3 "$TMP/db.sqlite3" \ |
|||
"SELECT COUNT(*) > 0 FROM event WHERE org_uuid='$ORG' AND event_type=$code;") |
|||
check_eq "event logged: $label" "$got" "1" |
|||
done |
|||
|
|||
sqlcheck "no Public API event records an acting user" \ |
|||
"SELECT COUNT(*) FROM event WHERE org_uuid='$ORG' AND act_user_uuid IS NOT NULL;" "0" |
|||
sqlcheck "no Public API event records a device type" \ |
|||
"SELECT COUNT(*) FROM event WHERE org_uuid='$ORG' AND device_type IS NOT NULL;" "0" |
|||
sqlcheck "Public API events still record the client address" \ |
|||
"SELECT COUNT(*) FROM event WHERE org_uuid='$ORG' AND ip_address IS NULL;" "0" |
|||
|
|||
echo "" |
|||
if [ "$FAILS" -ne 0 ]; then |
|||
echo "RESULT: $FAILS assertion(s) failed." |
|||
exit 1 |
|||
fi |
|||
echo "RESULT: all assertions passed." |
|||
Loading…
Reference in new issue