Skip to content

Conversation

@rmartinoscar
Copy link
Member

@rmartinoscar rmartinoscar commented Oct 27, 2025

Closes #1831

Made this a Draft cause Filament doesn't seem to call them everywhere atm.

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

📝 Walkthrough

Walkthrough

Authorization was moved from Filament Resource can*() methods into Laravel Policies. Resource-level can*() overrides were removed, new namespaced server/admin policy classes were added or relocated, and Gate policy discovery was configured to resolve policies per active Filament panel.

Changes

Cohort / File(s) Change Summary
Filament Resource Authorization Removals
app/Filament/Server/Resources/Activities/ActivityResource.php, app/Filament/Server/Resources/Allocations/AllocationResource.php, app/Filament/Server/Resources/Backups/BackupResource.php, app/Filament/Server/Resources/Databases/DatabaseResource.php, app/Filament/Server/Resources/Files/FileResource.php, app/Filament/Server/Resources/Schedules/ScheduleResource.php, app/Filament/Server/Resources/Users/UserResource.php
Removed resource-level canViewAny(), canCreate(), canEdit(), canDelete() (varies per file). Per-action authorization now relies on policies/authorization responses rather than these can*() overrides.
Admin Policies Namespace Migration
app/Policies/Admin/...
app/Policies/Admin/ApiKeyPolicy.php, .../DatabaseHostPolicy.php, .../DefaultPolicies.php, .../EggPolicy.php, .../MountPolicy.php, .../NodePolicy.php, .../RolePolicy.php, .../UserPolicy.php, .../WebhookConfigurationPolicy.php
Updated namespace for existing admin policy classes from App\PoliciesApp\Policies\Admin (no behavioral changes).
New Server-Side Policies
app/Policies/Server/ActivityLogPolicy.php, app/Policies/Server/AllocationPolicy.php, app/Policies/Server/BackupPolicy.php, app/Policies/Server/DatabasePolicy.php, app/Policies/Server/FilePolicy.php, app/Policies/Server/SchedulePolicy.php, app/Policies/Server/UserPolicy.php
Added server-scoped policy classes implementing viewAny()/view()/create()/edit()/delete() as applicable; each delegates to user()?->can(ACTION_*, Filament::getTenant()).
Server Policy Infrastructure
app/Policies/Server/DefaultPolicies.php, app/Policies/Server/ServerPolicies.php
Added DefaultPolicies trait implementing a no-op __call() fallback; moved/refactored ServerPolicies into App\Policies\Server and removed its prior __call() catch-all.
Deleted Legacy Policy
app/Policies/DatabasePolicy.php
Removed legacy non-namespaced DatabasePolicy class.
Policy Discovery Configuration
app/Providers/AppServiceProvider.php
Replaced Gate::before closure with arrow fn and added Gate::guessPolicyNamesUsing() to derive policy classes using the active Filament panel (e.g., App\Policies\{Panel}\{Model}Policy).
ViewAction Visibility Changes (Admin Resources)
app/Filament/Admin/Resources/DatabaseHosts/DatabaseHostResource.php, .../Mounts/MountResource.php, .../Roles/RoleResource.php, .../Users/UserResource.php, .../Webhooks/WebhookResource.php
Replaced static::canEdit($record) checks with static::getEditAuthorizationResponse($record)->allowed() to determine ViewAction visibility.

Sequence Diagram(s)

sequenceDiagram
    participant Resource as Filament Resource
    participant Gate as Laravel Gate
    participant Discovery as Gate::guessPolicyNamesUsing
    participant Policy as Policy Class
    participant User as Authenticated User

    Resource->>Gate: authorize(action, model)
    Gate->>Discovery: guess policy for model
    Discovery->>Discovery: get Filament panel id\nconstruct App\Policies\{Panel}\{Model}Policy
    Discovery-->>Gate: policy class or null
    Gate->>Policy: call policy method (viewAny/view/create/edit/delete)
    Policy->>User: user()?->can(ACTION_*, Filament::getTenant())
    User-->>Policy: permission bool
    Policy-->>Gate: authorization result
    Gate-->>Resource: allow / deny
Loading
sequenceDiagram
    participant Old as Old (Resource can*() overrides)
    participant New as New (Policies + Gate discovery)

    rect rgb(230,230,255)
    Old->>Old: Resource::canViewAny()/canCreate()/canEdit()/canDelete()
    Old-->>Old: return boolean
    end

    rect rgb(230,255,230)
    New->>Gate: Resource requests authorization
    Gate->>Policy: Policy::viewAny()/create()/edit()/delete()
    Policy->>User: user()?->can(ACTION_*, tenant)
    Policy-->>Gate: AuthorizationResponse / bool
    end
Loading

