stevie-mccomb/warden

Laravel的一个基于能力的角色系统。

dev-trunk 2024-07-25 19:33 UTC

This package is not auto-updated.

Last update: 2024-09-19 15:14:20 UTC


README

Laravel Code Coverage: 100% SemVer

Warden

Laravel的能力型安全系统。

轻松地从配置文件生成一组角色和能力,然后通过数据库关系将它们分配给用户。这对于构建支持通过UI编辑的自定义角色的Web应用非常有用。

安装

composer require stevie-mccomb/warden

发布资源

php artisan vendor:publish --tag=warden

这将发布内置迁移和配置文件。将要发布的文件包括

// Migrations
database/migrations/{date}_create_capabilities_table.php
database/migrations/{date}_create_roles_tables.php
database/migrations/{date}_create_capability_capability_tables.php
database/migrations/{date}_create_capability_role_tables.php

// Config
config/warden.php

快速入门/基本用法

  1. 运行内置迁移。
php artisan migrate
  1. 配置您的能力和角色。
// config/warden.php

'capabilities' => [
    [ 1, 'Manage Users', 'manage-users' ],
    [ 2, 'Create Posts', 'create-posts' ],
    [ 3, 'Publish Posts', 'publish-posts' ],
    [ 4, 'Archive Posts', 'archive-posts' ],
],

'roles' => [
    [ 1, 'Administrator', 'admin' ],
    [ 2, 'Editor', 'editor' ],
],

'capability_role_map' => [
    'admin' => '*', // grant all capabilities
    'editor' => [ // grant specific capabilities
        'create-posts',
        'publish-posts',
        'archive-posts',
    ],
],
  1. 使用您的配置的能力/角色更新数据库。
php artisan warden:update
  1. Stevie\Warden\HasRoles特性添加到您的App\Models\User模型。
use Stevie\Warden\Traits\HasRoles;

class User
{
    use HasRoles;
}
  1. 将角色分配给您的用户。
use Stevie\Warden\Models\Role;

$user->roles()->sync(
    Role::where('slug', 'editor')->pluck('id')
);
  1. 检查用户能力。
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController
{
    public function publish(Request $request)
    {
        // Using a gate:
        if (!Gate::allows('publish-posts')) {
            abort(403);
        }

        // Using Laravel's user capability method:
        if (!$request->user()->can('publish-posts')) {
            abort(403);
        }

        // Or manually, if complex logic is needed:
        if (!$request->user()->capabilities()->where('slug', 'publish-posts')->exists()) {
            abort(403);
        }

        // ...
    }
}

注意:Warden不是Laravel的策略的替代品,而是与其一起使用。您可以单独使用Warden的全局安全,或者在其策略方法中使用。请参阅使用示例部分以获取这两种用例的示例。

配置

php artisan vendor:publish --tag=warden

这将发布一个位于config/warden.php的配置文件,可以编辑以控制Warden的行为。启动配置文件包含以下字段

能力

您可以使用此选项定义要授予应用程序用户的权限列表。Warden提供了一个全局安全,可用于检查已认证用户是否有权执行某些操作。

角色

使用此选项来定义充当一组能力包装器的角色。这种基于角色的系统是Warden的核心,它使得将一组能力分组并批量分配给用户变得容易。它还使得在创建新能力时,只需更新用户的角色即可轻松更新特定用户组的能力变得容易。

能力/角色映射

此配置选项跟踪能力和角色之间的关系。使用此选项确定哪些角色授予用户哪些能力。

能力依赖映射

这是一个可选功能,允许您将能力作为其他能力的依赖项分配。虽然在默认安装中这个功能在表面上不起作用,但您可以在自己的UI或后端代码中使用此功能来控制可能相互依赖的必要能力的授予。

例如,假设您的应用程序有一个专门的行政面板,其中包含所有行政页面。您的应用程序的行政操作之一可能是用户管理。您可能有一个view-admin能力和一个manage-users能力,但是您不能在没有访问行政区域的情况下管理用户,因此对于manage-users来说,依赖于view-admin是有意义的,并且当manage-users被授予时,您的应用程序应自动授予此依赖项。

如果您想在您的逻辑中同步功能到角色时使用此功能,有一个名为 syncWithDependencies 的辅助方法,该方法在从 Role::capabilities 返回的关系中提供,这使得在没有在 UI 中选择所有依赖项的情况下同步功能及其依赖项变得容易,尽管仍然建议您在 UI 中选择依赖项以获得更好的用户体验并避免用户混淆。下面是使用 syncWithDependencies 的示例。

// config/warden.php
'capability_dependency_map' => [
    'update-posts' => [
        'index-posts',
    ],
],

---

// Controller
use Stevie\Warden\Models\Capability;
use Stevie\Warden\Models\Role;

$capabilities = Capability::where('slug', 'update-posts')->value('id');

$role = Role::where('slug', 'example-role')->first();

$role->capabilities()->syncWithDependencies($capabilities);

dd($role->capabilities->toArray()); // [ 'view-posts', 'update-posts' ]

数据库表

