当访问 Nova 的用户只有你和你或者公司内部的开发团队时,Nova 接收请求之前你是不需要添加额外的授权的。然而,如果你想要提供给客户或者大型开发团队去访问你的客户端时,你可能希望对某些请求加上权限。例如,只希望管理员可以删除记录。值得高兴的是,Nova 提供了一种简单的授权方法,这种方法正如你所了解的 Laravel 中的许多功能一样。
为了限制用户的哪些用户可以查看、创建、更新、或者删除资源,Nova 利用了 Laravel 授权策略,策略是一个用于管理特定模型或者资源授权逻辑的简单类。例如,如果你的程序是一个博客,你可能有一个 Post 模型和相应的 PostPolicy 在你的程序中。
在 Nova 中操作资源时, Nova 会自动的尝试为模型找相应的策略。通常,这些策略会在你应用程序的 AuthServiceProvider 中注册。如果 Nova 监测到你已经为模型注册了策略,他将在执行各自的操作前,自动检查该策略的相关授权方法,例如:
viewAnyviewcreateupdatereplicatedeleterestoreforceDelete不需要额外的配置!例如,要确定允许哪些用户能够更新 Post 模型,你只需在相应的策略类上定义一个 update 方法:
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;
class PostPolicy
{
use HandlesAuthorization;
/**
* 检查用户是否能够更新帖子模型
*
* @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 | Allowed 允许 |
view | Forbidden 禁止 |
create | Forbidden 禁止 |
update | Forbidden 禁止 |
replicate | Fallback to create and update 转到 |
delete | Forbidden 禁止 |
forceDelete | Forbidden 禁止 |
restore | Forbidden 禁止 |
add{Model} | Allowed 允许 |
attach{Model} | Allowed 允许 |
attachAny{Model} | Allowed 允许 |
detach{Model} | Allowed 允许 |
runAction | Fallback to update 转到 |
runDestructiveAction | Fallback to 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;
/**
* 确定用户是否可以查看任何帖子。
*
* @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;
/**
* 确定用户是否可以查看任何帖子。
*
* @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;
/**
* 确定用户是否可以在播客中添加评论。
*
* @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 模型有多对多的关系。如果你想授权哪些用户可以将 "标签" 附加到 Podcast 上,您可以在 podcast 策略中添加一个 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;
/**
* 确定用户是否可以将标签附加到播客上。
*
* @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;
}
/**
* 确定用户是否可以从播客中分离标签。
*
* @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} 方法。这样将导致 "Attach" 按钮的不会在 Nova UI 实体上的显示 :
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Podcast;
use Illuminate\Auth\Access\HandlesAuthorization;
class PodcastPolicy
{
use HandlesAuthorization;
/**
* 确定用户是否可以将任何标签附加到播客。
*
* @param \App\Models\User $user
* @param \App\Models\Podcast $podcast
* @return mixed
*/
public function attachAnyTag(User $user, Podcast $podcast)
{
return false;
}
}多对多的授权
在处理多对多的关系时,请确保在每个涉及的资源的策略类上定义适当的授权策略方法。
如果你的某个 Nova 的资源模型已经有相应的策略,但是你希望禁用该资源的 Nova 授权,我们可以覆盖 authorizable Nova 资源策略类的方法:
/**
* 确定给定的资源是否可授权。
*
* @return bool
*/
public static function authorizable()
{
return false;
}有时候,你可能希望对某些用户组隐藏某些字段。你可以通过 canSee 方法链接到字段的定义,轻松完成这个操作。canSee 该方法接收一个返回 true 或 false 的一个闭包函数。然而这个闭包函数接收 HTTP 的请求对象作为参数:
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
/**
* 获取资源显示的字段。
*
* @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 trait 的 can 方法来确定 User 模型中的 viewProfile 操作是否授权给授权用户。不过,由于代理授权策略方法是 canSee 的常见用例,你可以使用 canSeeWhen 方法来实现相同的行为。canSeeWhen 方法与 Illuminate\Foundation\Auth\Access\Authorizable trait 的 can 方法具有相同的方法签名:
Text::make('Name')
->sortable()
->canSeeWhen('viewProfile', $this),授权与「Can」方法
想了解更多关于 Laravel 授权和 can 方法的信息,请查看 授权文档。
你可能会注意到,从策略的 view 方法返回 false 并不能阻止给定资源出现在资源列表索引中。要从资源列表索引查询中过滤模型,可以覆盖资源类上的 indexQuery 方法。
该方法已在应用程序的 App\Nova\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);
}如果你希望过滤数据在关联数据中的展示,你可以在 Nova 资源中覆盖 relatableQuery 方法。
例如,如果你的应用程序中有一个 Comment 资源属于一个 Podcast 资源。在创建一个 Comment 资源时,Nova 将允许你选择他归属的 Podcast 资源。为了在选项中限制用户能选择的 Podcast,你应该在 Podcast 资源中覆盖 relatableQuery 方法:
/**
* 为给定资源建立 "可关联 "查询。
*
* 该查询可确定模型的哪些实例可附加到其他资源。
*
* @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 方法来为这种关系定制可关联查询:
/**
* 为给定资源建立「可关联」查询。
*
* 该查询可确定模型的哪些实例可附加到其他资源。
*
* @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'); // 资源类型...
$resourceId = $request->resourceId; // 资源 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 可能无法通过惯例确定这一点:
/**
* 为给定资源建立「可关联」查询。
*
* 该查询可确定模型的哪些实例可附加到其他资源。
*
* @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 方法:
/**
* 为给定资源建立 Scout 搜索查询。
*
* @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);
}