Possibly related PRs

  • Add user() helper #1768: Modifies the same ActivityResource.php and aligns resource/policy changes (e.g., migrating auth calls to user()), indicating related resource-to-policy refactors.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Use Policies rather then overriding can*() functions" directly and clearly summarizes the main change in the PR. The changeset removes all can*() authorization method overrides from multiple Filament Resource classes and replaces them with policy-based authorization logic. The title is specific, concise, and accurately represents the primary objective without vague language or noise.
Linked Issues Check ✅ Passed The PR comprehensively addresses all coding requirements from issue #1831. It removes can*() authorization overrides from Resources [ActivityResource, AllocationResource, BackupResource, DatabaseResource, FileResource, ScheduleResource, UserResource], implements new policy classes in App\\Policies\\Server namespace with authorization methods for each resource [ActivityLogPolicy, AllocationPolicy, BackupPolicy, DatabasePolicy, FilePolicy, SchedulePolicy, UserPolicy], and updates AppServiceProvider.php to include dynamic policy resolution via Gate::guessPolicyNamesUsing(). The PR also updates Admin Resources to use getEditAuthorizationResponse()->allowed() for authorization checks [DatabaseHostResource, MountResource, RoleResource, UserResource, WebhookResource, ScheduleResource]. All changes align with the Filament upgrade guide requirements to adopt policy response objects instead of boolean-only can*() methods.
Out of Scope Changes Check ✅ Passed All changes in this PR are within scope of the policy migration objective. The removal of can*() methods, creation of new Server policies, reorganization of Admin policies into a dedicated namespace, updates to policy resolution in AppServiceProvider.php, and modifications to View Action visibility logic all directly support the transition to policy-based authorization. The syntax improvement to Gate::before() (from closure to arrow function) is a minor refinement that accompanies the main changes and does not introduce unrelated functionality.
Description Check ✅ Passed The pull request description directly references issue #1831, which corresponds to the documented objectives of moving from can*() authorization methods to using policy response objects. The changeset comprehensively implements this objective by removing can*() methods from multiple Filament Resources, creating new policy classes in the Server namespace, moving existing policies to the Admin namespace, updating the AppServiceProvider to dynamically resolve policies, and adjusting ViewAction visibility to use authorization response objects. The brief note about draft status provides additional context. While the description is minimal in detail, it is clearly related to the changeset and provides sufficient context through the issue reference.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (14)
app/Policies/Server/ServerPolicies.php (1)

9-14: Consider adding explicit policy methods for standard CRUD operations.

While the before() method handles permission-based authorization, the policy relies on the DefaultPolicies trait for standard CRUD operations (viewAny, view, create, update, delete). According to the PR objectives, explicit policy methods with response objects are preferred over relying on fallback behavior.

Consider adding explicit methods like:

public function viewAny(User $user): bool
{
    return true; // or your specific logic
}

public function view(User $user, Server $server): bool
{
    return true; // handled by before() in most cases
}

public function create(User $user): bool
{
    // Define explicit create authorization
    return $user->canCreateServers();
}

// Additional methods as needed

This would make the authorization behavior more explicit and align with the broader refactoring goals.

app/Policies/Admin/MountPolicy.php (1)

14-28: Harden before() signature to avoid arg count/type pitfalls.

Gate may call before($user, $ability, ...$args) with zero or a class-string. A required third param can trigger an ArgumentCountError. Make it nullable with a default and guard type.

Apply:

-    public function before(User $user, string $ability, string|Mount $mount): ?bool
+    public function before(User $user, string $ability, string|Mount|null $mount = null): ?bool
     {
-        // For "viewAny" the $mount param is the class name
-        if (is_string($mount)) {
+        // For "viewAny" the $mount param is the class name; some calls may pass no subject.
+        if (! $mount instanceof Mount) {
             return null;
         }
 
-        foreach ($mount->nodes as $node) {
+        foreach ($mount->nodes as $node) {
             if (!$user->canTarget($node)) {
                 return false;
             }
         }
 
         return null;
     }

Optional: $mount->loadMissing('nodes'); before the loop to reduce repeated lazy load.

app/Policies/Admin/DefaultPolicies.php (1)

10-44: Consider returning policy Response objects for richer feedback.

Current bool returns work, but swapping to Response enables messages/reasons and aligns with the PR’s “policy response objects” goal.

Example:

+use Illuminate\Auth\Access\Response;
@@
-    public function viewAny(User $user): bool
+    public function viewAny(User $user): bool|\Illuminate\Auth\Access\Response
     {
-        return $user->can('viewList ' . $this->modelName);
+        return $user->can('viewList ' . $this->modelName)
+            ? Response::allow()
+            : Response::deny();
     }

Repeat similarly for view/create/update/delete/replicate if you want consistent messaging. Otherwise, leaving bools is fine.

app/Filament/Server/Resources/Schedules/ScheduleResource.php (1)

338-339: Use getEditAuthorizationResponse() instead of canEdit() for consistency with policy responses.

Aligns the UI logic with the new authorization flow and avoids the legacy can* API.

Apply:

-                ViewAction::make()
-                    ->hidden(fn ($record) => static::canEdit($record)),
+                ViewAction::make()
+                    ->hidden(fn (Schedule $record) => static::getEditAuthorizationResponse($record)->allowed()),
app/Policies/Server/UserPolicy.php (3)

23-26: Silence PHPMD UnusedFormalParameter for $record or consume it

Either suppress or mark it consumed; keeps signatures intact for future use.

Option A — suppression:

     public static function edit(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_USER_UPDATE, Filament::getTenant()) ?? false;
     }
@@
     public static function delete(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_USER_DELETE, Filament::getTenant()) ?? false;
     }

