Files
fotospiel-app/app/Support/TenantMemberPermissions.php
Codex Agent 7aa0a4c847
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Enforce tenant member permissions
2026-01-16 13:33:36 +01:00

175 lines
5.0 KiB
PHP

<?php
namespace App\Support;
use App\Models\Event;
use App\Models\EventMember;
use App\Models\User;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class TenantMemberPermissions
{
/**
* @return array<int, string>
*/
public static function resolveEventPermissions(Request $request, Event $event): array
{
$user = $request->user();
if (! $user instanceof User) {
return [];
}
if (self::isTenantAdmin($user)) {
return ['*'];
}
$member = self::resolveEventMember($user, $event);
if (! $member) {
return [];
}
return self::normalizePermissions($member->permissions);
}
public static function ensureEventPermission(Request $request, Event $event, string $permission): void
{
if (self::allowsPermission(self::resolveEventPermissions($request, $event), $permission)) {
return;
}
throw new HttpResponseException(ApiError::response(
'insufficient_permission',
'Insufficient permission',
'You are not allowed to perform this action.',
Response::HTTP_FORBIDDEN,
['required_permission' => $permission]
));
}
public static function allowsEventPermission(Request $request, Event $event, string $permission): bool
{
return self::allowsPermission(self::resolveEventPermissions($request, $event), $permission);
}
public static function ensureTenantPermission(Request $request, string $permission): void
{
$user = $request->user();
if (! $user instanceof User) {
throw new HttpResponseException(ApiError::response(
'unauthenticated',
'Unauthenticated',
'You must be authenticated to perform this action.',
Response::HTTP_UNAUTHORIZED
));
}
if (self::isTenantAdmin($user)) {
return;
}
$permissions = self::resolveTenantMemberPermissions($user);
if (self::allowsPermission($permissions, $permission)) {
return;
}
throw new HttpResponseException(ApiError::response(
'insufficient_permission',
'Insufficient permission',
'You are not allowed to perform this action.',
Response::HTTP_FORBIDDEN,
['required_permission' => $permission]
));
}
/**
* @return array<int, string>
*/
private static function resolveTenantMemberPermissions(User $user): array
{
if (! $user->tenant_id) {
return [];
}
$memberships = EventMember::query()
->where('tenant_id', $user->tenant_id)
->whereIn('status', ['active', 'invited'])
->where(function ($query) use ($user) {
$query->where('user_id', $user->id)
->orWhere('email', $user->email);
})
->get(['permissions']);
$permissions = [];
foreach ($memberships as $member) {
$permissions = array_merge($permissions, self::normalizePermissions($member->permissions));
}
return array_values(array_unique($permissions));
}
private static function isTenantAdmin(User $user): bool
{
return in_array($user->role, ['tenant_admin', 'admin', 'super_admin', 'superadmin'], true);
}
private static function resolveEventMember(User $user, Event $event): ?EventMember
{
return EventMember::query()
->where('tenant_id', $event->tenant_id)
->where('event_id', $event->id)
->whereIn('status', ['active', 'invited'])
->where(function ($query) use ($user) {
$query->where('user_id', $user->id)
->orWhere('email', $user->email);
})
->first();
}
/**
* @param array<int, string>|string|null $permissions
* @return array<int, string>
*/
private static function normalizePermissions(mixed $permissions): array
{
if (is_array($permissions)) {
return array_values(array_filter(array_map('strval', $permissions)));
}
if (is_string($permissions) && $permissions !== '') {
return array_values(array_filter(array_map('trim', explode(',', $permissions))));
}
return [];
}
/**
* @param array<int, string> $permissions
*/
private static function allowsPermission(array $permissions, string $permission): bool
{
foreach ($permissions as $entry) {
if ($entry === '*' || $entry === $permission) {
return true;
}
if (Str::endsWith($entry, ':*')) {
$prefix = Str::beforeLast($entry, '*');
if (Str::startsWith($permission, $prefix)) {
return true;
}
}
}
return false;
}
}