logo

授权

当只有您或您的开发团队可以访问 Nova 时,您可能不需要在 Nova 处理传入请求之前进行额外的授权。但是,如果您向您的客户或大型开发人员团队提供对 Nova 的访问权限,您可能希望授权某些请求。例如,也许只有管理员才能删除记录。值得庆幸的是,Nova 采用了一种简单的方法来进行授权,它利用了您已经熟悉的许多 Laravel 功能。

策略

为了限制哪些用户可以查看、创建、更新或删除资源,Nova 利用了 Laravel 的 授权策略。策略是简单的 PHP 类,用于组织特定模型或资源的授权逻辑。例如,如果您的应用程序是一个博客,您可能有一个 Post 模型和一个相应的 PostPolicy 在您的应用程序中。

在 Nova 中操作资源时,Nova 将自动尝试为该模型找到相应的策略。如果 Nova 检测到已为该模型注册了策略,它将在执行各自的操作(例如)之前自动检查该策略的相关授权方法。

  • viewAny
  • view
  • create
  • update
  • replicate
  • delete
  • restore
  • forceDelete

不需要额外的配置!因此,例如,要确定哪些用户可以更新 Post 模型,您只需在模型的相应策略类中定义一个 update 方法。

php
<?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回退到 createupdate
delete禁止
forceDelete禁止
restore禁止
add{Model}允许
attach{Model}允许
attachAny{Model}允许
detach{Model}允许
runAction回退到 update
runDestructiveAction回退到 delete

因此,如果您已定义策略,请不要忘记定义所有相关的授权方法,以便对给定资源的授权规则是明确的。

隐藏整个资源

如果您想从仪表板用户的子集中隐藏整个 Nova 资源,您可以在模型的策略类中定义 viewAny 方法。如果未为给定策略定义 viewAny 方法,Nova 将假定用户可以查看资源。

php
<?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 内部启动请求与从主应用程序启动请求时以不同的方式授权操作,您可以在策略中使用 Nova 的 whenServing 方法。此方法允许您仅在请求是 Nova 请求时执行给定的回调。可以提供一个额外的回调,该回调将在非 Nova 请求时执行。

php
<?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
<?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
<?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
<?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 方法

php
/**
 * Determine if the given resource is authorizable.
 *
 * @return bool
 */
public static function authorizable()
{
    return false;
}

字段

有时您可能希望对某些用户隐藏某些字段。您可以通过将 canSee 方法链接到您的字段定义来轻松实现此目的。canSee 方法接受一个闭包,该闭包应返回 truefalse。闭包将接收传入的 HTTP 请求

php
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 方法相同的函数签名

php
Text::make('Name')
        ->sortable()
        ->canSeeWhen('viewProfile', $this),

授权和“Can”方法

要了解有关 Laravel 授权助手和 can 方法的更多信息,请查看完整的 Laravel 授权文档

索引过滤

您可能会注意到,从策略的 view 方法返回 false 不会阻止给定资源出现在资源索引中。要从资源索引查询中过滤模型,您可以覆盖资源类上的 indexQuery 方法。

此方法已在您的应用程序的 App\Nova\Resource 基类中定义;因此,您只需将该方法复制粘贴到特定资源中,然后根据您希望过滤资源索引结果的方式修改查询

php
/**
 * 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方法

php
/**
 * 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方法来定制此关系的关联查询

php
/**
 * 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实例访问请求的resourceresourceId

php
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资源通过多个字段依赖于另一个资源时,您通常会为这些字段分配不同的名称,例如

php
BelongsTo::make('Current Team', 'currentTeam', Team::class),
HasMany::make('Owned Teams', 'ownedTeams', Team::class),
BelongsToMany::make('Teams', 'teams', Team::class),

在这些情况下,您应该在定义关系时提供第三个参数来指定关系应该使用哪个Nova资源,因为Nova可能无法通过约定确定这一点

php
/**
 * 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;
}

Scout 过滤

如果您的应用程序正在利用 Laravel Scout 的强大功能进行 搜索,您也可以在将Laravel\Scout\Builder查询实例发送到您的搜索提供程序之前对其进行定制。要实现这一点,请覆盖资源类上的scoutQuery方法

php
/**
 * 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);
}