Option B — explicitly consume:

-    public static function edit(Model $record): bool
+    public static function edit(Model $record): bool
     {
+        // parameter currently unused but kept for signature parity
+        unset($record);
         return user()?->can(Permission::ACTION_USER_UPDATE, Filament::getTenant()) ?? false;
     }

Also applies to: 28-31


23-31: Provide update() alias for broader compatibility

Many callers/Gate conventions use update(). Add a thin alias calling edit().

     public static function edit(Model $record): bool
     {
         return user()?->can(Permission::ACTION_USER_UPDATE, Filament::getTenant()) ?? false;
     }
+
+    public static function update(Model $record): bool
+    {
+        return self::edit($record);
+    }

13-31: Consider returning Laravel policy Responses to carry reasons

If PR goal targets richer responses, these methods can return Response::allow()/deny() instead of bools, or let Resource get*AuthorizationResponse() wrap reasons. Confirm intended design.

app/Policies/Server/SchedulePolicy.php (2)

23-26: Suppress or consume $record to appease PHPMD

Keep signature parity; silence the warning or unset the param.

     public static function edit(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant()) ?? false;
     }
@@
     public static function delete(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_SCHEDULE_DELETE, Filament::getTenant()) ?? false;
     }

Also applies to: 28-31


23-31: Add update() alias and optional view() for parity

  • Provide update() alias for compatibility.
  • Optionally add view() mirroring read permission to match DatabasePolicy.
     public static function edit(Model $record): bool
     {
         return user()?->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant()) ?? false;
     }
+
+    public static function update(Model $record): bool
+    {
+        return self::edit($record);
+    }
+
+    public static function view(Model $record): bool
+    {
+        return user()?->can(Permission::ACTION_SCHEDULE_READ, Filament::getTenant()) ?? false;
+    }
app/Policies/Server/AllocationPolicy.php (2)

23-26: Suppress or consume $record parameter

Silence PHPMD while preserving signatures.

     public static function edit(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_ALLOCATION_UPDATE, Filament::getTenant()) ?? false;
     }
@@
     public static function delete(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_ALLOCATION_DELETE, Filament::getTenant()) ?? false;
     }

Also applies to: 28-31


23-31: Add update() alias and optional view()

Improves compatibility and parity across policies.

     public static function edit(Model $record): bool
     {
         return user()?->can(Permission::ACTION_ALLOCATION_UPDATE, Filament::getTenant()) ?? false;
     }
+
+    public static function update(Model $record): bool
+    {
+        return self::edit($record);
+    }
+
+    public static function view(Model $record): bool
+    {
+        return user()?->can(Permission::ACTION_ALLOCATION_READ, Filament::getTenant()) ?? false;
+    }
app/Policies/Server/DatabasePolicy.php (3)

18-21: Suppress or consume $record to resolve PHPMD warnings

Same approach as others to avoid UnusedFormalParameter.

     public static function view(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_DATABASE_READ, Filament::getTenant()) ?? false;
     }
@@
     public static function edit(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_DATABASE_UPDATE, Filament::getTenant()) ?? false;
     }
@@
     public static function delete(Model $record): bool
     {
+        /** @noinspection PhpUnusedParameterInspection */
+        /** @SuppressWarnings(PHPMD.UnusedFormalParameter) */
         return user()?->can(Permission::ACTION_DATABASE_DELETE, Filament::getTenant()) ?? false;
     }

Also applies to: 28-31, 33-36


28-31: Add update() alias to complement edit()

Aligns with common ability naming.

     public static function edit(Model $record): bool
     {
         return user()?->can(Permission::ACTION_DATABASE_UPDATE, Filament::getTenant()) ?? false;
     }
+
+    public static function update(Model $record): bool
+    {
+        return self::edit($record);
+    }

11-11: Optional: constant instead of mutable property

If $modelName is constant metadata, prefer a class constant for clarity and immutability.

-    protected string $modelName = 'database';
+    public const MODEL_NAME = 'database';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bcdeea and 9127b9e.

