Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 2s
Rollen per gebruiker: - Drie rollen: beheerder (alles), operator (stacks, containers en backups) en alleen lezen. Afgedwongen in de before_request-guard, niet alleen in de UI. - Eigen wachtwoord wijzigen mag iedereen; de laatste beheerder kan niet gedegradeerd of verwijderd worden. - Proxy-gebruikers zonder eigen account krijgen AUTH.proxy_role; bestaat er wel een lokaal account met die naam, dan wint dat account. - Accounts van voor deze versie hebben geen rol en gelden als beheerder. Rechtenescalatie verholpen: - set_password verving het hele gebruikersrecord, waardoor het role-veld wegviel. Een ontbrekende rol geldt als beheerder (nodig voor bestaande accounts), dus elke operator of viewer kon zichzelf tot beheerder promoveren door zijn eigen wachtwoord te wijzigen. Gevonden bij het naspelen van de rollenflow tegen een draaiende instantie. Geplande backups draaiden nooit: - De scheduler stempelde ook taken af die zichzelf hadden overgeslagen. De backup-taak bewaakt zelf het ingestelde uur, dus die werd afgestempeld op het moment van de eerste tick; 24 uur later viel het volgende moment op precies dat verkeerde tijdstip. Een taak stempelt nu alleen af bij True. De backup-taak draait op een kort interval en beslist zelf, zodat een gewijzigde planning meteen werkt. BACKUP_SCHEDULE_DAY toegevoegd voor wekelijks. Verder: - Zoekveld en tagfilters in de app store; repo's zonder treffers vallen weg. - Opstartcontrole op de meegeleverde front-end-bestanden: mislukt het downloaden tijdens de image-build, dan laadde de UI zonder opmaak terwijl de server prima leek te draaien. Nu een duidelijke melding in het log. - 227 tests (15 nieuwe voor rollen, 15 voor de scheduler). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
173 lines
6.7 KiB
Python
173 lines
6.7 KiB
Python
"""Rollen: admin mag alles, operator beheert stacks, viewer leest alleen."""
|
|
import pytest
|
|
|
|
from conftest import login
|
|
|
|
|
|
def _maak(client, csrf, naam, rol, wachtwoord="eenlangwachtwoord"):
|
|
r = client.post("/api/auth/users",
|
|
json={"username": naam, "password": wachtwoord, "role": rol},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 200, r.get_json()
|
|
|
|
|
|
def _login_als(client, naam, wachtwoord="eenlangwachtwoord") -> str:
|
|
r = client.post("/api/auth/login", json={"username": naam, "password": wachtwoord})
|
|
assert r.status_code == 200, r.get_json()
|
|
return r.get_json()["csrf_token"]
|
|
|
|
|
|
def test_eerste_account_is_beheerder(client):
|
|
login(client)
|
|
assert client.get("/api/auth/me").get_json()["role"] == "admin"
|
|
|
|
|
|
def test_bestaand_account_zonder_rol_geldt_als_beheerder(client, env):
|
|
"""Accounts van vóór deze versie hebben geen `role`-veld."""
|
|
login(client)
|
|
import core.auth as auth
|
|
a = auth.settings()
|
|
a["users"]["tester"].pop("role", None)
|
|
env["core"].patch({"AUTH": a})
|
|
assert auth.role_of("tester") == "admin"
|
|
|
|
|
|
def test_viewer_mag_lezen_maar_niets_wijzigen(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "kijker", "viewer")
|
|
kijker_csrf = _login_als(client, "kijker")
|
|
|
|
assert client.get("/api/stacks").status_code == 200
|
|
assert client.get("/api/auth/me").get_json()["role"] == "viewer"
|
|
|
|
r = client.post("/api/stacks/web/start", headers={"X-CSRF-Token": kijker_csrf})
|
|
assert r.status_code == 403
|
|
assert "rol" in r.get_json()["msg"]
|
|
|
|
|
|
def test_operator_mag_stacks_maar_geen_instellingen(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "beheerder2", "operator")
|
|
op_csrf = _login_als(client, "beheerder2")
|
|
|
|
# Stackacties mogen: 404 betekent 'route bereikt, stack bestaat niet'.
|
|
r = client.post("/api/stacks/web/start", headers={"X-CSRF-Token": op_csrf})
|
|
assert r.status_code != 403
|
|
|
|
# Instellingen niet.
|
|
for pad, methode in (("/api/settings", "put"), ("/api/networks", "post"),
|
|
("/api/auth/users", "post"), ("/api/repos", "post"),
|
|
("/api/docker/restart", "post")):
|
|
r = getattr(client, methode)(pad, json={}, headers={"X-CSRF-Token": op_csrf})
|
|
assert r.status_code == 403, f"{methode.upper()} {pad} → {r.status_code}"
|
|
|
|
|
|
def test_operator_mag_backups_maken_en_terugzetten(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "op", "operator")
|
|
op_csrf = _login_als(client, "op")
|
|
r = client.post("/api/backups/web/create", headers={"X-CSRF-Token": op_csrf})
|
|
assert r.status_code != 403
|
|
|
|
|
|
def test_iedereen_mag_eigen_wachtwoord_wijzigen(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "kijker", "viewer")
|
|
kijker_csrf = _login_als(client, "kijker")
|
|
r = client.post("/api/auth/password",
|
|
json={"current": "eenlangwachtwoord", "new": "nogeenlangwachtwoord"},
|
|
headers={"X-CSRF-Token": kijker_csrf})
|
|
assert r.status_code == 200
|
|
|
|
|
|
def test_wachtwoord_wijzigen_behoudt_de_rol(client):
|
|
"""Rechtenescalatie: het hele gebruikersrecord werd vervangen bij een
|
|
wachtwoordwijziging, waardoor de rol wegviel. Omdat een ontbrekende rol als
|
|
admin geldt (voor oude accounts), promoveerde een viewer zichzelf zo tot
|
|
beheerder."""
|
|
csrf = login(client)
|
|
_maak(client, csrf, "kijker", "viewer")
|
|
kijker_csrf = _login_als(client, "kijker")
|
|
|
|
r = client.post("/api/auth/password",
|
|
json={"current": "eenlangwachtwoord", "new": "nogeenlangwachtwoord"},
|
|
headers={"X-CSRF-Token": kijker_csrf})
|
|
assert r.status_code == 200
|
|
|
|
import core.auth as auth
|
|
assert auth.role_of("kijker") == "viewer"
|
|
assert client.get("/api/auth/me").get_json()["role"] == "viewer"
|
|
# En nog steeds geen toegang tot beheeracties.
|
|
assert client.put("/api/settings", json={},
|
|
headers={"X-CSRF-Token": kijker_csrf}).status_code == 403
|
|
|
|
|
|
def test_viewer_mag_uitloggen(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "kijker", "viewer")
|
|
kijker_csrf = _login_als(client, "kijker")
|
|
assert client.post("/api/auth/logout", json={},
|
|
headers={"X-CSRF-Token": kijker_csrf}).status_code == 200
|
|
|
|
|
|
def test_laatste_beheerder_kan_niet_gedegradeerd_worden(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "op", "operator")
|
|
r = client.put("/api/auth/users/tester/role", json={"role": "viewer"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 400
|
|
assert "laatste beheerder" in r.get_json()["msg"]
|
|
|
|
|
|
def test_laatste_beheerder_kan_niet_verwijderd_worden(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "op", "operator")
|
|
r = client.delete("/api/auth/users/tester", headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_degraderen_mag_met_een_tweede_beheerder(client):
|
|
csrf = login(client)
|
|
_maak(client, csrf, "admin2", "admin")
|
|
r = client.put("/api/auth/users/tester/role", json={"role": "operator"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 200
|
|
import core.auth as auth
|
|
assert auth.role_of("tester") == "operator"
|
|
|
|
|
|
def test_onbekende_rol_wordt_geweigerd(client):
|
|
csrf = login(client)
|
|
r = client.post("/api/auth/users",
|
|
json={"username": "x", "password": "eenlangwachtwoord",
|
|
"role": "superuser"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_proxy_gebruiker_krijgt_de_ingestelde_rol(client, env):
|
|
csrf = login(client)
|
|
client.put("/api/auth/mode",
|
|
json={"mode": "both", "proxy_header": "Remote-User",
|
|
"trusted_proxies": ["10.9.9.9"], "proxy_role": "viewer"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
client.post("/api/auth/logout", json={}, headers={"X-CSRF-Token": csrf})
|
|
|
|
r = client.get("/api/auth/me", headers={"Remote-User": "collega"},
|
|
environ_overrides={"REMOTE_ADDR": "10.9.9.9"})
|
|
assert r.get_json()["role"] == "viewer"
|
|
|
|
|
|
def test_lokaal_account_wint_van_proxy_rol(client, env):
|
|
"""Komt de proxy-naam overeen met een bestaand account, dan geldt díé rol."""
|
|
csrf = login(client)
|
|
_maak(client, csrf, "collega", "operator")
|
|
client.put("/api/auth/mode",
|
|
json={"mode": "both", "proxy_header": "Remote-User",
|
|
"trusted_proxies": ["10.9.9.9"], "proxy_role": "viewer"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
client.post("/api/auth/logout", json={}, headers={"X-CSRF-Token": csrf})
|
|
|
|
r = client.get("/api/auth/me", headers={"Remote-User": "collega"},
|
|
environ_overrides={"REMOTE_ADDR": "10.9.9.9"})
|
|
assert r.get_json()["role"] == "operator"
|