当只有您或您的开发团队可以访问 Nova 时,您可能不需要在 Nova 处理传入请求之前进行额外的授权。但是,如果您向您的客户或大型开发人员团队提供对 Nova 的访问权限,您可能希望授权某些请求。例如,也许只有管理员才能删除记录。值得庆幸的是,Nova 采用了一种简单的方法来进行授权,它利用了您已经熟悉的许多 Laravel 功能。
为了限制哪些用户可以查看、创建、更新或删除资源,Nova 利用了 Laravel 的 授权策略。策略是简单的 PHP 类,用于组织特定模型或资源的授权逻辑。例如,如果您的应用程序是一个博客,您可能有一个 Post
模型和一个相应的 PostPolicy
在您的应用程序中。
在 Nova 中操作资源时,Nova 将自动尝试为该模型找到相应的策略。如果 Nova 检测到已为该模型注册了策略,它将在执行各自的操作(例如)之前自动检查该策略的相关授权方法。
viewAny
view
create
update
replicate
delete
restore
forceDelete
不需要额外的配置!因此,例如,要确定哪些用户可以更新 Post
模型,您只需在模型的相应策略类中定义一个 update
方法。
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can update the post.
*
* @param \App\Models\User $user
* @param \App\Models\Post $post
* @return mixed
*/
public function update(User $user, Post $post)
{
return $user->type == 'editor';
}
}
如果存在策略但缺少特定操作的方法,Nova 将对每个操作使用以下默认权限。
策略操作 | 默认权限 |
---|---|
viewAny | 允许 |
view | 禁止 |
create | 禁止 |
update | 禁止 |
replicate | 回退到 create 和 update |
delete | 禁止 |
forceDelete | 禁止 |
restore | 禁止 |
add{Model} | 允许 |
attach{Model} | 允许 |
attachAny{Model} | 允许 |
detach{Model} | 允许 |
runAction | 回退到 update |
runDestructiveAction | 回退到 delete |
因此,如果您已定义策略,请不要忘记定义所有相关的授权方法,以便对给定资源的授权规则是明确的。
如果您想从仪表板用户的子集中隐藏整个 Nova 资源,您可以在模型的策略类中定义 viewAny
方法。如果未为给定策略定义 viewAny
方法,Nova 将假定用户可以查看资源。
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any posts.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
return in_array('view-posts', $user->permissions);
}
}
如果您需要在从 Nova 内部启动请求与从主应用程序启动请求时以不同的方式授权操作,您可以在策略中使用 Nova 的 whenServing
方法。此方法允许您仅在请求是 Nova 请求时执行给定的回调。可以提供一个额外的回调,该回调将在非 Nova 请求时执行。
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Http\Request;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
class PostPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any posts.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
return Nova::whenServing(function (NovaRequest $request) use ($user) {
return in_array('nova:view-posts', $user->permissions);
}, function (Request $request) use ($user) {
return in_array('view-posts', $user->permissions);
});
}
}
我们已经了解了如何授权典型的查看、创建、更新和删除操作,但关系交互呢?例如,如果您正在构建一个播客应用程序,您可能希望指定只有某些 Nova 用户可以向播客添加评论。同样,Nova 通过利用 Laravel 的策略使这变得简单。
在处理关系时,Nova 使用简单的策略方法命名约定。为了说明此约定,假设您的应用程序具有 Podcast
资源和 Comment
资源。如果您想授权哪些用户可以向播客添加评论,您应该在播客模型的策略类中定义一个 addComment
方法。
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Podcast;
use Illuminate\Auth\Access\HandlesAuthorization;
class PodcastPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can add a comment to the podcast.
*
* @param \App\Models\User $user
* @param \App\Models\Podcast $podcast
* @return mixed
*/
public function addComment(User $user, Podcast $podcast)
{
return true;
}
}
如您所见,Nova 使用简单的 add{Model}
策略方法命名约定来授权关系操作。
对于多对多关系,Nova 使用类似的命名约定。但是,您应该使用 attach{Model}
/ detach{Model}
命名约定,而不是 add{Model}
。例如,假设 Podcast
模型与 Tag
模型具有多对多关系。如果您想授权哪些用户可以将“标签”附加到播客,您可以在播客策略中添加一个 attachTag
方法。此外,您可能还想在标签策略中定义反向 attachPodcast
。
<?php
namespace App\Policies;
use App\Models\Tag;
use App\Models\User;
use App\Models\Podcast;
use Illuminate\Auth\Access\HandlesAuthorization;
class PodcastPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can attach a tag to a podcast.
*
* @param \App\Models\User $user
* @param \App\Models\Podcast $podcast
* @param \App\Models\Tag $tag
* @return mixed
*/
public function attachTag(User $user, Podcast $podcast, Tag $tag)
{
return true;
}
/**
* Determine whether the user can detach a tag from a podcast.
*
* @param \App\Models\User $user
* @param \App\Models\Podcast $podcast
* @param \App\Models\Tag $tag
* @return mixed
*/
public function detachTag(User $user, Podcast $podcast, Tag $tag)
{
return true;
}
}
在前面的示例中,我们确定用户是否有权将一个模型附加到另一个模型。如果某些类型的用户永远不允许附加给定类型的模型,您可以在策略类中定义一个 attachAny{Model}
方法。这将完全阻止“附加”按钮显示在 Nova UI 中。
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Podcast;
use Illuminate\Auth\Access\HandlesAuthorization;
class PodcastPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can attach any tags to the podcast.
*
* @param \App\Models\User $user
* @param \App\Models\Podcast $podcast
* @return mixed
*/
public function attachAnyTag(User $user, Podcast $podcast)
{
return false;
}
}
多对多授权
在处理多对多关系时,请确保在每个相关资源的策略类中定义了正确的授权策略方法。
如果您的一个 Nova 资源的模型具有相应的策略,但您想禁用该资源的 Nova 授权(从而允许所有操作),您可以覆盖 Nova 资源上的 authorizable
方法
/**
* Determine if the given resource is authorizable.
*
* @return bool
*/
public static function authorizable()
{
return false;
}
有时您可能希望对某些用户隐藏某些字段。您可以通过将 canSee
方法链接到您的字段定义来轻松实现此目的。canSee
方法接受一个闭包,该闭包应返回 true
或 false
。闭包将接收传入的 HTTP 请求
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
/**
* Get the fields displayed by the resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @return array
*/
public function fields(NovaRequest $request)
{
return [
ID::make()->sortable(),
Text::make('Name')
->sortable()
->canSee(function ($request) {
return $request->user()->can('viewProfile', $this);
}),
];
}
在上面的示例中,我们使用 Laravel 的 Authorizable
特征的 can
方法在我们的 User
模型上确定授权用户是否被授权执行 viewProfile
操作。但是,由于代理到授权策略方法是 canSee
的常见用例,因此您可以使用 canSeeWhen
方法来实现相同的效果。canSeeWhen
方法具有与 Illuminate\Foundation\Auth\Access\Authorizable
特征的 can
方法相同的函数签名
Text::make('Name')
->sortable()
->canSeeWhen('viewProfile', $this),
授权和“Can”方法
要了解有关 Laravel 授权助手和 can
方法的更多信息,请查看完整的 Laravel 授权文档。
您可能会注意到,从策略的 view
方法返回 false
不会阻止给定资源出现在资源索引中。要从资源索引查询中过滤模型,您可以覆盖资源类上的 indexQuery
方法。
此方法已在您的应用程序的 App\Nova\Resource
基类中定义;因此,您只需将该方法复制粘贴到特定资源中,然后根据您希望过滤资源索引结果的方式修改查询
/**
* Build an "index" query for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function indexQuery(NovaRequest $request, $query)
{
return $query->where('user_id', $request->user()->id);
}
如果您想过滤用于填充关系模型选择菜单的查询,您可以覆盖资源上的 relatableQuery
方法。
例如,如果您的应用程序有一个属于Podcast
资源的Comment
资源,Nova将允许您在创建Comment
时选择父Podcast
。为了限制选择菜单中可用的播客,您应该覆盖Podcast
资源上的relatableQuery
方法
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Laravel\Nova\Fields\Field $field
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function relatableQuery(NovaRequest $request, $query)
{
return $query->where('user_id', $request->user()->id);
}
您可以使用以模型复数形式为后缀的动态、约定式方法名,为单个关系定制“关联”查询。例如,如果您的应用程序有一个Post
资源,其中帖子可以被标记,但Tag
资源与不同类型的模型相关联,您可以定义一个relatableTags
方法来定制此关系的关联查询
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Laravel\Nova\Fields\Field $field
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function relatableTags(NovaRequest $request, $query)
{
return $query->where('type', 'posts');
}
如有必要,您可以通过传递给方法的NovaRequest
实例访问请求的resource
和resourceId
public static function relatableTags(NovaRequest $request, $query)
{
$resource = $request->route('resource'); // The resource type...
$resourceId = $request->resourceId; // The resource ID...
return $query->where('type', $resource);
}
当一个Nova资源通过多个字段依赖于另一个资源时,您通常会为这些字段分配不同的名称,例如
BelongsTo::make('Current Team', 'currentTeam', Team::class),
HasMany::make('Owned Teams', 'ownedTeams', Team::class),
BelongsToMany::make('Teams', 'teams', Team::class),
在这些情况下,您应该在定义关系时提供第三个参数来指定关系应该使用哪个Nova资源,因为Nova可能无法通过约定确定这一点
/**
* Build a "relatable" query for the given resource.
*
* This query determines which instances of the model may be attached to other resources.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Laravel\Nova\Fields\Field $field
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function relatableTeams(NovaRequest $request, $query, Field $field)
{
if ($field instanceof BelongsToMany && $field->attribute === 'teams') {
// ...
} elseif ($field instanceof BelongsTo && $field->attribute === 'currentTeam') {
// ...
}
return $query;
}
如果您的应用程序正在利用 Laravel Scout 的强大功能进行 搜索,您也可以在将Laravel\Scout\Builder
查询实例发送到您的搜索提供程序之前对其进行定制。要实现这一点,请覆盖资源类上的scoutQuery
方法
/**
* Build a Scout search query for the given resource.
*
* @param \Laravel\Nova\Http\Requests\NovaRequest $request
* @param \Laravel\Scout\Builder $query
* @return \Laravel\Scout\Builder
*/
public static function scoutQuery(NovaRequest $request, $query)
{
return $query->where('user_id', $request->user()->id);
}