refactor: move workspace tenancy check on Post into PostPolicy
The same "is this post in the user's current workspace?" check was
duplicated across every Post-related endpoint (5 in Api/PostController
via the ensurePostInCurrentWorkspace helper, 5 in App/PostController
inline). PostPolicy already had a duplicate() method following this
exact pattern — extending it with view/update/delete unifies the
tenancy guard in one place.
- Add view/update/delete to PostPolicy. Each returns
Response::denyAsNotFound() when the post belongs to a different
workspace, so we keep the existing 404 behavior (don't leak
cross-tenant existence) instead of switching to the default 403.
- Update duplicate() to also use denyAsNotFound() for the workspace
mismatch path. The createPost role check still returns bool/403.
- Replace ensurePostInCurrentWorkspace() calls in Api/PostController
with $this->authorize('view'|'update'|'delete', $post). Helper deleted.
- Replace inline workspace_id !== $workspace->id checks in
App/PostController (show/edit/update/destroy/platformMetrics) with
the same authorize calls. The PostPolicy guard now subsumes both
the workspace-tenancy check and the role-permission check that was
previously delegated through Workspace::createPost.
This commit is contained in:
parent
6949ee4ec3
commit
3b96a9ebdb
3 changed files with 56 additions and 45 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue