From 77c4d6fea7f97ae97035c5d9f8a1f5c14f09aa8a Mon Sep 17 00:00:00 2001 From: sonali-dudhia Date: Thu, 29 Oct 2026 12:05:55 +0530 Subject: [PATCH 1/2] Let staff with plugin view permission preview unlisted plugins The "View Listing Page" button on the plugin admin page links to the public plugin page, which only let super admins and the owner see a plugin that isn't approved and active. Staff whose role grants View:Plugin saw the button but got a 404. Check the plugin view permission instead, on both the plugin page and its license page. Super admins keep access through the gate; customers without the permission still get a 404. Co-Authored-By: Claude Opus 5.5 --- .../Controllers/PluginDirectoryController.php | 20 +++--- tests/Feature/AdminPluginPreviewTest.php | 65 +++++++++++++++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/PluginDirectoryController.php b/app/Http/Controllers/PluginDirectoryController.php index 6ffc3d3df..96e994a70 100644 --- a/app/Http/Controllers/PluginDirectoryController.php +++ b/app/Http/Controllers/PluginDirectoryController.php @@ -54,13 +54,13 @@ public function show(string $vendor, string $package): View $user = Auth::user(); - $isAdmin = $user?->isAdmin() ?? false; + $isStaff = $user?->can('view', $plugin) ?? false; $isOwner = $user && $plugin->user_id === $user->id; - abort_unless(($plugin->isApproved() && $plugin->is_active) || $isAdmin || $isOwner, 404); + abort_unless(($plugin->isApproved() && $plugin->is_active) || $isStaff || $isOwner, 404); - // For paid plugins, check if user has an accessible price (admins and owners bypass) - if (! $isAdmin && ! $isOwner && $plugin->isPaid() && ! $plugin->hasAccessiblePriceFor($user)) { + // For paid plugins, check if user has an accessible price (staff and owners bypass) + if (! $isStaff && ! $isOwner && $plugin->isPaid() && ! $plugin->hasAccessiblePriceFor($user)) { abort(404); } @@ -81,7 +81,7 @@ public function show(string $vendor, string $package): View 'regularPrice' => $regularPrice, 'ultraOfferPrice' => $user?->hasActiveUltraSubscription() ? null : $plugin->getUltraPrice(), 'hasDiscount' => $bestPrice && $regularPrice && $bestPrice->id !== $regularPrice->id, - 'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isAdmin || $isOwner), + 'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isStaff || $isOwner), ]); } @@ -91,15 +91,15 @@ public function license(string $vendor, string $package): View $user = Auth::user(); - $isAdmin = $user?->isAdmin() ?? false; + $isStaff = $user?->can('view', $plugin) ?? false; $isOwner = $user && $plugin->user_id === $user->id; abort_unless($plugin->isPaid(), 404); abort_unless($plugin->license_html, 404); - abort_unless(($plugin->isApproved() && $plugin->is_active) || $isAdmin || $isOwner, 404); + abort_unless(($plugin->isApproved() && $plugin->is_active) || $isStaff || $isOwner, 404); - // For paid plugins, check if user has an accessible price (admins and owners bypass) - if (! $isAdmin && ! $isOwner && ! $plugin->hasAccessiblePriceFor($user)) { + // For paid plugins, check if user has an accessible price (staff and owners bypass) + if (! $isStaff && ! $isOwner && ! $plugin->hasAccessiblePriceFor($user)) { abort(404); } @@ -107,7 +107,7 @@ public function license(string $vendor, string $package): View return view('plugin-license', [ 'plugin' => $plugin, - 'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isAdmin || $isOwner), + 'isAdminPreview' => (! $plugin->isApproved() || ! $plugin->is_active) && ($isStaff || $isOwner), ]); } diff --git a/tests/Feature/AdminPluginPreviewTest.php b/tests/Feature/AdminPluginPreviewTest.php index 981253ee6..b867104de 100644 --- a/tests/Feature/AdminPluginPreviewTest.php +++ b/tests/Feature/AdminPluginPreviewTest.php @@ -7,6 +7,8 @@ use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Laravel\Pennant\Feature; +use Spatie\Permission\Models\Permission; +use Spatie\Permission\Models\Role; use Tests\TestCase; class AdminPluginPreviewTest extends TestCase @@ -83,4 +85,67 @@ public function test_admin_can_view_approved_plugin_without_preview_banner(): vo ->assertStatus(200) ->assertDontSee('This plugin is not publicly visible'); } + + public function test_staff_with_plugin_view_permission_can_preview_pending_plugin(): void + { + $plugin = Plugin::factory()->pending()->create(); + + $this->actingAs($this->staffWithPermissions(['ViewAny:Plugin', 'View:Plugin'])) + ->get(route('plugins.show', $plugin->routeParams())) + ->assertOk() + ->assertSee('This plugin is not publicly visible.'); + } + + public function test_staff_with_plugin_view_permission_can_preview_paid_pending_plugin_and_its_license(): void + { + $plugin = Plugin::factory()->pending()->paid()->create(['license_html' => '

License terms

']); + $staff = $this->staffWithPermissions(['ViewAny:Plugin', 'View:Plugin']); + + $this->actingAs($staff) + ->get(route('plugins.show', $plugin->routeParams())) + ->assertOk(); + + $this->actingAs($staff) + ->get(route('plugins.license', $plugin->routeParams())) + ->assertOk() + ->assertSee('License terms', false); + } + + public function test_staff_without_plugin_view_permission_cannot_preview_pending_plugin(): void + { + $plugin = Plugin::factory()->pending()->create(); + + $this->actingAs($this->staffWithPermissions(['ViewAny:Lead'])) + ->get(route('plugins.show', $plugin->routeParams())) + ->assertNotFound(); + } + + public function test_super_admin_role_can_preview_pending_plugin(): void + { + $superAdmin = User::factory()->create(); + $superAdmin->assignRole(Role::findOrCreate(config('filament-shield.super_admin.name'))); + + $plugin = Plugin::factory()->pending()->create(); + + $this->actingAs($superAdmin) + ->get(route('plugins.show', $plugin->routeParams())) + ->assertOk(); + } + + /** + * @param list $permissions + */ + private function staffWithPermissions(array $permissions): User + { + $role = Role::findOrCreate('test_role'); + + foreach ($permissions as $permission) { + $role->givePermissionTo(Permission::findOrCreate($permission)); + } + + $user = User::factory()->create(); + $user->assignRole($role); + + return $user; + } } From 94f337b48bdf16a968dfa1003ea26de0d5888472 Mon Sep 17 00:00:00 2001 From: sonali-dudhia Date: Thu, 29 Oct 2026 12:18:30 +0530 Subject: [PATCH 2/2] Use role permissions wherever staff were checked as super admins After roles shipped, several places still only let super admins in, so staff with the right permission saw a button or link and then got a 404 or 403: - Article "Preview" on drafts now needs View:Article - Support ticket attachments open for anyone who can view the ticket - Replies from any staff member get the "Staff" badge, via a new User::isStaff() Links from one admin section to another (user, subscription, payout, plugin, course module) are now hidden when the role can't open the target, instead of leading to a 403. While there, fix the user link on subscription items and the wall of love "user" action, which passed the wrong record id. Co-Authored-By: Claude Opus 5.5 --- app/Filament/Pages/Companies.php | 1 + .../ModulesRelationManager.php | 2 +- .../OpenCollectiveDonationResource.php | 2 +- app/Filament/Resources/PluginResource.php | 2 +- .../Resources/SubscriptionItemResource.php | 2 +- .../Pages/ViewSubscriptionItem.php | 4 +- .../Pages/ViewSubscription.php | 2 +- .../Resources/SupportTicketResource.php | 2 +- .../Resources/ThirdPartySaleResource.php | 2 +- .../DeveloperPluginsRelationManager.php | 2 +- .../Pages/EditWallOfLoveSubmission.php | 5 +- app/Http/Controllers/ShowBlogController.php | 2 +- .../SupportTicketAttachmentController.php | 4 +- app/Models/User.php | 8 ++ .../widgets/ticket-replies.blade.php | 6 +- .../Filament/AdminRolesAndPermissionsTest.php | 76 +++++++++++++++++++ 16 files changed, 103 insertions(+), 19 deletions(-) diff --git a/app/Filament/Pages/Companies.php b/app/Filament/Pages/Companies.php index a06278a87..1342bc585 100644 --- a/app/Filament/Pages/Companies.php +++ b/app/Filament/Pages/Companies.php @@ -101,6 +101,7 @@ public function table(Table $table): Table ->label('Users') ->icon('heroicon-o-users') ->color('gray') + ->visible(fn (): bool => UserResource::canViewAny()) ->url(fn (array $record): string => UserResource::getUrl('index', [ 'tableSearch' => $record['domain'], ])), diff --git a/app/Filament/Resources/CourseResource/RelationManagers/ModulesRelationManager.php b/app/Filament/Resources/CourseResource/RelationManagers/ModulesRelationManager.php index 6ea944a92..3736655be 100644 --- a/app/Filament/Resources/CourseResource/RelationManagers/ModulesRelationManager.php +++ b/app/Filament/Resources/CourseResource/RelationManagers/ModulesRelationManager.php @@ -71,7 +71,7 @@ public function table(Table $table): Table ]) ->defaultSort('sort_order') ->reorderable('sort_order') - ->recordUrl(fn ($record) => CourseModuleResource::getUrl('edit', ['record' => $record])) + ->recordUrl(fn ($record) => CourseModuleResource::can('update', $record) ? CourseModuleResource::getUrl('edit', ['record' => $record]) : null) ->headerActions([ Actions\CreateAction::make(), ]) diff --git a/app/Filament/Resources/OpenCollectiveDonationResource.php b/app/Filament/Resources/OpenCollectiveDonationResource.php index 4558a24fb..fc8aff9bf 100644 --- a/app/Filament/Resources/OpenCollectiveDonationResource.php +++ b/app/Filament/Resources/OpenCollectiveDonationResource.php @@ -90,7 +90,7 @@ public static function infolist(Schema $schema): Schema Infolists\Components\TextEntry::make('user.email') ->label('Claimed By') ->placeholder('Not claimed') - ->url(fn ($record) => $record->user_id ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null), + ->url(fn ($record) => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null), ]), ]); } diff --git a/app/Filament/Resources/PluginResource.php b/app/Filament/Resources/PluginResource.php index 2de1aba46..5848c3dc3 100644 --- a/app/Filament/Resources/PluginResource.php +++ b/app/Filament/Resources/PluginResource.php @@ -344,7 +344,7 @@ public static function form(Schema $schema): Schema ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null) ->openUrlInNewTab() - ->visible(fn (?Plugin $record) => $record?->user_id !== null), + ->visible(fn (?Plugin $record) => $record?->user !== null && UserResource::can('update', $record->user)), ), Forms\Components\Placeholder::make('created_at') diff --git a/app/Filament/Resources/SubscriptionItemResource.php b/app/Filament/Resources/SubscriptionItemResource.php index 235e3f474..160954ce5 100644 --- a/app/Filament/Resources/SubscriptionItemResource.php +++ b/app/Filament/Resources/SubscriptionItemResource.php @@ -62,7 +62,7 @@ public static function table(Table $table): Table Tables\Columns\TextColumn::make('subscription.id') ->numeric() ->sortable() - ->url(fn ($record) => SubscriptionResource::getUrl('view', ['record' => $record->subscription])), + ->url(fn ($record) => $record->subscription && SubscriptionResource::can('view', $record->subscription) ? SubscriptionResource::getUrl('view', ['record' => $record->subscription]) : null), Tables\Columns\TextColumn::make('subscription.stripe_status') ->label('Subscription Status') ->badge() diff --git a/app/Filament/Resources/SubscriptionItemResource/Pages/ViewSubscriptionItem.php b/app/Filament/Resources/SubscriptionItemResource/Pages/ViewSubscriptionItem.php index 83b1d9c8b..3f7c3be6d 100644 --- a/app/Filament/Resources/SubscriptionItemResource/Pages/ViewSubscriptionItem.php +++ b/app/Filament/Resources/SubscriptionItemResource/Pages/ViewSubscriptionItem.php @@ -88,10 +88,10 @@ public function infolist(Schema $schema): Schema ->schema([ Components\TextEntry::make('subscription.id') ->label('Subscription ID') - ->url(fn ($record) => SubscriptionResource::getUrl('view', ['record' => $record->subscription_id])), + ->url(fn ($record) => $record->subscription && SubscriptionResource::can('view', $record->subscription) ? SubscriptionResource::getUrl('view', ['record' => $record->subscription_id]) : null), Components\TextEntry::make('subscription.user.email') ->label('User') - ->url(fn ($record) => UserResource::getUrl('edit', ['record' => $record->subscription_id])), + ->url(fn ($record) => $record->subscription?->user && UserResource::can('update', $record->subscription->user) ? UserResource::getUrl('edit', ['record' => $record->subscription->user_id]) : null), Components\TextEntry::make('stripe_id') ->label('Stripe ID') ->copyable(), diff --git a/app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php b/app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php index 7a1f5b52d..bf06ddd34 100644 --- a/app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php +++ b/app/Filament/Resources/SubscriptionResource/Pages/ViewSubscription.php @@ -39,7 +39,7 @@ public function infolist(Schema $schema): Schema ->schema([ Components\TextEntry::make('user.email') ->label('User') - ->url(fn ($record) => UserResource::getUrl('edit', ['record' => $record->user_id])), + ->url(fn ($record) => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null), Components\TextEntry::make('type'), Components\TextEntry::make('stripe_id') ->label('Stripe ID') diff --git a/app/Filament/Resources/SupportTicketResource.php b/app/Filament/Resources/SupportTicketResource.php index 292ac19a0..8602ed769 100644 --- a/app/Filament/Resources/SupportTicketResource.php +++ b/app/Filament/Resources/SupportTicketResource.php @@ -60,7 +60,7 @@ public static function infolist(Schema $schema): Schema Infolists\Components\TextEntry::make('user.email') ->label('User') ->formatStateUsing(fn (SupportTicket $record): string => trim(($record->user->name ?? '').' ('.$record->user->email.')')) - ->url(fn (SupportTicket $record): string => UserResource::getUrl('edit', ['record' => $record->user_id])), + ->url(fn (SupportTicket $record): ?string => $record->user && UserResource::can('update', $record->user) ? UserResource::getUrl('edit', ['record' => $record->user_id]) : null), Infolists\Components\TextEntry::make('created_at') ->label('Created') ->dateTime(), diff --git a/app/Filament/Resources/ThirdPartySaleResource.php b/app/Filament/Resources/ThirdPartySaleResource.php index c45af73f1..9668de169 100644 --- a/app/Filament/Resources/ThirdPartySaleResource.php +++ b/app/Filament/Resources/ThirdPartySaleResource.php @@ -168,7 +168,7 @@ public static function table(Table $table): Table ->url(fn (PluginLicense $record): ?string => $record->payout ? PluginPayoutResource::getUrl('view', ['record' => $record->payout]) : null) - ->visible(fn (PluginLicense $record): bool => $record->payout !== null), + ->visible(fn (PluginLicense $record): bool => $record->payout !== null && PluginPayoutResource::can('view', $record->payout)), ]) ->bulkActions([]) ->defaultSort('purchased_at', 'desc'); diff --git a/app/Filament/Resources/UserResource/RelationManagers/DeveloperPluginsRelationManager.php b/app/Filament/Resources/UserResource/RelationManagers/DeveloperPluginsRelationManager.php index 50874aa04..d9cf67278 100644 --- a/app/Filament/Resources/UserResource/RelationManagers/DeveloperPluginsRelationManager.php +++ b/app/Filament/Resources/UserResource/RelationManagers/DeveloperPluginsRelationManager.php @@ -44,6 +44,6 @@ public function table(Table $table): Table ->sortable(), ]) ->defaultSort('created_at', 'desc') - ->recordUrl(fn ($record) => PluginResource::getUrl('edit', ['record' => $record])); + ->recordUrl(fn ($record) => PluginResource::can('update', $record) ? PluginResource::getUrl('edit', ['record' => $record]) : null); } } diff --git a/app/Filament/Resources/WallOfLoveSubmissionResource/Pages/EditWallOfLoveSubmission.php b/app/Filament/Resources/WallOfLoveSubmissionResource/Pages/EditWallOfLoveSubmission.php index bda814849..1e1c83027 100644 --- a/app/Filament/Resources/WallOfLoveSubmissionResource/Pages/EditWallOfLoveSubmission.php +++ b/app/Filament/Resources/WallOfLoveSubmissionResource/Pages/EditWallOfLoveSubmission.php @@ -17,9 +17,8 @@ protected function getHeaderActions(): array Actions\DeleteAction::make(), Actions\Action::make('user') ->icon('heroicon-s-user') - ->action(function ($record) { - return redirect(UserResource::getUrl('edit', ['record' => $record])); - }), + ->visible(fn ($record): bool => $record->user !== null && UserResource::can('update', $record->user)) + ->url(fn ($record): string => UserResource::getUrl('edit', ['record' => $record->user_id])), ]; } } diff --git a/app/Http/Controllers/ShowBlogController.php b/app/Http/Controllers/ShowBlogController.php index 8e9a1e745..2434ba5e4 100644 --- a/app/Http/Controllers/ShowBlogController.php +++ b/app/Http/Controllers/ShowBlogController.php @@ -35,7 +35,7 @@ public function feed(BlogFeed $feed): Response public function show(Article $article) { - abort_unless($article->isPublished() || $article->isScheduled() || auth()->user()?->isAdmin(), 404); + abort_unless($article->isPublished() || $article->isScheduled() || auth()->user()?->can('view', $article), 404); // Set SEO metadata for the article SEOTools::setTitle($article->title.' - Blog'); diff --git a/app/Http/Controllers/SupportTicketAttachmentController.php b/app/Http/Controllers/SupportTicketAttachmentController.php index eb72cff63..152661c3c 100644 --- a/app/Http/Controllers/SupportTicketAttachmentController.php +++ b/app/Http/Controllers/SupportTicketAttachmentController.php @@ -13,7 +13,7 @@ public function downloadTicketAttachment(SupportTicket $supportTicket, int $inde { $user = auth()->user(); - abort_unless($user->id === $supportTicket->user_id || $user->isAdmin(), 403); + abort_unless($user->can('view', $supportTicket), 403); $attachments = $supportTicket->attachments ?? []; @@ -30,7 +30,7 @@ public function downloadReplyAttachment(SupportTicket $supportTicket, Reply $rep { $user = auth()->user(); - abort_unless($user->id === $supportTicket->user_id || $user->isAdmin(), 403); + abort_unless($user->can('view', $supportTicket), 403); abort_unless($reply->support_ticket_id === $supportTicket->id, 404); $attachments = $reply->attachments ?? []; diff --git a/app/Models/User.php b/app/Models/User.php index 4aeb8457a..63fdeddcc 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -66,6 +66,14 @@ public function getFilamentName(): string * role, whose permissions then decide which sections they can see. */ public function canAccessPanel(Panel $panel): bool + { + return $this->isStaff(); + } + + /** + * Staff are super admins plus anyone given a role in the admin panel. + */ + public function isStaff(): bool { return $this->isAdmin() || $this->roles->isNotEmpty(); } diff --git a/resources/views/filament/resources/support-ticket-resource/widgets/ticket-replies.blade.php b/resources/views/filament/resources/support-ticket-resource/widgets/ticket-replies.blade.php index 7bee723f6..9e66981ea 100644 --- a/resources/views/filament/resources/support-ticket-resource/widgets/ticket-replies.blade.php +++ b/resources/views/filament/resources/support-ticket-resource/widgets/ticket-replies.blade.php @@ -111,13 +111,13 @@
@forelse ($record->replies()->with('user')->orderBy('created_at', 'desc')->get() as $reply) @php - $isAdmin = $reply->user?->isAdmin(); + $isStaff = $reply->user?->isStaff(); $isNote = $reply->note; if ($isNote) { $bgColor = '#fefce8'; $borderColor = '#facc15'; - } elseif ($isAdmin) { + } elseif ($isStaff) { $bgColor = '#ede9fe'; $borderColor = '#c4b5fd'; } else { @@ -139,7 +139,7 @@ @if ($reply->pinned) Pinned @endif - @elseif ($isAdmin) + @elseif ($isStaff) Staff @endif @endif diff --git a/tests/Feature/Filament/AdminRolesAndPermissionsTest.php b/tests/Feature/Filament/AdminRolesAndPermissionsTest.php index 3920d19b0..7e3d3d7a9 100644 --- a/tests/Feature/Filament/AdminRolesAndPermissionsTest.php +++ b/tests/Feature/Filament/AdminRolesAndPermissionsTest.php @@ -11,10 +11,12 @@ use App\Filament\Resources\PluginResource\RelationManagers\ActivitiesRelationManager; use App\Filament\Resources\PluginResource\RelationManagers\PricesRelationManager; use App\Filament\Resources\SalesResource; +use App\Filament\Resources\SupportTicketResource\Pages\ViewSupportTicket; use App\Filament\Resources\SupportTicketResource\Widgets\TicketRepliesWidget; use App\Filament\Resources\UserResource; use App\Filament\Resources\UserResource\Pages\EditUser; use App\Filament\Widgets\StatsOverview; +use App\Models\Article; use App\Models\Plugin; use App\Models\PluginActivity; use App\Models\SupportTicket; @@ -26,6 +28,7 @@ use Filament\Facades\Filament; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Facades\Storage; use Livewire\Livewire; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -317,6 +320,79 @@ public function test_every_admin_page_and_widget_requires_a_permission(): void } } + public function test_staff_with_article_view_permission_can_preview_a_draft_article(): void + { + $draft = Article::factory()->create(['published_at' => null]); + + $this->actingAs($this->userWithRole(['ViewAny:Article', 'View:Article'])) + ->get(route('article', $draft)) + ->assertOk(); + + $this->actingAs($this->userWithRole(self::PLUGIN_REVIEW_PERMISSIONS)) + ->get(route('article', $draft)) + ->assertNotFound(); + } + + public function test_staff_with_ticket_view_permission_can_download_ticket_attachments(): void + { + Storage::fake('support-tickets', ['serve' => true]); + Storage::disk('support-tickets')->put('ticket/screenshot.png', 'image'); + + $ticket = SupportTicket::factory()->create([ + 'attachments' => [['name' => 'screenshot.png', 'path' => 'ticket/screenshot.png', 'size' => 5, 'mime_type' => 'image/png']], + ]); + + $this->actingAs($this->userWithRole(['ViewAny:SupportTicket', 'View:SupportTicket'])) + ->get(route('customer.support.tickets.attachment', [$ticket, 0])) + ->assertRedirect(); + + $this->actingAs($this->userWithRole(self::PLUGIN_REVIEW_PERMISSIONS)) + ->get(route('customer.support.tickets.attachment', [$ticket, 0])) + ->assertForbidden(); + } + + public function test_anyone_with_a_role_counts_as_staff(): void + { + $this->assertTrue($this->userWithRole(['ViewAny:SupportTicket'])->isStaff()); + $this->assertTrue($this->superAdmin()->isStaff()); + $this->assertFalse(User::factory()->create()->isStaff()); + } + + public function test_links_to_other_sections_are_hidden_when_the_role_cannot_open_them(): void + { + $ticket = SupportTicket::factory()->create(); + $userUrl = UserResource::getUrl('edit', ['record' => $ticket->user_id]); + + $this->actingAs($this->userWithRole(['ViewAny:SupportTicket', 'View:SupportTicket'])) + ->get(ViewSupportTicket::getUrl(['record' => $ticket])) + ->assertOk() + ->assertDontSee($userUrl, false); + } + + public function test_super_admin_sees_links_to_other_sections(): void + { + $ticket = SupportTicket::factory()->create(); + + $this->actingAs($this->superAdmin()) + ->get(ViewSupportTicket::getUrl(['record' => $ticket])) + ->assertOk() + ->assertSee(UserResource::getUrl('edit', ['record' => $ticket->user_id]), false); + } + + public function test_go_to_user_action_is_hidden_without_user_access(): void + { + $plugin = Plugin::factory()->pending()->free()->create(); + $goToUser = TestAction::make('viewUser')->schemaComponent('user_id'); + + Livewire::actingAs($this->userWithRole(self::PLUGIN_REVIEW_PERMISSIONS)) + ->test(EditPlugin::class, ['record' => $plugin->getRouteKey()]) + ->assertActionHidden($goToUser); + + Livewire::actingAs($this->superAdmin()) + ->test(EditPlugin::class, ['record' => $plugin->getRouteKey()]) + ->assertActionVisible($goToUser); + } + private function superAdmin(): User { $user = User::factory()->create();