diff --git a/app/Http/Controllers/Api/PostController.php b/app/Http/Controllers/Api/PostController.php index e2f6ab17..2ef5eee8 100644 --- a/app/Http/Controllers/Api/PostController.php +++ b/app/Http/Controllers/Api/PostController.php @@ -35,7 +35,7 @@ public function index(Request $request): AnonymousResourceCollection public function show(Request $request, Post $post): PostResource { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('view', $post); $post->load(['postPlatforms.socialAccount', 'user', 'labels']); @@ -59,7 +59,7 @@ public function store(StorePostRequest $request): JsonResponse public function update(UpdatePostRequest $request, Post $post): PostResource|JsonResponse { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('update', $post); $result = UpdatePost::execute($request->user()->currentWorkspace, $post, $request->validated()); @@ -75,7 +75,7 @@ public function update(UpdatePostRequest $request, Post $post): PostResource|Jso public function destroy(Request $request, Post $post): JsonResponse { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('delete', $post); DeletePost::execute($post); @@ -84,7 +84,7 @@ public function destroy(Request $request, Post $post): JsonResponse public function attachMedia(Request $request, Post $post): PostMediaAttachResource { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('update', $post); $validated = $request->validate([ 'urls' => ['required', 'array', 'min:1', 'max:10'], @@ -100,7 +100,7 @@ public function attachMedia(Request $request, Post $post): PostMediaAttachResour public function metrics(Request $request, Post $post): PostMetricsResource { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('view', $post); $post->load(['postPlatforms.socialAccount']); @@ -109,17 +109,10 @@ public function metrics(Request $request, Post $post): PostMetricsResource public function preview(Request $request, Post $post): PostPreviewResource { - $this->ensurePostInCurrentWorkspace($request, $post); + $this->authorize('view', $post); $post->load(['postPlatforms.socialAccount']); return new PostPreviewResource($post); } - - private function ensurePostInCurrentWorkspace(Request $request, Post $post): void - { - if ($post->workspace_id !== $request->user()->currentWorkspace->id) { - abort(Response::HTTP_NOT_FOUND); - } - } } diff --git a/app/Http/Controllers/App/PostController.php b/app/Http/Controllers/App/PostController.php index afeba1df..b2b62409 100644 --- a/app/Http/Controllers/App/PostController.php +++ b/app/Http/Controllers/App/PostController.php @@ -165,13 +165,7 @@ public function store(Request $request): RedirectResponse|\Symfony\Component\Htt public function platformMetrics(Request $request, Post $post, PostPlatform $postPlatform): JsonResponse { - $workspace = $request->user()->currentWorkspace; - - if (! $workspace || $post->workspace_id !== $workspace->id) { - abort(404); - } - - $this->authorize('view', $workspace); + $this->authorize('view', $post); if ($postPlatform->post_id !== $post->id) { abort(404); @@ -188,11 +182,7 @@ public function show(Request $request, Post $post): Response|RedirectResponse return redirect()->route('app.workspaces.create'); } - $this->authorize('view', $workspace); - - if ($post->workspace_id !== $workspace->id) { - abort(404); - } + $this->authorize('view', $post); if (in_array($post->status, [PostStatus::Draft, PostStatus::Scheduled, PostStatus::Failed], true)) { return redirect()->route('app.posts.edit', $post); @@ -214,11 +204,7 @@ public function edit(Request $request, Post $post): Response|RedirectResponse return redirect()->route('app.workspaces.create'); } - $this->authorize('view', $workspace); - - if ($post->workspace_id !== $workspace->id) { - abort(404); - } + $this->authorize('update', $post); if (in_array($post->status, [PostStatus::Publishing, PostStatus::Published, PostStatus::PartiallyPublished], true)) { return redirect()->route('app.posts.show', $post); @@ -274,11 +260,7 @@ public function update(UpdatePostRequest $request, Post $post): RedirectResponse return redirect()->route('app.workspaces.create'); } - $this->authorize('createPost', $workspace); - - if ($post->workspace_id !== $workspace->id) { - abort(404); - } + $this->authorize('update', $post); $result = UpdatePost::execute($workspace, $post, $request->validated()); @@ -316,11 +298,7 @@ public function destroy(Request $request, Post $post): RedirectResponse return redirect()->route('app.workspaces.create'); } - $this->authorize('createPost', $workspace); - - if ($post->workspace_id !== $workspace->id) { - abort(404); - } + $this->authorize('delete', $post); if (in_array($post->status, [PostStatus::Publishing, PostStatus::Published, PostStatus::PartiallyPublished], true)) { session()->flash('flash.banner', __('posts.flash.cannot_delete_published')); diff --git a/app/Policies/PostPolicy.php b/app/Policies/PostPolicy.php index 9c41bd74..c89f8267 100644 --- a/app/Policies/PostPolicy.php +++ b/app/Policies/PostPolicy.php @@ -6,18 +6,58 @@ use App\Models\Post; use App\Models\User; +use Illuminate\Auth\Access\Response; class PostPolicy { /** - * Authorize duplicating a post into the user's current workspace as a - * fresh draft. The post must live in the user's current workspace and - * the user must have permission to create posts there. + * Authorize viewing a post. The post must live in the user's current + * workspace; cross-workspace lookups deny as 404 so we don't leak + * existence across tenants. */ - public function duplicate(User $user, Post $post): bool + public function view(User $user, Post $post): bool|Response { if ($post->workspace_id !== $user->current_workspace_id) { - return false; + return Response::denyAsNotFound(); + } + + return true; + } + + /** + * Authorize updating a post. Same workspace-tenancy guard as `view`. + */ + public function update(User $user, Post $post): bool|Response + { + if ($post->workspace_id !== $user->current_workspace_id) { + return Response::denyAsNotFound(); + } + + return true; + } + + /** + * Authorize deleting a post. Same workspace-tenancy guard as `view`. + */ + public function delete(User $user, Post $post): bool|Response + { + if ($post->workspace_id !== $user->current_workspace_id) { + return Response::denyAsNotFound(); + } + + return true; + } + + /** + * Authorize duplicating a post into the user's current workspace as a + * fresh draft. The post must live in the user's current workspace + * (404 otherwise — tenancy guard, see `view()`) AND the user must + * have permission to create posts there (403 otherwise). + */ + public function duplicate(User $user, Post $post): bool|Response + { + if ($post->workspace_id !== $user->current_workspace_id) { + return Response::denyAsNotFound(); } return $user->can('createPost', $user->currentWorkspace);