Enforce tenant member permissions
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-16 13:33:36 +01:00
parent df60be826d
commit 7aa0a4c847
22 changed files with 592 additions and 112 deletions

View File

@@ -19,6 +19,7 @@ use App\Models\Tenant;
use App\Models\User;
use App\Services\EventJoinTokenService;
use App\Support\ApiError;
use App\Support\TenantMemberPermissions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -83,6 +84,8 @@ class EventController extends Controller
public function store(EventStoreRequest $request): JsonResponse
{
TenantMemberPermissions::ensureTenantPermission($request, 'events:manage');
$tenant = $request->attributes->get('tenant');
if (! $tenant instanceof Tenant) {
$tenantId = $request->attributes->get('tenant_id');
@@ -383,6 +386,8 @@ class EventController extends Controller
);
}
TenantMemberPermissions::ensureEventPermission($request, $event, 'events:manage');
$validated = $request->validated();
if (isset($validated['event_date'])) {
@@ -586,6 +591,8 @@ class EventController extends Controller
);
}
TenantMemberPermissions::ensureEventPermission($request, $event, 'events:manage');
$event->delete();
return response()->json([

View File

@@ -11,6 +11,7 @@ use App\Models\Event;
use App\Models\GuestNotification;
use App\Models\GuestPolicySetting;
use App\Services\GuestNotificationService;
use App\Support\TenantMemberPermissions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
@@ -23,6 +24,7 @@ class EventGuestNotificationController extends Controller
public function index(Request $request, Event $event): JsonResponse
{
$this->assertEventTenant($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'guest-notifications:manage');
$limit = max(1, min(100, (int) $request->integer('limit', 25)));
@@ -38,6 +40,7 @@ class EventGuestNotificationController extends Controller
public function store(BroadcastGuestNotificationRequest $request, Event $event): JsonResponse
{
$this->assertEventTenant($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'guest-notifications:manage');
$data = $request->validated();

View File

@@ -7,6 +7,7 @@ use App\Http\Resources\Tenant\EventJoinTokenResource;
use App\Models\Event;
use App\Models\EventJoinToken;
use App\Services\EventJoinTokenService;
use App\Support\TenantMemberPermissions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -19,7 +20,7 @@ class EventJoinTokenController extends Controller
public function index(Request $request, Event $event): AnonymousResourceCollection
{
$this->authorizeEvent($request, $event);
$this->authorizeEvent($request, $event, 'join-tokens:manage');
$tokens = $event->joinTokens()
->orderByDesc('created_at')
@@ -30,7 +31,7 @@ class EventJoinTokenController extends Controller
public function store(Request $request, Event $event): JsonResponse
{
$this->authorizeEvent($request, $event);
$this->authorizeEvent($request, $event, 'join-tokens:manage');
$validated = $this->validatePayload($request);
@@ -45,7 +46,7 @@ class EventJoinTokenController extends Controller
public function update(Request $request, Event $event, EventJoinToken $joinToken): EventJoinTokenResource
{
$this->authorizeEvent($request, $event);
$this->authorizeEvent($request, $event, 'join-tokens:manage');
if ($joinToken->event_id !== $event->id) {
abort(404);
@@ -89,7 +90,7 @@ class EventJoinTokenController extends Controller
public function destroy(Request $request, Event $event, EventJoinToken $joinToken): EventJoinTokenResource
{
$this->authorizeEvent($request, $event);
$this->authorizeEvent($request, $event, 'join-tokens:manage');
if ($joinToken->event_id !== $event->id) {
abort(404);
@@ -101,13 +102,17 @@ class EventJoinTokenController extends Controller
return new EventJoinTokenResource($token);
}
private function authorizeEvent(Request $request, Event $event): void
private function authorizeEvent(Request $request, Event $event, ?string $permission = null): void
{
$tenantId = $request->attributes->get('tenant_id');
if ($event->tenant_id !== $tenantId) {
abort(404, 'Event not found');
}
if ($permission) {
TenantMemberPermissions::ensureEventPermission($request, $event, $permission);
}
}
private function validatePayload(Request $request, bool $partial = false): array

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\Event;
use App\Models\EventJoinToken;
use App\Support\JoinTokenLayoutRegistry;
use App\Support\TenantMemberPermissions;
use Dompdf\Dompdf;
use Dompdf\Options;
use Illuminate\Http\Request;
@@ -28,6 +29,7 @@ class EventJoinTokenLayoutController extends Controller
public function index(Request $request, Event $event, EventJoinToken $joinToken)
{
$this->ensureBelongsToEvent($event, $joinToken);
TenantMemberPermissions::ensureEventPermission($request, $event, 'join-tokens:manage');
$layouts = JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($event, $joinToken) {
return route('api.v1.tenant.events.join-tokens.layouts.download', [
@@ -46,6 +48,7 @@ class EventJoinTokenLayoutController extends Controller
public function download(Request $request, Event $event, EventJoinToken $joinToken, string $layout, string $format)
{
$this->ensureBelongsToEvent($event, $joinToken);
TenantMemberPermissions::ensureEventPermission($request, $event, 'join-tokens:manage');
$layoutConfig = JoinTokenLayoutRegistry::find($layout);

View File

@@ -9,6 +9,7 @@ use App\Models\Event;
use App\Models\EventMember;
use App\Models\Tenant;
use App\Models\User;
use App\Support\TenantMemberPermissions;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -22,6 +23,7 @@ class EventMemberController extends Controller
public function index(Request $request, Event $event): JsonResponse
{
$this->assertEventTenant($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage');
/** @var LengthAwarePaginator $members */
$members = $event->members()
@@ -34,6 +36,7 @@ class EventMemberController extends Controller
public function store(EventMemberInviteRequest $request, Event $event): JsonResponse
{
$this->assertEventTenant($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage');
$data = $request->validated();
$tenant = $this->resolveTenantFromRequest($request);
@@ -92,6 +95,7 @@ class EventMemberController extends Controller
public function destroy(Request $request, Event $event, EventMember $member): JsonResponse
{
$this->assertEventTenant($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage');
if ((int) $member->event_id !== (int) $event->id) {
throw ValidationException::withMessages([

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Models\Event;
use App\Support\TenantMemberPermissions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
@@ -13,6 +14,7 @@ class LiveShowLinkController extends Controller
public function show(Request $request, Event $event): JsonResponse
{
$this->authorizeEvent($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'live-show:manage');
$token = $event->ensureLiveShowToken();
@@ -24,6 +26,7 @@ class LiveShowLinkController extends Controller
public function rotate(Request $request, Event $event): JsonResponse
{
$this->authorizeEvent($request, $event);
TenantMemberPermissions::ensureEventPermission($request, $event, 'live-show:manage');
$token = $event->rotateLiveShowToken();

View File

@@ -10,6 +10,7 @@ use App\Http\Resources\Tenant\PhotoResource;
use App\Models\Event;
use App\Models\Photo;
use App\Support\ApiError;
use App\Support\TenantMemberPermissions;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -23,6 +24,7 @@ class LiveShowPhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
$liveStatus = $request->string('live_status', 'pending')->toString();
$perPage = (int) $request->input('per_page', 20);
@@ -51,6 +53,7 @@ class LiveShowPhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(
@@ -94,6 +97,7 @@ class LiveShowPhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(
@@ -146,6 +150,7 @@ class LiveShowPhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(
@@ -173,6 +178,7 @@ class LiveShowPhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(

View File

@@ -14,6 +14,7 @@ use App\Services\Packages\PackageUsageTracker;
use App\Services\Storage\EventStorageManager;
use App\Support\ApiError;
use App\Support\ImageHelper;
use App\Support\TenantMemberPermissions;
use App\Support\UploadStream;
use App\Support\WatermarkConfigResolver;
use Illuminate\Http\JsonResponse;
@@ -524,15 +525,8 @@ class PhotoController extends Controller
'alt_text' => ['sometimes', 'string', 'max:255'],
]);
// Only tenant admins can moderate
if (isset($validated['status']) && ! $this->tokenHasScope($request, 'tenant-admin')) {
return ApiError::response(
'insufficient_scope',
'Insufficient Scopes',
'You are not allowed to moderate photos for this event.',
Response::HTTP_FORBIDDEN,
['required_scope' => 'tenant-admin']
);
if (isset($validated['status'])) {
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
}
$photo->update($validated);
@@ -634,6 +628,7 @@ class PhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(
@@ -657,6 +652,7 @@ class PhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
if ($photo->event_id !== $event->id) {
return ApiError::response(
@@ -680,6 +676,7 @@ class PhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
$request->validate([
'photo_ids' => 'required|array',
@@ -725,6 +722,7 @@ class PhotoController extends Controller
$event = Event::where('slug', $eventSlug)
->where('tenant_id', $tenantId)
->firstOrFail();
TenantMemberPermissions::ensureEventPermission($request, $event, 'photos:moderate');
$request->validate([
'photo_ids' => 'required|array',

View File

@@ -11,6 +11,7 @@ use App\Models\Task;
use App\Models\TaskCollection;
use App\Models\Tenant;
use App\Support\ApiError;
use App\Support\TenantMemberPermissions;
use App\Support\TenantRequestResolver;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -66,6 +67,8 @@ class TaskController extends Controller
*/
public function store(TaskStoreRequest $request): JsonResponse
{
TenantMemberPermissions::ensureTenantPermission($request, 'tasks:manage');
$tenant = $this->currentTenant($request);
$collectionId = $request->input('collection_id');
$collection = $collectionId ? $this->resolveAccessibleCollection($request, $collectionId) : null;
@@ -107,6 +110,8 @@ class TaskController extends Controller
*/
public function update(TaskUpdateRequest $request, Task $task): JsonResponse
{
TenantMemberPermissions::ensureTenantPermission($request, 'tasks:manage');
$tenant = $this->currentTenant($request);
if ($task->tenant_id !== $tenant->id) {
@@ -138,6 +143,8 @@ class TaskController extends Controller
*/
public function destroy(Request $request, Task $task): JsonResponse
{
TenantMemberPermissions::ensureTenantPermission($request, 'tasks:manage');
if ($task->tenant_id !== $this->currentTenant($request)->id) {
abort(404, 'Task nicht gefunden.');
}
@@ -154,6 +161,8 @@ class TaskController extends Controller
*/
public function assignToEvent(Request $request, Task $task, Event $event): JsonResponse
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
if (($task->tenant_id && $task->tenant_id !== $tenantId) || $event->tenant_id !== $tenantId) {
@@ -176,6 +185,8 @@ class TaskController extends Controller
*/
public function bulkAssignToEvent(Request $request, Event $event): JsonResponse
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
if ($event->tenant_id !== $tenantId) {
@@ -230,6 +241,8 @@ class TaskController extends Controller
public function bulkDetachFromEvent(Request $request, Event $event): JsonResponse
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
if ($event->tenant_id !== $tenantId) {
@@ -256,6 +269,8 @@ class TaskController extends Controller
public function reorderForEvent(Request $request, Event $event): JsonResponse
{
TenantMemberPermissions::ensureEventPermission($request, $event, 'tasks:manage');
$tenantId = $this->currentTenant($request)->id;
if ($event->tenant_id !== $tenantId) {