murilo's avatar
Level 10

policy nested model

I was recently looking this tutorial -

https://laracasts.com/series/laravel-api-master-class/episodes/17?page=1

Jeremy, created a token with ability for users like this -

    public const CreateTicket = 'ticket:create';
    public const UpdateTicket = 'ticket:update';
    public const ReplaceTicket = 'ticket:replace';
    public const DeleteTicket = 'ticket:delete';

https://github.com/laracasts/laravel-api-master-class/blob/episode_24/app/Permissions/V1/Abilities.php

but imagine that in Ticket we have as well ImageGallery .

in Update ticket you need to validate the image gallery with policy as well . to check if user can update images .

you have 2 options -

  1. make at the same policy ( ticketPolicy ) , and make a foreach to check the gallery .
namespace App\Policies;

use App\Models\Ticket;
use App\Models\User;
use App\Models\ImageGallery;
use App\Permissions\Abilities;
use Illuminate\Auth\Access\Response;

class TicketImageGalleryPolicy
{
    public function update(User $user, ImageGallery $imageGallery)
    {
        // Check if the user has the ability to update ticket images
        if (!$user->tokenCan(Abilities::UpdateTicketImages)) {
            return Response::deny('You do not have permission to update these images.');
        }

        // Additional checks if necessary
        if ($imageGallery->user_id != $user->id) {
            return Response::deny('You do not have permission to update these images.');
        }

        return Response::allow();
    }
}


  1. make a new token ability - Abilities::UpdateTicketImages And create a new policy - TicketImageGalleryPolicy

And check in the update ticket in the UpdateTicketRequest . something like this -

what wold a better approach to work with abilities token, knowing that I will only update gallery when update Ticket ?

0 likes
1 reply
LaryAI's avatar
Level 58

When dealing with nested models like ImageGallery within a Ticket, and you want to ensure that the user has the appropriate permissions to update both the ticket and its associated image gallery, you have a couple of options. Both approaches you mentioned are valid, but they serve slightly different purposes and can be chosen based on your specific needs.

Option 1: Single Policy with Nested Checks

This approach involves using a single policy (e.g., TicketPolicy) to handle both the ticket and its associated image gallery. This can be efficient if the permissions for updating the ticket and the image gallery are closely related or if you want to keep the logic centralized.

Pros:

  • Centralized logic for related permissions.
  • Easier to manage if the permissions are tightly coupled.

Cons:

  • Can become complex if the logic for the image gallery is significantly different from the ticket.

Option 2: Separate Policies and Abilities

Creating a separate policy for the ImageGallery and defining a new ability (e.g., Abilities::UpdateTicketImages) can be beneficial if the permissions for the image gallery are distinct or if you anticipate needing to manage these permissions separately in the future.

Pros:

  • Clear separation of concerns.
  • Easier to extend or modify permissions for the image gallery independently.

Cons:

  • Slightly more overhead in managing multiple policies and abilities.

Recommended Approach

Given that you will only update the image gallery when updating the ticket, and assuming the permissions are somewhat related, you might lean towards the first option for simplicity. However, if you foresee the need for distinct permissions or more complex logic for the image gallery, the second option would be more appropriate.

Here's how you might implement the second option with separate policies:

  1. Define a New Ability:

    Add a new ability for updating ticket images in your Abilities class:

    public const UpdateTicketImages = 'ticket:update-images';
    
  2. Create a Separate Policy for ImageGallery:

    Define a policy specifically for the ImageGallery:

    namespace App\Policies;
    
    use App\Models\User;
    use App\Models\ImageGallery;
    use App\Permissions\Abilities;
    use Illuminate\Auth\Access\Response;
    
    class ImageGalleryPolicy
    {
        public function update(User $user, ImageGallery $imageGallery)
        {
            if (!$user->tokenCan(Abilities::UpdateTicketImages)) {
                return Response::deny('You do not have permission to update these images.');
            }
    
            if ($imageGallery->user_id != $user->id) {
                return Response::deny('You do not have permission to update these images.');
            }
    
            return Response::allow();
        }
    }
    
  3. Check Permissions in the Request:

    Use the Gate facade to check permissions in your request validation:

    namespace App\Http\Requests;
    
    use Illuminate\Foundation\Http\FormRequest;
    use Illuminate\Support\Facades\Gate;
    use App\Models\ImageGallery;
    
    class TicketRequest extends FormRequest
    {
        public function rules()
        {
            return [
                // Other validation rules...
    
                'image_gallery.images.*' => function (string $attribute, mixed $value, Closure $fail) {
                    $imageGallery = ImageGallery::find($value['id']);
                    if (!$imageGallery) {
                        $fail('Invalid image ID.');
                        return;
                    }
    
                    $response = Gate::inspect('update', $imageGallery);
                    if (!$response->allowed()) {
                        $fail($response->message());
                    }
                },
            ];
        }
    }
    

This approach provides a clear separation of concerns and allows you to manage permissions for the image gallery independently, which can be beneficial as your application grows.

Please or to participate in this conversation.