📒 Files selected for processing (27)
  • app/Filament/Server/Resources/Activities/ActivityResource.php (0 hunks)
  • app/Filament/Server/Resources/Allocations/AllocationResource.php (0 hunks)
  • app/Filament/Server/Resources/Backups/BackupResource.php (0 hunks)
  • app/Filament/Server/Resources/Databases/DatabaseResource.php (0 hunks)
  • app/Filament/Server/Resources/Files/FileResource.php (0 hunks)
  • app/Filament/Server/Resources/Schedules/ScheduleResource.php (1 hunks)
  • app/Filament/Server/Resources/Users/UserResource.php (0 hunks)
  • app/Policies/Admin/ApiKeyPolicy.php (1 hunks)
  • app/Policies/Admin/DatabaseHostPolicy.php (1 hunks)
  • app/Policies/Admin/DefaultPolicies.php (1 hunks)
  • app/Policies/Admin/EggPolicy.php (1 hunks)
  • app/Policies/Admin/MountPolicy.php (1 hunks)
  • app/Policies/Admin/NodePolicy.php (1 hunks)
  • app/Policies/Admin/RolePolicy.php (1 hunks)
  • app/Policies/Admin/UserPolicy.php (1 hunks)
  • app/Policies/Admin/WebhookConfigurationPolicy.php (1 hunks)
  • app/Policies/DatabasePolicy.php (0 hunks)
  • app/Policies/Server/ActivityLogPolicy.php (1 hunks)
  • app/Policies/Server/AllocationPolicy.php (1 hunks)
  • app/Policies/Server/BackupPolicy.php (1 hunks)
  • app/Policies/Server/DatabasePolicy.php (1 hunks)
  • app/Policies/Server/DefaultPolicies.php (1 hunks)
  • app/Policies/Server/FilePolicy.php (1 hunks)
  • app/Policies/Server/SchedulePolicy.php (1 hunks)
  • app/Policies/Server/ServerPolicies.php (1 hunks)
  • app/Policies/Server/UserPolicy.php (1 hunks)
  • app/Providers/AppServiceProvider.php (2 hunks)
💤 Files with no reviewable changes (7)
  • app/Filament/Server/Resources/Activities/ActivityResource.php
  • app/Filament/Server/Resources/Files/FileResource.php
  • app/Policies/DatabasePolicy.php
  • app/Filament/Server/Resources/Users/UserResource.php
  • app/Filament/Server/Resources/Backups/BackupResource.php
  • app/Filament/Server/Resources/Databases/DatabaseResource.php
  • app/Filament/Server/Resources/Allocations/AllocationResource.php
🧰 Additional context used
🧬 Code graph analysis (8)
app/Policies/Server/DatabasePolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Models/Database.php (1)
  • Database (29-195)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/SchedulePolicy.php (4)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (12-15)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/DatabasePolicy.php (4)
  • viewAny (13-16)
  • create (23-26)
  • edit (28-31)
  • delete (33-36)
app/Policies/Server/BackupPolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (12-15)
app/Policies/Server/AllocationPolicy.php (3)
  • viewAny (13-16)
  • create (18-21)
  • delete (28-31)
app/Policies/Server/ActivityLogPolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/AllocationPolicy.php (1)
  • viewAny (13-16)
app/Policies/Server/DatabasePolicy.php (2)
  • viewAny (13-16)
  • view (18-21)
app/Policies/Server/UserPolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (12-15)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/FilePolicy.php (2)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/AllocationPolicy.php (7)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (12-15)
app/Policies/Server/BackupPolicy.php (3)
  • viewAny (13-16)
  • create (18-21)
  • delete (23-26)
app/Policies/Server/DatabasePolicy.php (4)
  • viewAny (13-16)
  • create (23-26)
  • edit (28-31)
  • delete (33-36)
app/Policies/Server/FilePolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/SchedulePolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/UserPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Providers/AppServiceProvider.php (1)
app/Models/User.php (2)
  • User (94-492)
  • isRootAdmin (379-382)
🪛 PHPMD (2.15.0)
app/Policies/Server/DatabasePolicy.php

18-18: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


33-33: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/SchedulePolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/BackupPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/UserPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/FilePolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/AllocationPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Providers/AppServiceProvider.php

173-173: Avoid unused parameters such as '$ability'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (14)
app/Policies/Server/ServerPolicies.php (3)

3-3: LGTM! Good namespace organization.

Moving server policies to the App\Policies\Server namespace improves code organization and aligns with the broader restructuring effort.


18-45: Authorization logic is well-structured.

The before() method correctly implements the authorization hierarchy:

  • Owners receive full permissions
  • Subusers are authorized based on their permission array
  • Node targeting is enforced as a hard requirement
  • Defers to default policy methods when none of the special cases apply

Note: When a subuser exists but lacks the specific permission, the method falls through to the node check and then returns null. This delegates the final decision to individual policy methods (or the DefaultPolicies trait), which appears intentional for the authorization flow.


11-11: The DefaultPolicies trait correctly implements deny-by-default behavior for undefined policy methods.

