首页 > 解决方案 > Laravel:防止更改其他用户的项目

问题描述

我有三个模型。我想避免用户可以从属于其他用户的 todolists 更改待办事项。

class User extends Authenticatable
{
    public function todolists()
    {
        return $this->hasMany('App\Todolist');
    }

    public function todos()
    {
        return $this->hasManyThrough('App\Todo', 'App\Todolist');
    }
}

class Todolist extends Model
{
    public function user()
    {
        return $this->belongsTo('App\User');
    }

    public function todos()
    {
        return $this->hasMany('App\Todo');
    }
}

class Todo extends Model
{
    protected $casts = [
        'completed' => 'boolean',
    ];

    public function todolist()
    {
        return $this->belongsTo('App\Todolist');
    }
}

为了避免用户可以查看其他用户的 todolists 和 todo 项,我实现了以下内容:

 public function getTodosForTodolist(Todolist $todolist)
    {
        if (Auth::user()->id == $todolist->user_id) {
            $todos = Todo::where('todolist_id', $todolist->id )->get();
            return view('todo/index', ['todos' => $todos); 
        }
        else {
            abort(403, 'Unauthorized action.');
        }
    }

下一步是防止用户可以编辑其他用户的待办事项。目前在 TodoController 我只有以下内容:

public function edit(Todo $todo)
        {
         if (Auth::user()->todos->id == $todo->todolist->id) {
            return view('todo/edit', ['todo' => $todo]);
         }
    } 

这给出了以下错误:

此集合实例上不存在属性 [id]。

该错误是因为当前用户有多个待办事项。所以我改变了我的代码如下。

public function edit(Todo $todo)
    {
        if (Auth::user()->todos->first()->id == $todo->todolist->id) {
            return view('todo/edit', ['todo' => $todo]);
        }
        abort('403', 'Unauthorized action.'); 
}

这行得通,但这样做感觉非常错误。

实现用户可以查看/编辑/删除属于其他用户的项目的更好方法是什么?

标签: laravel

解决方案


我建议您为您的 Todo 和 TodoList 模型使用策略以及将 todo 限制为一个用户的范围,以防止您的应用程序中出现重复代码:

class ToDoListPolicy
{
    public function view(User $user, TodoList $post)
    {
        return $user->id === $todolist->user_id;
    }
}

class ToDoPolicy
{
    public function edit(User $user, Todo $toDo)
    {
        $toDo->loadMissing('todolist');
        
        return $user->id === $toDo->todolist->user_id;
    }
}

在您的AuthServiceProvider.php中注册它们

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        TodoList::class => ToDoListPolicy::class,
        Todo::class => ToDoPolicy::class
    ];
}

然后在你的行动中使用它们:

public function getTodosForTodolist(Todolist $toDoList)
{
    $this->authorize('view', $toDoList);
    
    $toDoList->loadMissing('todos');
    
    return view('todo.index', ['todos' => $toDoList->todos); 
}

class ToDoController extends Controller
{
    public function edit(Todo $toDo)
    {
        $this->authorize('edit', $toDo);
        
        return view('todo.edit', compact('toDo'));
    } 
}

以及将查询限制为特定用户的范围:

class Todo extends Model {
    // ...
    
    public function scopeByUser(Builder $query, ?User $user = null)
    {
        if (! $user) {
            $user = Auth::user();
        }
        
        $query->whereHas('todolist', function (Builder $toDoListQuery) use ($user) {
            $toDoListQuery->where('user_id', $user->id);
        });
    }
}

在评论中回答您的问题

Q1:我必须添加Auth::user()->can('view', $todolist);一个 if-else 子句才能使其工作。猜猜这就是它的工作方式?

$this->authorize('edit', $todo)Q2:和有什么区别Auth::user()->can('edit', $todo)

对不起,那是我这边的一个错误。Auth::user()->can()返回一个布尔值,而$this->authorize()(通常包含在 BaseController 中的 AuthorizesRequests 特征的一种方法)如果授权失败则抛出异常。


推荐阅读