Meysam Zarei
Meysam Zarei

Reputation: 439

How is it possible to manage users' permission on specific data in Backpack for Laravel?

I'm using the laravel-backpack admin for the administration section of my app, as well as I'm using the PermissionManager for managing user roles and permissions. Now I want to assign specific access to specific data.

for example, imagine we have an article management system, as well as we have an editor role and its related permission for a user whose username is user24, so the desired way is that the user24 must access to specific articles like article 1 in their dashboard or crud list.

Articles table's structure:

NOTE: the 'user_id' field refers to the owner of the article.

|---------------------|------------------|------------------|   
|      id             |     title        |     user_id      |
|---------------------|------------------|------------------|
|          1          |     article1     |        2         |
|---------------------|------------------|------------------|

Upvotes: 1

Views: 1136

Answers (1)

Wesley Smith
Wesley Smith

Reputation: 19571

Approach 1:

One way to accomplish this it to create a second class called CrudArticle (or whatever), make it extend the original Article class but then, add a global scope to this model that limits all queries to the table using the class to only include records that belong to the current user. Use this class with your CRUD panel and the normal Articles class elsewhere in your application.

class CrudArticle extends Article {

    public static function boot()
    {
        parent::boot();
        
        // only include models that belong to this user
        $user_id = 0;
        Auth::check();
        if ($user = Auth::user()) {
            $user_id = $user->id;
        }
        static::addGlobalScope('userFilter', static function (Builder $builder) use ($user_id){
            $builder->where('user_id', $user_id);
        });
    }
}

NOTE: This will prevent the non-owned records from appearing on the list page and prevent loading of the edit page via a direct url. However, Im not 100% sure the above alone would protect from inserts (post requests directly to the update endpoint), ie, you might still need the change for the "UpdateRequest" recomended in the second possible solution below


Approach 2:

Another way to accomplish this is to modify the queries used by the CRUD for example, in your CRUD controller:

/**
 * Set up the "list" or "read" operation for the resource
 */
public function setupListOperation(): void
{
    // ... normal setup code ...

    Auth::check();
    $user = Auth::user();
    if (!$user) {
        throw new \Exception('Unauthorized');
    }
    $this->crud->query = $this->crud->query->where('user_id', $user->id);
}

To prevent viewing of the update page via a direct url, you can add something like this to setupUpdateOperation:

/**
 * Set up the "update" operation for the resource
 */
public function setupUpdateOperation(): void
{
    // ... normal setup code ...
    
    $authorized = false;
    // only allow viewing the update page if the user is logged in and owns the Article
    Auth::check();
    if ($user = Auth::user()) {
        $id = $this->get('id');
        $product = Article::find($id);
        if ($product) {
            $authorized = $product->user_id === $user->id;
        }
    }
    if (!$authorized) {
        $this->crud->denyAccess(['update']);
    }
}

To prevent unauthorized edits posted directly to the update endpoint, you'll need to also add something like the below to your UpdateRequest:

/**
 * Determine if the user is authorized to make this request.
 *
 * @return bool
 */
public function authorize()
{
    $authorized = false;
    // only allow updates if the user is logged in and owns the Article
    Auth::check();
    if ($user = Auth::user()) {
        $id = $this->get('id');
        $product = Article::find($id);
        if ($product) {
            $authorized = $product->user_id === $user->id;
        }
    }
    return $authorized;
}

Alternatively, you could add a global scope to the model in question as explained here though I tend to avoid that as it changes behavior across the whole app

Upvotes: 1

Related Questions