The trait's __call() method intercepts any undefined method calls and returns void (implicitly falsy), which causes Laravel's authorization to deny access. This is the intended "hack" to prevent unintended authorization bypasses. The implementation is secure and no changes are needed.

app/Policies/Admin/RolePolicy.php (1)

3-3: Namespace move looks good.

No behavioral change; aligns with Admin-scoped policies.

app/Policies/Admin/ApiKeyPolicy.php (1)

3-3: LGTM on namespace shift.

Consistent with Admin policy layout.

app/Policies/Admin/EggPolicy.php (1)

3-3: OK to move under Admin namespace.

Matches the new policy resolution scheme.

app/Policies/Admin/WebhookConfigurationPolicy.php (1)

3-3: Namespace adjustment approved.

Consistent with the Admin policy grouping.

app/Policies/Admin/NodePolicy.php (1)

3-3: LGTM! Clean namespace refactor.

The namespace change organizes admin policies into a dedicated subdirectory, aligning with the broader policy reorganization in this PR.

app/Policies/Admin/DatabaseHostPolicy.php (1)

3-3: LGTM! Consistent namespace refactor.

The namespace change aligns with the policy reorganization pattern applied across all admin policies.

app/Policies/Admin/UserPolicy.php (1)

3-3: LGTM! Namespace refactor.

Consistent with the policy reorganization moving admin policies to App\Policies\Admin.

app/Providers/AppServiceProvider.php (2)

173-173: LGTM! Cleaner arrow function syntax.

The refactor to an arrow function is more concise while preserving the same logic (root admins bypass all checks).

Note: The static analysis warning about unused $ability is a false positive—Laravel's Gate::before signature requires this parameter.


175-188: LGTM! Dynamic policy resolution aligns with namespace refactors.

The dynamic policy resolution based on the active Filament panel enables per-panel authorization logic. The implementation properly checks for class existence and skips when the panel ID is 'App', which complements the namespace reorganization of policies into Admin and Server subdirectories.

app/Policies/Server/BackupPolicy.php (1)

13-26: LGTM! Consistent server policy implementation.

The policy methods correctly check permissions against the Filament tenant context, following the same pattern as other server policies in this PR.

Note: The static analysis warning about unused $record is likely a false positive—the parameter may be required for Laravel's policy method resolution or reserved for future use.

app/Policies/Server/FilePolicy.php (1)

13-31: LGTM! Consistent file policy implementation.

The policy methods correctly delegate to permission checks with appropriate ACTION_FILE_* constants, following the same pattern as AllocationPolicy and other server policies.

Note: The static analysis warnings about unused $record parameters are likely false positives—these parameters may be required by Laravel's policy method resolution or reserved for future enhancements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (7)
app/Policies/Server/UserPolicy.php (2)

11-11: Consider removing unused $modelName property.

The $modelName property is not referenced in this policy class. Unless used by reflection (which doesn't appear to be the case), it should be removed.

Apply this diff if unused:

-    protected string $modelName = 'user';
-
     public function viewAny(): bool

13-31: Add null coalescing to prevent type errors when user is unauthenticated.

The nullsafe operator user()?->can(...) returns null when user() is null, violating the bool return type and potentially causing a fatal type error.

Apply this diff:

     public function viewAny(): bool
     {
-        return user()?->can(Permission::ACTION_USER_READ, Filament::getTenant());
+        return user()?->can(Permission::ACTION_USER_READ, Filament::getTenant()) ?? false;
     }

     public function create(): bool
     {
-        return user()?->can(Permission::ACTION_USER_CREATE, Filament::getTenant());
+        return user()?->can(Permission::ACTION_USER_CREATE, Filament::getTenant()) ?? false;
     }

     public function edit(Model $record): bool
     {
-        return user()?->can(Permission::ACTION_USER_UPDATE, Filament::getTenant());
+        return user()?->can(Permission::ACTION_USER_UPDATE, Filament::getTenant()) ?? false;
     }

     public function delete(Model $record): bool
     {
-        return user()?->can(Permission::ACTION_USER_DELETE, Filament::getTenant());
+        return user()?->can(Permission::ACTION_USER_DELETE, Filament::getTenant()) ?? false;
     }
app/Filament/Server/Resources/Schedules/ScheduleResource.php (1)

338-339: Fix inverted authorization logic for ViewAction.

The ViewAction is currently hidden when edit is allowed, which is backwards. It should be hidden when edit is not allowed.

Apply this diff:

                 ViewAction::make()
-                    ->hidden(fn ($record) => static::getEditAuthorizationResponse($record)->allowed()),
+                    ->hidden(fn ($record) => !static::getEditAuthorizationResponse($record)->allowed()),
app/Policies/Server/SchedulePolicy.php (1)

13-31: Coalesce null to false to satisfy bool return type.

All four methods use user()?->can(...) which returns null when the user is not authenticated, violating the bool return type declaration. This will cause a type error in strict mode.

Apply this diff to coalesce to false:

 public function viewAny(): bool
 {
-    return user()?->can(Permission::ACTION_SCHEDULE_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_SCHEDULE_READ, Filament::getTenant()) ?? false;
 }

 public function create(): bool
 {
-    return user()?->can(Permission::ACTION_SCHEDULE_CREATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_SCHEDULE_CREATE, Filament::getTenant()) ?? false;
 }

 public function edit(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant()) ?? false;
 }

 public function delete(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_SCHEDULE_DELETE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_SCHEDULE_DELETE, Filament::getTenant()) ?? false;
 }
