mimmi20/mezzio-generic-authorization-rbac

为mezzio-generic-authorization提供laminas-permissions-rbac适配器。

3.0.4 2024-09-10 07:40 UTC

README

Latest Stable Version Latest Unstable Version License

代码状态

codecov Test Coverage Average time to resolve an issue Percentage of issues still open Mutation testing badge Maintainability

此库为mezzio-generic-authorization提供laminas-rbac适配器。

安装

您可以使用Composer安装mezzio-generic-authorization-rbac库。

composer require mimmi20/mezzio-generic-authorization-rbac

介绍

此组件为基于角色的访问控制 (RBAC) 授权抽象提供了 mezzio-generic-authorization 库。

RBAC基于角色的概念。在一个Web应用中,用户有一个身份(例如,用户名,电子邮件等)。然后每个已标识的用户都有一个或多个角色(例如,管理员,编辑器,访客)。每个角色都有一个执行一个或多个操作(例如,访问URL,执行特定的Web API调用)的权限。

在一个典型的RBAC系统中

  • 身份有一个或多个角色。
  • 角色请求访问一个权限。
  • 权限被赋予一个角色。

因此,RBAC有以下模型

  • 身份和角色之间的多对多关系。
  • 角色和权限之间的多对多关系。
  • 角色可以有父角色。

RBAC系统的第一个要求是身份。在我们的场景中,用户由mezzio-authentication提供的认证系统生成。该库在用户认证时提供一个名为 Mezzio\Authentication\UserInterface 的PSR-7请求属性。RBAC系统使用此实例来获取有关用户身份的信息。

配置RBAC系统

您可以使用以下配置文件配置您的RBAC

// config/autoload/authorization.local.php
return [
    // ...
    'mezzio-authorization-rbac' => [
        'roles' => [
            'administrator' => [],
            'editor'        => ['administrator'],
            'contributor'   => ['editor'],
        ],
        'permissions' => [
            'contributor' => [
                'admin.dashboard',
                'admin.posts',
            ],
            'editor' => [
                'admin.publish',
            ],
            'administrator' => [
                'admin.settings',
            ],
        ],
    ],
];

在上面的示例中,我们设计了一个包含3个角色(管理员,编辑器,贡献者)的RBAC系统。我们定义了以下角色层次结构

  • 管理员没有父角色。
  • 编辑器的父角色是管理员。这意味着管理员继承了编辑器的权限。
  • 贡献者的父角色是编辑器。这意味着编辑器继承了贡献者的权限,按照此链,管理员继承了贡献者的权限。

对于每个角色,我们指定了一个权限数组。如您所注意到的,权限只是一个字符串;它可以代表任何东西。在我们的实现中,这个字符串代表路由名称。这意味着贡献者角色可以访问admin.dashboardadmin.posts路由,但不能访问分配给编辑器角色的admin.publish路由和分配给管理员角色的admin.settings路由。

如果您想为每个权限更改授权逻辑,您可以编写自己的Mimmi20\Mezzio\GenericAuthorization\AuthorizationInterface实现。该接口定义了以下方法

public function isGranted(string $role, string $resource, ?string $privilege = null, ?\Psr\Http\Message\ServerRequestInterface\ServerRequestInterface $request = null): bool;

其中 $role 是角色,$resource 是资源,$privilege 是权限,$request 是用于授权的PSR-7 HTTP请求。

本库使用 laminas/laminas-permissions-rbac 库实现 RBAC 系统。此 RBAC 实现不支持权限。如果您想了解更多关于此库的用法,请阅读博客文章 使用 laminas-permissions-rbac 管理权限

动态断言

在某些情况下,您可能需要根据特定的 HTTP 请求授权一个角色。例如,假设您有一个“编辑”角色,该角色可以添加/更新/删除内容管理系统 (CMS) 中的页面。我们希望阻止“编辑”修改他们未创建的页面。

这种类型的授权称为 动态断言,并且通过 laminas-permissions-rbacLaminas\Permissions\Rbac\AssertionInterface 实现。

为了使用它,此包提供了 LaminasRbacAssertionInterface,它扩展了 Laminas\Permissions\Rbac\AssertionInterface

namespace Mezzio\Authorization\Rbac;

use Psr\Http\Message\ServerRequestInterface;
use Laminas\Permissions\Rbac\AssertionInterface;

interface LaminasRbacAssertionInterface extends AssertionInterface
{
    public function setRequest(ServerRequestInterface $request) : void;
}

Laminas\Permissions\Rbac\AssertionInterface 定义了以下内容

namespace Laminas\Permissions\Rbac;

interface AssertionInterface
{
    public function assert(Rbac $rbac, RoleInterface $role, string $permission) : bool;
}

回到我们的用例,我们可以构建一个类来管理“编辑”授权要求,如下所示

use Mimmi20\Mezzio\GenericAuthorization\Rbac\LaminasRbacAssertionInterface;
use App\Service\Article;
use Laminas\Permissions\Rbac\Rbac;
use Laminas\Permissions\Rbac\RoleInterface;
use Psr\Http\Message\ServerRequestInterface;

class EditorAuth implements LaminasRbacAssertionInterface
{
    public function __construct(Article $article)
    {
        $this->article = $article;
    }

    public function setRequest(ServerRequestInterface $request): void
    {
        $this->request = $request;
    }

    public function assert(Rbac $rbac, RoleInterface $role, string $permission): bool
    {
        $user = $this->request->getAttribute(UserInterface::class, false);
        return $this->article->isUserOwner($user->getIdentity(), $this->request);
    }
}

其中 Article 是一个类,它检查被识别的用户是否是 HTTP 请求中引用的文章的所有者。

如果您使用 SQL 数据库管理文章,则 isUserOwner() 的实现可能如下所示

public function isUserOwner(string $identity, ServerRequestInterface $request): bool
{
    // get the article {article_id} attribute specified in the route
    $url = $request->getAttribute('article_id', false);
    if (! $url) {
        return false;
    }
    $sth = $this->pdo->prepare(
        'SELECT * FROM article WHERE url = :url AND owner = :identity'
    );
    $sth->bindParam(':url', $url);
    $sth->bindParam(':identity', $identity);
    if (! $sth->execute()) {
        return false;
    }
    $row = $sth->fetch();
    return ! empty($row);
}

要将 Article 依赖项传递到您的断言中,您可以使用一个 Factory 类来生成 EditorAuth 类的实例,如下所示

use App\Service\Article;

class EditorAuthFactory
{
    public function __invoke(ContainerInterface $container) : EditorAuth
    {
        return new EditorAuth(
            $container->get(Article::class)
        );
    }
}

然后配置服务容器使用 EditorAuthFactory 指向 EditorAuth,如下所示

return [    
    'dependencies' => [
        'factories' => [
            // ...
            EditorAuth::class => EditorAuthFactory::class
        ]
    ]
];

许可协议

此包使用 MIT 许可协议授权。

请参阅 LICENSE.md