如果需要,您可以更改 Warden 默认表的名称。数组的键不应更改,因为这些是 Warden 对表的内部标识符;值是表名称。

类映射

如果您想覆盖或替换包内部分功能,可以在此类映射中替换其中一个类路径为您自己的类。在运行时,Warden 将使用配置文件类映射来确定哪些类应该处理其功能。

广播频道

两个内置的 Laravel 模型(功能和角色)使用 Laravel 文档中定义的所有 模型事件,并且每当模型被检索、更新、删除等时自动分发这些事件。您可以通过以下两种方式使用这些事件:

  1. 将您自己的 监听器 绑定到它们。
  2. 使用 Laravel Echo 在前端监听它们。

如果您想使用 Laravel Echo,您可以配置此选项以使用应用程序偏好的任何广播频道。

有关应用程序可以监听的所有事件的完整列表,请查看包的 src/Events 目录。

使用示例

将角色分配给您的用户

use Illuminate\Http\Request;

class ExampleController
{
    public function updateUserRoles(Request $request, User $user)
    {
        $rolesTable = config('warden.tables.roles');

        $safe = $request->validate([
            'roles' => 'required|array',
            'roles.*' => "required|exists:$rolesTable,id",
        ]);

        $user->roles()->sync($safe['roles']);
    }
}

使用网关检查用户功能

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class ExampleController
{
    public function example(Request $request)
    {
        if (!Gate::allows('example-capability')) {
            abort(403);
        }

        // ...
    }
}

通过 Laravel 方法检查用户功能

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class ExampleController
{
    public function example(Request $request)
    {
        if (!$request->user()->can('example-capability')) {
            abort(403);
        }

        // ...
    }
}

手动检查用户功能

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class ExampleController
{
    public function example(Request $request)
    {
        if (
            !$request->user()->capabilities()
                ->where('slug', 'example-capability')
                ->exists()
        ) {
            abort(403);
        }

        // ...
    }
}

在策略中检查用户功能

use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

class PostPolicy
{
    public function publish(User $user, Post $post): bool
    {
        return $user->can('publish-posts') && $user->is($post->user);
    }
}

在您的应用程序中允许用户定义的角色

首先,覆盖内置的角色种子器以防止用户定义的角色在未来数据库更新中被覆盖。您需要定义自己的逻辑来指定如何播种这些表(或者如果您不打算使用内置角色,只需创建空种子器即可)。

// config/warden.php

'class_map' => [
    'seeders' => [
        // ...
        'roles' => \Database\Seeders\RolesTableSeeder::class,
        'capability_role' => \Database\Seeders\CapabilityRoleTableSeeder::class,
    ],

    // ...
],

一种常见的方法是使用预留的角色名称,如“超级管理员”或“客户管理员”,在您的后端中播种,并在应用程序中有特殊逻辑,同时允许一些用户(例如客户管理员)定义自己的角色。

这样,您的种子器可以播种应用程序的预留角色,而您的用户可以创建自己的角色,它们可以共存并使用 Warden 的内部逻辑。下面是一个示例自定义种子器。

use Illuminate\Support\Database\Schema;

class ExampleRolesTableSeeder
{
    public function run()
    {
        $roles = config('warden.tables.roles');

        DB::statement("
            INSERT INTO
                $roles (`name`, `slug`)
            VALUES
                (1, 'Super Administrator', 'super-administrator'),
                (2, 'Client Administrator', 'client-administrator')
            ON DUPLICATE KEY UPDATE
                `name` = VALUES(`name`),
                `slug` = VALUES(`slug`),
                `updated_at` = CURRENT_TIMESTAMP;
        ");
    }
}

在上面的示例中,这些 ID 为 1 和 2 的两个角色可以在用户定义自己的角色之前添加到您的应用程序中,并在您运行 artisan warden:update 命令时播种。如果您想防止用户使用相同的名称定义自己的角色,可以在您的验证逻辑中实现这一点。

接下来,创建用于定义角色并将其附加到用户的路由和控制器。

// e.g. POST /admin/roles
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Stevie\Warden\Models\Role;

class RoleController
{
    /**
     * Create a new role with the given capabilities
     * and attach it to the given users.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate the input.
        $capabilitiesTable = config('warden.tables.capabilities');
        $rolesTable = config('warden.tables.roles');
        $safe = $request->validate([
            'name' => "required|string|max:255|unique:$rolesTable",
            'slug' => "required|string|max:255|unique:$rolesTable",
            'capabilities' => 'nullable|array',
            'capabilities.*' => "required|integer|exists:$capabilitiesTable",
            'users' => 'nullable|array',
            'users.*' => 'required|integer|exists:users',
        ]);

        // Create the role.
        $role = Role::create([
            'name' => $safe['name'],
            'slug' => $safe['slug'],
        ]);

        // Attach the capabilities to the role.
        $role->capabilities()->sync($safe['capabilities']);

        // Attach the role to the given users.
        $role->users()->sync($safe['users']);

        // Redirect.
        return to_route('roles.index')
            ->with('success', 'Role successfully created.');
    }
}