app/Policies/Server/AllocationPolicy.php (1)

13-31: Coalesce null to false to satisfy bool return type.

All four methods use user()?->can(...) which returns null when the user is not authenticated, violating the bool return type declaration.

Apply this diff:

 public function viewAny(): bool
 {
-    return user()?->can(Permission::ACTION_ALLOCATION_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ALLOCATION_READ, Filament::getTenant()) ?? false;
 }

 public function create(): bool
 {
-    return user()?->can(Permission::ACTION_ALLOCATION_CREATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ALLOCATION_CREATE, Filament::getTenant()) ?? false;
 }

 public function edit(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_ALLOCATION_UPDATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ALLOCATION_UPDATE, Filament::getTenant()) ?? false;
 }

 public function delete(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_ALLOCATION_DELETE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ALLOCATION_DELETE, Filament::getTenant()) ?? false;
 }
app/Policies/Server/ActivityLogPolicy.php (1)

13-21: Coalesce null to false to satisfy bool return type.

Both methods use user()?->can(...) which returns null when the user is not authenticated, violating the bool return type declaration.

Apply this diff:

 public function viewAny(): bool
 {
-    return user()?->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant()) ?? false;
 }

 public function view(Model $model): bool
 {
-    return user()?->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant()) ?? false;
 }
app/Policies/Server/DatabasePolicy.php (1)

13-36: Coalesce null to false to satisfy bool return type.

All five methods use user()?->can(...) which returns null when the user is not authenticated, violating the bool return type declaration.

Apply this diff:

 public function viewAny(): bool
 {
-    return user()?->can(Permission::ACTION_DATABASE_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_DATABASE_READ, Filament::getTenant()) ?? false;
 }

 public function view(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_DATABASE_READ, Filament::getTenant());
+    return user()?->can(Permission::ACTION_DATABASE_READ, Filament::getTenant()) ?? false;
 }

 public function create(): bool
 {
-    return user()?->can(Permission::ACTION_DATABASE_CREATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_DATABASE_CREATE, Filament::getTenant()) ?? false;
 }

 public function edit(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_DATABASE_UPDATE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_DATABASE_UPDATE, Filament::getTenant()) ?? false;
 }

 public function delete(Model $record): bool
 {
-    return user()?->can(Permission::ACTION_DATABASE_DELETE, Filament::getTenant());
+    return user()?->can(Permission::ACTION_DATABASE_DELETE, Filament::getTenant()) ?? false;
 }
🧹 Nitpick comments (6)
app/Policies/Server/BackupPolicy.php (1)

11-11: Consider removing unused $modelName property.

The $modelName property is not referenced anywhere in this policy class. Unless it's used by reflection or a parent trait (which doesn't appear to be the case), it should be removed.

Apply this diff if unused:

-    protected string $modelName = 'backup';
-
     public function viewAny(): bool
app/Policies/Server/FilePolicy.php (1)

11-11: Consider removing unused $modelName property.

