Rollen, die Zugriff haben sollen. Alle anderen Rollen muessen // abgewiesen werden. Muss mit den saas_user_has_role()-Aufrufen in den // jeweiligen Dateien uebereinstimmen. $pages = [ 'kaffeeliste.php' => ['owner', 'admin', 'treasurer'], 'mitarbeiterverwalten.php' => ['owner', 'admin'], 'hinweise.php' => ['owner', 'admin'], 'stricheintragen.php' => ['owner', 'admin', 'treasurer'], 'einzahlung.php' => ['owner', 'admin', 'treasurer'], 'letzteneintraege.php' => ['owner', 'admin', 'treasurer'], 'csvupload.php' => ['owner', 'admin', 'treasurer'], 'exportKaffeeliste.php' => ['owner', 'admin', 'treasurer'], 'mailversenden.php' => ['owner', 'admin', 'treasurer'], 'jahresauswertung.php' => ['owner', 'admin', 'treasurer'], 'mandant-einstellungen.php' => ['owner', 'admin'], ]; $denialMarkers = ['kein zugriff', 'keinen zugang', 'keine berechtigung']; function m8rm_looks_denied(string $body): bool { global $denialMarkers; $lower = strtolower($body); foreach ($denialMarkers as $marker) { if (str_contains($lower, $marker)) { return true; } } return false; } /** * Minimaler manueller Cookie-Jar auf Basis von file_get_contents, da diese * PHP-Installation keine curl-Extension hat. $cookies wird per Referenz * aktualisiert, sobald die Antwort Set-Cookie-Header enthaelt (insbesondere * nach dem Login, der die Session-ID per session_regenerate_id() erneuert). * * @param array $cookies * @return array{status:int, body:string} */ function m8rm_request(string $url, array &$cookies, string $method = 'GET', ?string $postBody = null): array { $cookieHeader = ''; if ($cookies !== []) { $pairs = []; foreach ($cookies as $name => $value) { $pairs[] = "{$name}={$value}"; } $cookieHeader = 'Cookie: ' . implode('; ', $pairs) . "\r\n"; } $headers = "User-Agent: KaffeelisteRoleMatrix/1.0\r\n" . $cookieHeader; if ($postBody !== null) { $headers .= "Content-Type: application/x-www-form-urlencoded\r\n"; } $context = stream_context_create([ 'http' => [ 'method' => $method, 'header' => $headers, 'content' => $postBody, 'timeout' => 15, 'ignore_errors' => true, 'follow_location' => 0, ], ]); $body = @file_get_contents($url, false, $context); $responseHeaders = $http_response_header ?? []; $status = 0; foreach ($responseHeaders as $header) { if (preg_match('~^HTTP/\S+\s+(\d{3})~', $header, $m) === 1) { $status = (int)$m[1]; } if (preg_match('/^Set-Cookie:\s*([^=;]+)=([^;]+)/i', $header, $m) === 1) { $cookies[$m[1]] = $m[2]; } } return ['status' => $status, 'body' => (string)$body]; } $tenantId = null; $userIds = []; try { $stmt = $pdo->prepare('INSERT INTO tenants (slug, name, status) VALUES (?, ?, ?)'); $stmt->execute(["rolematrix-{$suffix}", 'Rollenmatrix Tenant', 'active']); $tenantId = (int)$pdo->lastInsertId(); $pdo->prepare('INSERT INTO tenant_settings (tenant_id) VALUES (?)')->execute([$tenantId]); foreach ($roles as $role) { $email = "rolematrix-{$role}-{$suffix}@test.local"; $stmt = $pdo->prepare( 'INSERT INTO users (email, email_norm, display_name, password_hash, status) VALUES (?, ?, ?, ?, ?)' ); $stmt->execute([$email, $email, "Rolematrix {$role}", password_hash($password, PASSWORD_DEFAULT), 'active']); $userId = (int)$pdo->lastInsertId(); $userIds[$role] = $userId; $pdo->prepare( 'INSERT INTO tenant_memberships (tenant_id, user_id, role, status, joined_at) VALUES (?, ?, ?, ?, NOW())' )->execute([$tenantId, $userId, $role, 'active']); $pdo->prepare( 'INSERT INTO participants (tenant_id, user_id, display_name, email, email_norm, active) VALUES (?, ?, ?, ?, ?, 1)' )->execute([$tenantId, $userId, "Rolematrix {$role}", $email, $email]); } } catch (Throwable $e) { fwrite(STDERR, "M8 role matrix setup failed: {$e->getMessage()}\n"); exit(1); } $failures = []; $passes = 0; foreach ($roles as $role) { $cookies = []; $loginPage = m8rm_request("{$baseUrl}/login.php", $cookies); preg_match('/name="csrf_token" value="([^"]+)"/', $loginPage['body'], $matches); $csrf = $matches[1] ?? ''; $postBody = http_build_query([ 'csrf_token' => $csrf, 'email' => "rolematrix-{$role}-{$suffix}@test.local", 'password' => $password, 'tenant_slug' => "rolematrix-{$suffix}", ]); m8rm_request("{$baseUrl}/login.php", $cookies, 'POST', $postBody); foreach ($pages as $page => $allowedRoles) { $response = m8rm_request("{$baseUrl}/{$page}", $cookies); $shouldHaveAccess = in_array($role, $allowedRoles, true); $looksGranted = !m8rm_looks_denied($response['body']); $label = "{$role} auf {$page}"; if ($shouldHaveAccess === $looksGranted) { $passes++; echo "PASS {$label}\n"; } else { $failures[] = $shouldHaveAccess ? "{$label}: Zugriff erwartet, aber verweigert" : "{$label}: Zugriff verweigert erwartet, aber gewährt"; echo "FAIL {$label}\n"; } } } // Aufraeumen $pdo->prepare('DELETE FROM participants WHERE tenant_id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM tenant_memberships WHERE tenant_id = ?')->execute([$tenantId]); foreach ($userIds as $uid) { $pdo->prepare('DELETE FROM user_auth_tokens WHERE user_id = ?')->execute([$uid]); $pdo->prepare('DELETE FROM users WHERE id = ?')->execute([$uid]); } $pdo->prepare('DELETE FROM tenant_settings WHERE tenant_id = ?')->execute([$tenantId]); $pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tenantId]); if ($failures !== []) { echo "\nM8 role matrix check failed with " . count($failures) . " failure(s):\n"; foreach ($failures as $failure) { echo "- {$failure}\n"; } exit(1); } echo "\nM8 role matrix check passed with {$passes} assertions.\n";