Steuerkram/tests/e2e/specs/useradmin-authorization.spec.js

54 lines
2.3 KiB
JavaScript

// @ts-check
// Fix #147: Vor der Änderung konnte jeder eingeloggte Benutzer über
// /useradmin beliebige Konten anlegen, Passwörter setzen oder löschen
// (Privilege Escalation). Dieser Test prüft die Durchsetzung auf HTTP-Ebene,
// die die PHP-Suite nicht erreicht (Router-Guard, Redirect, Nav-Rendering).
const { test, expect } = require('@playwright/test')
const { uniqueTag, gotoOk, acceptDialogs } = require('../helpers/app')
test.describe('Autorisierung: Benutzerverwaltung nur für Admins (Fix #147)', () => {
test('Neu angelegter Benutzer ohne Admin-Rolle wird von /useradmin ausgesperrt', async ({ page, browser }) => {
const username = uniqueTag('noadmin').toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 64)
const password = 'TempPass123!'
await gotoOk(page, '/useradmin')
await page.fill('#field-username', username)
await page.fill('#field-password', password)
await page.click('#submit-btn')
await page.waitForURL('/useradmin')
await expect(page.locator('body')).toContainText('Benutzer angelegt.')
const row = page.locator('tr', { hasText: username })
await expect(row).toBeVisible()
try {
// Eigener, unangemeldeter Kontext: Der neu angelegte Benutzer meldet
// sich selbst an und ist per Default kein Admin.
const context = await browser.newContext({ storageState: { cookies: [], origins: [] } })
const userPage = await context.newPage()
await userPage.goto('/login')
await userPage.fill('#username', username)
await userPage.fill('#password', password)
await userPage.click('form[action="/login"] button[type="submit"]')
await userPage.waitForURL(url => !url.pathname.startsWith('/login'))
// Kosmetischer Schutz: der Nav-Link ist für Nicht-Admins ausgeblendet.
await expect(userPage.locator('a[href="/useradmin"]')).toHaveCount(0)
// Serverseitige Durchsetzung: direkter Aufruf bleibt trotzdem gesperrt.
const response = await userPage.goto('/useradmin')
expect(response?.status()).toBe(403)
await expect(userPage.locator('body')).toContainText('Kein Zugriff')
await context.close()
} finally {
acceptDialogs(page)
const delForm = row.locator('form[action$="/delete"]')
if (await delForm.count() > 0) {
await delForm.locator('button[type="submit"]').click()
await page.waitForLoadState('networkidle')
}
}
})
})