The $modelName property is not referenced in this policy class. Unless used by reflection (which doesn't appear to be the case), it should be removed.

Apply this diff if unused:

-    protected string $modelName = 'file';
-
     public function viewAny(): bool
app/Policies/Server/SchedulePolicy.php (1)

23-26: Unused $record parameter is acceptable for Laravel policy contract.

The static analysis tool flags the $record parameter as unused in both edit() and delete() methods. This is acceptable because:

  • Laravel policy methods follow standard signatures that include the model parameter
  • The permission checks are tenant-scoped via Filament::getTenant() rather than record-scoped
  • Maintaining the parameter preserves consistency with Laravel's policy contract

Also applies to: 28-31

app/Policies/Server/AllocationPolicy.php (1)

23-26: Unused $record parameter is acceptable for Laravel policy contract.

The $record parameter in edit() and delete() follows Laravel's standard policy signature. Since permission checks are tenant-scoped via Filament::getTenant(), the parameter remains unused but maintains API consistency.

Also applies to: 28-31

app/Policies/Server/ActivityLogPolicy.php (1)

18-21: Unused $model parameter is acceptable for Laravel policy contract.

The $model parameter in view() follows Laravel's standard policy signature. Since the permission check is tenant-scoped, the parameter remains unused but maintains API consistency.

app/Policies/Server/DatabasePolicy.php (1)

18-21: Unused $record parameter is acceptable for Laravel policy contract.

The $record parameter in view(), edit(), and delete() methods follows Laravel's standard policy signature. Since permission checks are tenant-scoped via Filament::getTenant(), the parameters remain unused but maintain API consistency.

Also applies to: 28-31, 33-36

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9127b9e and 42ce06e.

📒 Files selected for processing (32)
  • app/Filament/Admin/Resources/DatabaseHosts/DatabaseHostResource.php (1 hunks)
  • app/Filament/Admin/Resources/Mounts/MountResource.php (1 hunks)
  • app/Filament/Admin/Resources/Roles/RoleResource.php (1 hunks)
  • app/Filament/Admin/Resources/Users/UserResource.php (1 hunks)
  • app/Filament/Admin/Resources/Webhooks/WebhookResource.php (1 hunks)
  • app/Filament/Server/Resources/Activities/ActivityResource.php (0 hunks)
  • app/Filament/Server/Resources/Allocations/AllocationResource.php (0 hunks)
  • app/Filament/Server/Resources/Backups/BackupResource.php (0 hunks)
  • app/Filament/Server/Resources/Databases/DatabaseResource.php (0 hunks)
  • app/Filament/Server/Resources/Files/FileResource.php (0 hunks)
  • app/Filament/Server/Resources/Schedules/ScheduleResource.php (1 hunks)
  • app/Filament/Server/Resources/Users/UserResource.php (0 hunks)
  • app/Policies/Admin/ApiKeyPolicy.php (1 hunks)
  • app/Policies/Admin/DatabaseHostPolicy.php (1 hunks)
  • app/Policies/Admin/DefaultPolicies.php (1 hunks)
  • app/Policies/Admin/EggPolicy.php (1 hunks)
  • app/Policies/Admin/MountPolicy.php (1 hunks)
  • app/Policies/Admin/NodePolicy.php (1 hunks)
  • app/Policies/Admin/RolePolicy.php (1 hunks)
  • app/Policies/Admin/UserPolicy.php (1 hunks)
  • app/Policies/Admin/WebhookConfigurationPolicy.php (1 hunks)
  • app/Policies/DatabasePolicy.php (0 hunks)
  • app/Policies/Server/ActivityLogPolicy.php (1 hunks)
  • app/Policies/Server/AllocationPolicy.php (1 hunks)
  • app/Policies/Server/BackupPolicy.php (1 hunks)
  • app/Policies/Server/DatabasePolicy.php (1 hunks)
  • app/Policies/Server/DefaultPolicies.php (1 hunks)
  • app/Policies/Server/FilePolicy.php (1 hunks)
  • app/Policies/Server/SchedulePolicy.php (1 hunks)
  • app/Policies/Server/ServerPolicies.php (1 hunks)
  • app/Policies/Server/UserPolicy.php (1 hunks)
  • app/Providers/AppServiceProvider.php (2 hunks)
💤 Files with no reviewable changes (7)
  • app/Filament/Server/Resources/Users/UserResource.php
  • app/Filament/Server/Resources/Activities/ActivityResource.php
  • app/Filament/Server/Resources/Backups/BackupResource.php
  • app/Filament/Server/Resources/Databases/DatabaseResource.php
  • app/Filament/Server/Resources/Files/FileResource.php
  • app/Filament/Server/Resources/Allocations/AllocationResource.php
  • app/Policies/DatabasePolicy.php
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/Policies/Admin/DatabaseHostPolicy.php
  • app/Policies/Admin/EggPolicy.php
  • app/Policies/Admin/ApiKeyPolicy.php
  • app/Policies/Admin/WebhookConfigurationPolicy.php
  • app/Policies/Admin/DefaultPolicies.php
🧰 Additional context used
🧬 Code graph analysis (8)
app/Policies/Server/AllocationPolicy.php (2)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/DatabasePolicy.php (4)
  • viewAny (13-16)
  • create (23-26)
  • edit (28-31)
  • delete (33-36)
app/Providers/AppServiceProvider.php (5)
app/Policies/Admin/DatabaseHostPolicy.php (1)
  • before (14-28)
app/Policies/Admin/MountPolicy.php (1)
  • before (14-28)
app/Policies/Admin/NodePolicy.php (1)
  • before (14-26)
app/Policies/Server/ServerPolicies.php (1)
  • before (18-45)
app/Models/User.php (2)
  • User (94-492)
  • isRootAdmin (379-382)
app/Policies/Server/ActivityLogPolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/AllocationPolicy.php (1)
  • viewAny (13-16)
app/Policies/Server/DatabasePolicy.php (2)
  • viewAny (13-16)
  • view (18-21)
app/Policies/Server/UserPolicy.php (2)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/FilePolicy.php (3)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (13-16)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/BackupPolicy.php (5)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (1)
  • viewAny (13-16)
app/Policies/Server/AllocationPolicy.php (3)
  • viewAny (13-16)
  • create (18-21)
  • delete (28-31)
app/Policies/Server/DatabasePolicy.php (3)
  • viewAny (13-16)
  • create (23-26)
  • delete (33-36)
app/Policies/Server/FilePolicy.php (3)
  • viewAny (13-16)
  • create (18-21)
  • delete (28-31)
app/Policies/Server/DatabasePolicy.php (4)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/ActivityLogPolicy.php (2)
  • viewAny (13-16)
  • view (18-21)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/FilePolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
app/Policies/Server/SchedulePolicy.php (2)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Policies/Server/AllocationPolicy.php (4)
  • viewAny (13-16)
  • create (18-21)
  • edit (23-26)
  • delete (28-31)
🪛 PHPMD (2.15.0)
app/Policies/Server/AllocationPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Providers/AppServiceProvider.php

173-173: Avoid unused parameters such as '$ability'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/ActivityLogPolicy.php

18-18: Avoid unused parameters such as '$model'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/UserPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/FilePolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/BackupPolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/DatabasePolicy.php

18-18: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


33-33: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

app/Policies/Server/SchedulePolicy.php

23-23: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)


