feat(nextcloud-files): Anmeldung per Passwort und im Browser (Zwei-Faktor), Abmelden mit Widerruf
- Anmelde-Client (getapppassword, cloud/user, Widerruf, Login Flow v2 mit fester Abfrageadresse, Link aus Basis und Token neu gebaut), Anmeldebremse 3/15 min je Benutzer und 8/30 min je Server, Ablaufspeicher für Browser-Anmeldungen (20 min, höchstens 200, eine je Benutzer) - Kontodienst: Verbinden, Trennen mit Widerruf, Sitzung mit Zugangsschlüssel-Sperre, frisch ausgestellte oder ersetzte App-Passwörter bleiben nie verwaist; jeder Kontozugriff über forTenant mit Mandant UND Benutzer aus dem Token - Migration 20261008183000: Spalte ncLoginName (App-Passwort gilt nur für den Anmeldenamen der Ausstellung, gemessen mit E-Mail-Anmeldung gegen Nextcloud 34) - Verbindungsbildschirm und Kontoleiste, Texte de/en, RLS-Inventar fortgeschrieben, E2E-Skript e2e-connect.sh Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-zu-Ende-Pruefung Aufgabe 2 (quick-261008-mzu): Verbinden mit Passwort, Zwei-Faktor-Konto,
|
||||
# Browser-Anmeldung (Start/Abfrage/Abbruch), Wiederverbinden ohne verwaiste Zugaenge, Trennen mit
|
||||
# Widerruf. Voraussetzung: lokaler Stack laeuft (neu gebaut), nc-test-setup.sh ist gelaufen.
|
||||
# Die Zwei-Faktor-Anmeldung im Browser (Zugriff gewaehren) belegt der Playwright-Lauf, nicht dieses Skript.
|
||||
set -euo pipefail
|
||||
# shellcheck source=e2e-lib.sh
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/e2e-lib.sh"
|
||||
|
||||
JAR="$E2E_TMP/admin.jar"
|
||||
C="$API/modules/nextcloud-files"
|
||||
|
||||
e2e_login "$JAR"
|
||||
e2e_activate "$JAR"
|
||||
e2e_set_address "$JAR"
|
||||
|
||||
# Sauberer Start: Verbindung trennen (200, oder 409 wenn nicht verbunden).
|
||||
code=$(e2e_status "$JAR" DELETE "$C/connect")
|
||||
case "$code" in 200) ;; 409) e2e_contains "$E2E_TMP/body.out" 'notConnected' "Start: notConnected" ;; *) e2e_fail "DELETE connect (Start) -> $code" ;; esac
|
||||
e2e_expect 0 "$(e2e_nc_tokens anna)" "Start: keine Tessera-Zugaenge fuer anna"
|
||||
|
||||
# 1. Verbinden mit Passwort
|
||||
code=$(e2e_connect_anna "$JAR")
|
||||
e2e_expect 200 "$code" "connect/password anna"
|
||||
e2e_contains "$E2E_TMP/body.out" '"ncUserId":"anna"' "Antwort: ncUserId"
|
||||
if grep -q -e 'User1-Pass' -e 'ncrypted' -e 'appPassword' "$E2E_TMP/body.out"; then e2e_fail "Antwort enthaelt ein Geheimnis"; fi
|
||||
|
||||
code=$(e2e_status "$JAR" GET "$C/status")
|
||||
e2e_expect 200 "$code" "GET status"
|
||||
e2e_contains "$E2E_TMP/body.out" '"status":"ACTIVE"' "status ACTIVE"
|
||||
if grep -q -e 'User1-Pass' -e 'ncrypted' "$E2E_TMP/body.out"; then e2e_fail "status enthaelt ein Geheimnis"; fi
|
||||
|
||||
e2e_expect 1 "$(e2e_nc_tokens anna)" "genau ein Tessera-Zugang fuer anna"
|
||||
|
||||
# 2. Wiederverbinden: der alte Zugang wird widerrufen, es bleibt genau einer
|
||||
code=$(e2e_connect_anna "$JAR")
|
||||
e2e_expect 200 "$code" "Wiederverbinden anna"
|
||||
e2e_expect 1 "$(e2e_nc_tokens anna)" "nach Wiederverbinden genau ein Tessera-Zugang"
|
||||
|
||||
# 2b. Anmeldung mit E-Mail-Adresse: das App-Passwort gehoert zum eingegebenen Anmeldenamen (gemessen:
|
||||
# mit der Kennung als Basic-Benutzer antwortet Nextcloud 401). Der Widerruf beim Trennen gelingt nur,
|
||||
# wenn Tessera den Anmeldenamen mitgespeichert hat; das belegt die Token-Zahl 0 danach.
|
||||
NC_OCC user:setting anna settings email anna@example.com >/dev/null
|
||||
code=$(e2e_status "$JAR" POST "$C/connect/password" '{"loginName":"anna@example.com","password":"User1-Pass-12345"}')
|
||||
e2e_expect 200 "$code" "connect/password mit E-Mail"
|
||||
e2e_contains "$E2E_TMP/body.out" '"ncUserId":"anna"' "E-Mail-Anmeldung: ncUserId ist die Kennung"
|
||||
e2e_expect 1 "$(e2e_nc_tokens anna)" "E-Mail-Anmeldung: der alte Zugang wurde widerrufen"
|
||||
code=$(e2e_status "$JAR" DELETE "$C/connect")
|
||||
e2e_expect 200 "$code" "Trennen nach E-Mail-Anmeldung"
|
||||
e2e_expect 0 "$(e2e_nc_tokens anna)" "Widerruf mit dem Anmeldenamen der Ausstellung"
|
||||
code=$(e2e_connect_anna "$JAR")
|
||||
e2e_expect 200 "$code" "wieder mit der Kennung verbinden"
|
||||
|
||||
# 3. Zweiter Tessera-Benutzer derselben Organisation sieht das Konto nicht (Mandant UND Benutzer)
|
||||
e2e_second_user e2euser "$E2E_TMP/e2euser.jar"
|
||||
e2e_grant_use "$JAR" "$E2E_TMP/e2euser.jar"
|
||||
code=$(e2e_status "$E2E_TMP/e2euser.jar" GET "$C/status")
|
||||
e2e_expect 200 "$code" "e2euser GET status"
|
||||
e2e_contains "$E2E_TMP/body.out" '"account":null' "e2euser sieht anna's Konto nicht (account null)"
|
||||
code=$(e2e_status "$E2E_TMP/e2euser.jar" DELETE "$C/connect")
|
||||
e2e_expect 409 "$code" "e2euser kann annas Konto nicht trennen"
|
||||
e2e_contains "$E2E_TMP/body.out" 'notConnected' "e2euser: notConnected"
|
||||
e2e_expect 1 "$(e2e_nc_tokens anna)" "annas Zugang bleibt nach dem Trennversuch von e2euser"
|
||||
|
||||
# 4. Zwei-Faktor-Konto: 422 credentialsOrTwoFactor. Jeder Versuch zaehlt in Tessera als Fehlversuch
|
||||
# (3 je Benutzer in 15 min); bei 429 startet die API neu und der Versuch wird einmal wiederholt.
|
||||
zoe_attempt() {
|
||||
e2e_status "$JAR" POST "$C/connect/password" '{"loginName":"zoe","password":"User2-Pass-12345"}'
|
||||
}
|
||||
code=$(zoe_attempt)
|
||||
if [ "$code" = "429" ]; then
|
||||
e2e_contains "$E2E_TMP/body.out" 'tooManyAttempts' "429 ist tooManyAttempts"
|
||||
(cd "$E2E_DIR/../../../.." && docker compose restart api >/dev/null)
|
||||
e2e_wait_health
|
||||
e2e_login "$JAR"
|
||||
code=$(zoe_attempt)
|
||||
fi
|
||||
e2e_expect 422 "$code" "zoe per Passwort"
|
||||
e2e_contains "$E2E_TMP/body.out" 'credentialsOrTwoFactor' "zoe: credentialsOrTwoFactor"
|
||||
e2e_expect 0 "$(e2e_nc_tokens zoe)" "zoe bekommt keinen Zugang"
|
||||
|
||||
# 5. Browser-Anmeldung: Start, Abfrage (noch nicht bestaetigt), Abbruch
|
||||
code=$(e2e_status "$JAR" POST "$C/connect/flow")
|
||||
e2e_expect 200 "$code" "connect/flow Start"
|
||||
e2e_contains "$E2E_TMP/body.out" "\"loginUrl\":\"$NC_BASE/index.php/login/v2/flow/" "loginUrl zeigt auf die konfigurierte Adresse"
|
||||
if grep -qiE '"(token|poll)' "$E2E_TMP/body.out"; then e2e_fail "Flow-Antwort traegt Token oder Poll-Feld"; fi
|
||||
FLOW_ID=$(python3 -I -c 'import json,sys; print(json.load(open(sys.argv[1]))["flowId"])' "$E2E_TMP/body.out")
|
||||
|
||||
code=$(e2e_status "$JAR" GET "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 200 "$code" "connect/flow Abfrage"
|
||||
e2e_contains "$E2E_TMP/body.out" '"state":"pending"' "Abfrage: pending"
|
||||
|
||||
# Ein zweiter Tessera-Benutzer kennt die Kennung nicht: 404.
|
||||
code=$(e2e_status "$E2E_TMP/e2euser.jar" GET "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 404 "$code" "fremder Benutzer fragt Ablauf ab"
|
||||
code=$(e2e_status "$E2E_TMP/e2euser.jar" DELETE "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 404 "$code" "fremder Benutzer bricht Ablauf ab"
|
||||
code=$(e2e_status "$JAR" GET "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 200 "$code" "eigener Ablauf lebt nach dem fremden Versuch"
|
||||
|
||||
code=$(e2e_status "$JAR" DELETE "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 200 "$code" "connect/flow Abbruch"
|
||||
code=$(e2e_status "$JAR" GET "$C/connect/flow/$FLOW_ID")
|
||||
e2e_expect 404 "$code" "abgebrochener Ablauf ist weg"
|
||||
|
||||
# 6. Trennen mit Widerruf
|
||||
e2e_expect 1 "$(e2e_nc_tokens anna)" "vor dem Trennen genau ein Zugang"
|
||||
code=$(e2e_status "$JAR" DELETE "$C/connect")
|
||||
e2e_expect 200 "$code" "DELETE connect"
|
||||
e2e_expect 0 "$(e2e_nc_tokens anna)" "nach dem Trennen kein Tessera-Zugang mehr"
|
||||
code=$(e2e_status "$JAR" GET "$C/status")
|
||||
e2e_expect 200 "$code" "GET status nach Trennen"
|
||||
e2e_contains "$E2E_TMP/body.out" '"account":null' "status: account null"
|
||||
|
||||
echo "e2e connect ok"
|
||||
@@ -82,3 +82,49 @@ e2e_second_user() {
|
||||
e2e_expect 200 "$code" "Passwortwechsel $name"
|
||||
e2e_login "$jar" "$name" "$final"
|
||||
}
|
||||
|
||||
# --- Aufgabe 2: Konten -------------------------------------------------------------------
|
||||
|
||||
NC_OCC() { docker exec -u www-data tessera-nc-test php occ "$@"; }
|
||||
|
||||
# e2e_nc_tokens <nc-benutzer> — Anzahl der Tessera-Zugaenge (Name enthaelt "Tessera") in der
|
||||
# Nextcloud. Messweg: occ user:auth-tokens:list --output=json, Zeilen mit "Tessera" im Namen.
|
||||
e2e_nc_tokens() {
|
||||
NC_OCC user:auth-tokens:list "$1" --output=json 2>/dev/null \
|
||||
| python3 -I -c 'import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(len([t for t in d if "Tessera" in str(t.get("name",""))]))'
|
||||
}
|
||||
|
||||
# e2e_connect_anna <jar> — verbindet anna (Passwort-Weg) im Tessera-Benutzer des <jar>.
|
||||
# Gibt den HTTP-Status aus; der Antwortkoerper landet in $E2E_TMP/body.out.
|
||||
e2e_connect_anna() {
|
||||
e2e_status "$1" POST "$API/modules/nextcloud-files/connect/password" \
|
||||
'{"loginName":"anna","password":"User1-Pass-12345"}'
|
||||
}
|
||||
|
||||
# e2e_wait_health — wartet bis die API antwortet.
|
||||
e2e_wait_health() {
|
||||
local i
|
||||
for i in $(seq 1 60); do
|
||||
curl -sf "$API/health" >/dev/null 2>&1 && return 0
|
||||
sleep 2
|
||||
done
|
||||
e2e_fail "API wurde nicht gesund"
|
||||
}
|
||||
|
||||
# e2e_grant_use <admin-jar> <benutzer-jar> [slug] — gibt dem Benutzer des zweiten <jar> die
|
||||
# Freigabestufe Benutzen fuer das Modul (idempotent: ein vorhandener Grant ist in Ordnung).
|
||||
e2e_grant_use() {
|
||||
local admin_jar=$1 user_jar=$2 slug=${3:-nextcloud-files} code user_id module_id
|
||||
code=$(e2e_status "$user_jar" GET "$API/auth/me" "" "$E2E_TMP/me.out")
|
||||
e2e_expect 200 "$code" "auth/me"
|
||||
user_id=$(python3 -I -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' "$E2E_TMP/me.out")
|
||||
e2e_status "$admin_jar" GET "$API/modules/catalog" "" "$E2E_TMP/catalog.out" >/dev/null
|
||||
module_id=$(python3 -I -c 'import json,sys
|
||||
for m in json.load(open(sys.argv[2])):
|
||||
if m["slug"]==sys.argv[1]: print(m["id"]); break' "$slug" "$E2E_TMP/catalog.out")
|
||||
code=$(e2e_status "$admin_jar" POST "$API/module-grants" \
|
||||
"{\"moduleId\":\"$module_id\",\"userId\":\"$user_id\",\"level\":\"USE\"}")
|
||||
case "$code" in 200|201|409) ;; *) e2e_fail "Freigabe Benutzen -> $code" ;; esac
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user