// @ts-check // Fix #193: SettingsController::update()/mailTest() verlangten kein // Auth::requireAdmin() – jeder eingeloggte Benutzer konnte mail_smtp_host // setzen und per "SMTP testen" sofort einen Verbindungsversuch des Servers // zu einem beliebigen Ziel auslösen (Ausgangspunkt für DNS-Rebinding/SSRF). // Dieser Test prüft die Durchsetzung auf HTTP-Ebene, die die PHP-Suite nicht // erreicht (Router-Guard, Auth::requireAdmin()). const { test, expect } = require('@playwright/test') const { uniqueTag } = require('../helpers/app') test.describe('Autorisierung: SMTP-Einstellungen nur für Admins (Fix #193)', () => { test('Nicht-Admin wird von POST /settings und /settings/mail-test ausgesperrt, Admin nicht', async ({ page, browser }) => { const username = uniqueTag('noadmin').toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 64) const password = 'TempPass123!' await page.goto('/useradmin') await page.fill('#field-username', username) await page.fill('#field-password', password) await page.click('#submit-btn') await page.waitForURL('/useradmin') const row = page.locator('tr', { hasText: username }) await expect(row).toBeVisible() try { 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')) // GET /settings bleibt für alle eingeloggten Benutzer erreichbar // (Firmendaten sind kein Admin-Feature) – nur die SMTP-mutierenden // Endpunkte werden geschützt. await userPage.goto('/settings') const csrfToken = await userPage.evaluate(() => document.querySelector('meta[name="csrf-token"]')?.content || '') expect(csrfToken).not.toBe('') const mailTestResponse = await userPage.request.post('/settings/mail-test', { headers: { 'X-CSRF-Token': csrfToken }, form: {}, }) expect(mailTestResponse.status()).toBe(403) await expect(mailTestResponse.text()).resolves.toContain('Kein Zugriff') const updateResponse = await userPage.request.post('/settings', { headers: { 'X-CSRF-Token': csrfToken }, form: { firma: 'Sollte nicht gespeichert werden', strasse: 'X', plz: '12345', ort: 'X', mail_smtp_host: '127.0.0.1' }, }) expect(updateResponse.status()).toBe(403) await expect(updateResponse.text()).resolves.toContain('Kein Zugriff') await context.close() } finally { const delForm = row.locator('form[action$="/delete"]') if (await delForm.count() > 0) { await delForm.locator('button[type="submit"]').click() await page.waitForLoadState('networkidle') } } }) test('Admin darf /settings/mail-test weiterhin aufrufen (kein 403)', async ({ page }) => { await page.goto('/settings') const csrfToken = await page.evaluate(() => document.querySelector('meta[name="csrf-token"]')?.content || '') expect(csrfToken).not.toBe('') const response = await page.request.post('/settings/mail-test', { headers: { 'X-CSRF-Token': csrfToken }, form: {}, }) // Egal ob die SMTP-Verbindung selbst gelingt oder scheitert (abhängig von // den hinterlegten Test-Einstellungen) – der Admin-Zugriff selbst darf // nicht mit 403 verweigert werden. expect(response.status()).not.toBe(403) }) })