28-28: Avoid unused parameters such as '$record'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (14)
app/Filament/Admin/Resources/DatabaseHosts/DatabaseHostResource.php (1)

95-95: LGTM! Authorization response migration implemented correctly.

The ViewAction visibility now uses getEditAuthorizationResponse($record)->allowed() instead of the deprecated canEdit($record) method, aligning with the PR's objective to adopt policy response objects for authorization.

app/Filament/Admin/Resources/Users/UserResource.php (1)

133-133: LGTM! Consistent with authorization response pattern.

ViewAction visibility correctly migrated to use authorization response method.

app/Policies/Admin/RolePolicy.php (1)

3-3: LGTM! Policy namespace reorganization.

The namespace change from App\Policies to App\Policies\Admin is part of a broader policy reorganization to separate admin and server policies. No behavioral changes.

app/Filament/Admin/Resources/Webhooks/WebhookResource.php (1)

101-101: LGTM! Authorization response pattern applied correctly.

app/Policies/Admin/NodePolicy.php (1)

3-3: LGTM! Namespace refactor consistent with policy reorganization.

app/Policies/Admin/MountPolicy.php (1)

3-3: LGTM! Policy namespace reorganization applied consistently.

app/Filament/Admin/Resources/Roles/RoleResource.php (1)

101-101: LGTM! Authorization response migration implemented correctly.

app/Filament/Admin/Resources/Mounts/MountResource.php (1)

98-98: LGTM! Authorization response pattern applied consistently.

Excellent work on this PR! All resource files consistently migrate from canEdit() to getEditAuthorizationResponse()->allowed(), and all policy namespaces are properly reorganized. The changes align perfectly with the PR objectives.

app/Policies/Server/ServerPolicies.php (2)

3-3: LGTM! Namespace refactored to align with Admin/Server policy structure.

The move to App\Policies\Server is consistent with the new policy organization approach.


11-11: LGTM! DefaultPolicies trait ensures before() is always invoked.

The trait provides a __call fallback so Laravel's authorization system will call before() even when no matching policy method exists.

app/Policies/Server/DefaultPolicies.php (1)

5-18: LGTM! Trait correctly implements __call workaround.

The __call method signature is correct with array $arguments, and the no-op implementation appropriately ensures Laravel invokes before() for all authorization checks, even when specific policy methods don't exist.

app/Policies/Admin/UserPolicy.php (1)

3-3: LGTM! Namespace refactored to Admin policy structure.

The move to App\Policies\Admin aligns with the new policy organization separating Admin and Server contexts.

app/Providers/AppServiceProvider.php (2)

173-173: LGTM! Simplified Gate::before to arrow function.

The arrow function is more concise and correctly returns true for root admins, allowing other users to proceed through normal authorization.

Note: The $ability parameter is required by the Gate::before signature even though it's unused here.


175-188: LGTM! Dynamic policy resolution enables panel-specific policies.

The Gate::guessPolicyNamesUsing callback correctly maps models to panel-specific policy classes (App\Policies\Admin\* or App\Policies\Server\*), supporting the new authorization structure.

@rmartinoscar rmartinoscar marked this pull request as draft October 27, 2025 20:05
Comment on lines +13 to +16
public function viewAny(): bool
{
return user()?->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user should be passed as param, like the other (admin) policies.
This is not always the logged-in user.

public function viewAny(User $user): bool
{
return $user->can('viewList ' . $this->modelName);
}


class ActivityLogPolicy
{
protected string $modelName = 'activityLog';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the modelName is needed?

Comment on lines +14 to +17
public function __call(string $name, array $arguments): void
{
// do nothing
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this moved to it's own class/ trait?


if (class_exists($class)) {
return $class;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this missing a return null; or something at the end?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move to Policies

3 participants