dusta/lock

适用于PHP 5.6+的灵活、基于驱动器的Acl包

v0.3.0 2019-01-09 19:06 UTC

This package is auto-updated.

Last update: 2024-09-10 07:55:35 UTC


README

Build Status Code Climate Test Coverage Software License Packagist Version Total Downloads

Lock是一个灵活的、基于驱动器的Acl包,适用于PHP 5.6+

Dries Vints创建。感谢BeatSwitch使之成为可能。受到Authority的启发,由Matthew Machuga创建。标志由Jerry Low设计。

目录

术语

  • Lock:一个主题的acl实例。此包目前包含一个CallerLock和一个RoleLock
  • Caller:一个身份对象,可以进行某些操作并具有权限
  • Driver:权限的存储系统,可以是静态的或持久的
  • Permission:权限包含一个操作和一个可选的(唯一)资源。可以是RestrictionPrivilege
  • Restriction:限制阻止你执行操作(在可选资源上)
  • Privilege:特权允许你执行操作(在可选资源上)
  • Action:操作是你允许或拒绝执行的事情
  • Resource:资源可以是一个你可以执行一个或多个操作的对象。它可以是针对特定类型的资源或通过其唯一标识符指定的特定资源
  • Role:角色也可以包含多个权限。调用者可以有多个角色。角色可以从其他角色继承权限

特性

  • 为多个身份(调用者)提供灵活的acl权限
  • 静态或持久化驱动器用于存储权限
  • 动作别名
  • 角色
  • 条件(断言)
  • 使用特性轻松地在你的调用者或角色上实现acl功能

简介

Lock与其他acl包的不同之处在于试图提供最灵活的方式来处理多个权限调用者和存储权限。

通过使用Lock的Caller协议,你可以在多个身份上设置权限。

Driver协议允许以简单的方式将权限存储到持久化或静态存储系统中。此包包含一个默认的静态ArrayDriver。查看以下列表以获取更多为你准备好的驱动器。或者通过实现Driver协议来构建自己的驱动器。

你可以通过手动传递资源类型及其(可选)标识符来设置和检查资源权限,或者你可以将Resource协议实现到你的对象上,这样你就可以更容易地将它们传递给lock。

Manager 允许轻松创建新的 Lock 实例,设置操作别名或注册角色。

驱动器

如果您需要特定框架的实现,请选择以下已准备的驱动之一。

  • ArrayDriver(包含在此软件包中)
  • Laravel 5

路线图

  • 分组权限
  • 更多驱动程序(Symfony,Zend Framework,Doctrine,...)
  • 事件监听器

安装

通过 Composer 安装此软件包。

$ composer require dusta/lock

使用方法

实现调用者协议

每个应该具有执行某项操作权限的身份都必须实现 BeatSwitch\Lock\Callers\Caller 接口。该 Caller 接口通过要求它返回其类型和唯一标识符来识别调用者。下面看一个例子。

<?php

use BeatSwitch\Lock\Callers\Caller;

class User implements Caller
{
    public function getCallerType()
    {
        return 'users';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['editor', 'publisher'];
    }
}

通过添加 getCallerType 函数,我们可以通过唯一的类型来识别一组调用者。如果我们想在某个时候为另一组调用者设置权限,我们可以在另一个对象上轻松实现该协议。

<?php

use BeatSwitch\Lock\Callers\Caller;

class Organization implements Caller
{
    public function getCallerType()
    {
        return 'organizations';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['enterprise'];
    }
}

因此,我们可以通过驱动程序轻松检索特定调用者类型的权限。

使用静态驱动器

如果您希望在运行应用程序之前先配置所有权限,可以使用软件包中包含的静态 ArrayDriver。这允许您在应用程序运行之前为调用者设置权限列表。

use \BeatSwitch\Lock\Drivers\ArrayDriver;
use \BeatSwitch\Lock\Lock;
use \BeatSwitch\Lock\Manager;

// Create a new Manager instance.
$manager = new Manager(new ArrayDriver());

// Instantiate a new Lock instance for an object which implements the Caller contract.
$lock = $manager->caller($caller);

// Set some permissions.
$lock->allow('manage_settings');
$lock->allow('create', 'events');

// Use the Lock instance to validate permissions on the given caller.
$lock->can('manage_settings'); // true: can manage settings
$lock->can('create', 'events'); // true: can create events
$lock->cannot('update', 'events'); // true: cannot update events
$lock->can('delete', 'events'); // false: cannot delete events

使用持久化驱动器

使用 持久化驱动程序 可以将权限存储到持久化存储层,并在运行时进行调整。例如,如果您实现 Laravel 5 驱动程序,则它将使用 Laravel 的数据库组件将权限存储到数据库中。通过创建自己的 UI,您可以轻松地将此软件包的 acl 功能附加到创建用户管理系统,例如,不同的用户有不同的权限。

让我们看一下一个非常基本的管理控制器,看看它是如何实现的。我们将假设我们有一个用 Laravel 数据库驱动程序启动的锁管理器实例。

<?php

use BeatSwitch\Lock\Manager;

class UserManagementController extends BaseController
{
    protected $lockManager;

    public function __construct(Manager $lockManager)
    {
        $this->lockManager = $lockManager;
    }

    public function togglePermission()
    {
        $userId = Input::get('user');
        $action = Input::get('action');
        $resource = Input::get('resource');

        $user = User::find($userId);

        $this->lockManager->caller($user)->toggle($action, $resource);

        return Redirect::route('user_management');
    }
}

每次使用 togglePermission 方法时,用户的给定操作和资源类型的权限将被切换。

设置和检查权限

您可以允许或拒绝调用者执行某事。以下是一些设置和检查权限的方法。

允许调用者创建一切。

$lock->allow('create');

$lock->can('create'); // true

允许调用者仅创建帖子。

$lock->allow('create', 'posts');

$lock->can('create'); // false
$lock->can('create', 'posts'); // true

允许调用者仅编辑 ID 为 5 的特定帖子。

$lock->allow('edit', 'posts', 5);

$lock->can('edit'); // false
$lock->can('edit', 'posts'); // false
$lock->can('edit', 'posts', 5); // true

允许调用者编辑所有帖子,但拒绝他们编辑 ID 为 5 的帖子。

$lock->allow('edit', 'posts');
$lock->deny('edit', 'posts', 5);

$lock->can('edit', 'posts'); // true
$lock->can('edit', 'posts', 5); // false

切换权限的值。

$lock->allow('create');
$lock->can('create'); // true

$lock->toggle('create');
$lock->can('create'); // false

您可以同时允许或拒绝多个操作,并且也可以同时检查多个操作。

$lock->allow(['create', 'edit'], 'posts');

$lock->can('create', 'posts'); // true
$lock->can(['create', 'edit'], 'posts'); // true
$lock->can(['create', 'delete'], 'posts'); // false

清除权限

您可以轻松清除特定操作和资源组合的权限。

$lock->allow(['create', 'edit'], 'posts');

$lock->clear('edit', 'posts');

$lock->can('edit', 'posts'); // false
$lock->can('create', 'posts'); // true

您还可以仅清除锁实例的所有权限。

$lock->allow('manage-posts');
$lock->allow(['create', 'edit'], 'users');

$lock->clear();

$lock->can('manage-posts'); // false
$lock->can('create', 'users'); // false

设置动作别名

要一次设置多个操作并一起设置它们,您可能需要设置操作别名。

$lock->alias('manage', ['create', 'read', 'delete']);
$lock->allow('manage', 'posts');

$lock->can('manage', 'posts'); // true
$lock->can('create', 'posts'); // true
$lock->can('delete', 'posts', 1); // true
$lock->can('update', 'posts'); // false

设置上帝调用者

您可以通过在锁实例上传递 all 通配符作为操作,轻松设置具有所有权限的调用者。

$lock->allow('all');

现在,每次 "can" 方法调用都会验证为真为此调用者。

使用角色

Lock 提供了一种轻松处理角色的方法。您可以直接使用角色,但如果您想使用继承,则需要将角色注册到管理器实例中。

$manager->setRole('guest');
$manager->setRole('user', 'guest'); // "user" will inherit all permissions from "guest"

或一次性注册多个角色。

$manager->setRole(['editor', 'admin'], 'user'); // "editor" and "admin" will inherit all permissions from "user".

让我们设置一些权限,看看它们是如何解决的。

// Allow a guest to read everything.
$manager->role('guest')->allow('guest', 'read');

// Allow a user to create posts.
$manager->role('user')->allow('create', 'posts');

// Allow an editor and admin to publish posts.
$manager->role('editor')->allow('publish', 'posts');
$manager->role('admin')->allow('publish', 'posts');

// Allow an admin to delete posts.
$manager->role('admin')->allow('delete', 'posts');

// Let's assume our caller has the role of "editor" and check some permissions.
$lock = $manager->caller($caller);
$lock->can('read'); // true
$lock->can('delete', 'posts'); // false
$lock->can('publish'); // false: we can't publish everything, just posts.
$lock->can(['create', 'publish'], 'posts'); // true

您需要注意的事项是,调用者级别的权限优先于角色级别的权限。让我们看看这是如何工作的。

我们的调用者将拥有用户角色。

$manager->caller($caller)->allow('create', 'posts');

// Notice that we don't need to set the role in the
// manager first if we don't care about inheritance.
$manager->role('user')->deny('user', 'create', 'posts');

$manager->caller($caller)->can('create', 'posts'); // true: the user has explicit permission to create posts.

使用条件

条件实际上是断言,您可以为权限设置额外的检查。您可以将它们作为 allowdeny 的最后一个参数传递的数组。所有条件都必须实现 BeatSwitch\Lock\Permissions\Condition 接口。

警告:请注意,条件当前仅适用于静态驱动程序。

让我们设置一个条件。

<?php

use BeatSwitch\Lock\Lock;
use BeatSwitch\Lock\Permissions\Condition;
use BeatSwitch\Lock\Permissions\Permission;
use BeatSwitch\Lock\Resources\Resource;
use Illuminate\Auth\AuthManager;

class LoggedInCondition implements Condition
{
    /**
     * The Laravel AuthManager instance
     *
     * @var \Illuminate\Auth\AuthManager
     */
    protected $auth;

    /**
     * @param \Illuminate\Auth\AuthManager $auth
     */
    public function __construct(AuthManager $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Assert if the condition is correct
     *
     * @param \BeatSwitch\Lock\Lock $lock                         The current Lock instance that's being used
     * @param \BeatSwitch\Lock\Permissions\Permission $permission The Permission that's being checked
     * @param string $action                                      The action passed to the can or cannot method
     * @param \BeatSwitch\Lock\Resources\Resource|null $resource  The resource passed to the can or cannot method
     * @return bool
     */
    public function assert(Lock $lock, Permission $permission, $action, Resource $resource = null)
    {
        // Condition will succeed if the user is logged in.
        return $this->auth->check();
    }
}

现在,让我们看看在设置权限时这将如何工作。

$condition = App::make('LoggedInCondition');

$lock->allow('create', 'posts', null, $condition);
$lock->can('create', 'posts'); // true if logged in, otherwise false.

您还可以传递多个条件。

$lock->allow('create', 'posts', null, [$falseCondition, $trueCondition]);
$lock->can('create', 'posts'); // false: there's at least one false condition

您可以传递任意多的条件,但它们都必须成功,权限才能生效。

您也可以使用回调函数。

$lock->allow('create', 'posts', null, function ($lock, $permission, $action, $resource = null) {
    return false;
});
$lock->can('create', 'posts'); // false because the callback returns false.

检索允许或拒绝的资源

如果您想检索允许或拒绝执行特定操作的资源的列表,您可以在 Lock 实例上使用 alloweddenied 方法。

$lock->allow('update', 'users', 1);
$lock->allow('update', 'users', 2);
$lock->allow('update', 'users', 3);
$lock->deny('update', 'users', 2);

$lock->allowed('update', 'users'); // Returns [1, 3];
$lock->denied('update', 'users'); // Returns [2];

请注意,您只能从已设置权限的资源检索 ID。未通过 Lock 注册的资源不会返回。

使用LockAware特性

您可以通过实现 BeatSwitch\Lock\LockAware 特性,轻松地将 acl 功能添加到调用者或角色。

<?php

use BeatSwitch\Lock\Callers\Caller;
use BeatSwitch\Lock\LockAware;

class Organization implements Caller
{
    use LockAware;

    public function getCallerType()
    {
        return 'organizations';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['enterprise'];
    }
}

现在我们需要设置它的锁实例。

$caller->setLock($lock);

现在您的调用者可以使用所有锁方法。

$caller->can('create', 'posts');
$caller->allow('edit', 'pages');

如果您有一个实现了 LockAware 特性的调用者但尚未启动调用者的锁实例,您可以使用管理器的 makeCallerLockAware 方法轻松地使调用者具有锁意识。

$caller = $manager->makeCallerLockAware($caller);

现在您的调用者将能够使用 LockAware 方法。对于角色也有类似的方法。

$role = $manager->makeRoleLockAware('guest');

这将启动一个已经包含 LockAware 特性的 SimpleRole 对象。

Api

BeatSwitch\Lock\Lock

以下方法都可以在 BeatSwitch\Lock\Lock 实例上调用。

can

检查当前调用者是否有权限做某事。

can(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

cannot

检查当前调用者是否被禁止做某事。

cannot(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

allow

为调用者设置 Privilege 权限,允许它做某事。移除任何匹配的限制。

allow(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null,
    \BeatSwitch\Lock\Permissions\Condition[] $conditions = []
)

deny

为调用者设置 Restriction 权限,阻止它做某事。移除任何匹配的权限。

deny(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null,
    \BeatSwitch\Lock\Permissions\Condition[] $conditions = []
)

toggle

切换给定权限的值。

toggle(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

allowed

返回一个数组中所有给定资源类型 ID,允许主题执行给定操作。

allowed(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resourceType
)

denied

返回一个数组中所有给定资源类型 ID,禁止主题执行给定操作。

denied(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resourceType
)

BeatSwitch\Lock\Manager

以下方法都可以在 BeatSwitch\Lock\Manager 实例上调用。

caller

为调用者返回一个 BeatSwitch\Lock\Lock 实例。

caller(
    \BeatSwitch\Lock\Callers\Caller $caller
)

role

为角色返回一个 BeatSwitch\Lock\Lock 实例。

role(
    \BeatSwitch\Lock\Roles\Role $role
)

alias

为一个或多个操作添加别名。

alias(
    string $name,
    string|array $actions
)

setRole

设置一个或多个角色和可选的角色以继承权限。

setRole(
    string|array $name,
    string $inherit = null
)

makeCallerLockAware

为实现了 LockAware 特性的调用者设置锁实例。返回设置了锁实例的调用者。

makeCallerLockAware(
    \BeatSwitch\Lock\Callers\Caller $caller
)

makeRoleLockAware

为实现了 LockAware 特性的角色设置锁实例。返回设置了锁实例的角色。

makeRoleLockAware(
    \BeatSwitch\Lock\Roles\Role|string $role
)

构建驱动器

您可以通过实现 BeatSwitch\Lock\Drivers\Driver 合约来轻松构建一个驱动程序。以下我们将演示如何使用 Laravel 的 Eloquent ORM 作为存储机制来创建我们自己的持久化驱动程序。

我们假设我们有一个 CallerPermission 模型类,至少有以下数据库列

  • caller_type (varchar, 100)
  • caller_id (int, 11)
  • type (varchar, 10)
  • action (varchar, 100)
  • resource_type (varchar, 100, nullable)
  • resource_id (int, 11, nullable)

并且我们有一个 RolePermission 模型,以下数据库列

  • role (varchar, 100)
  • type (varchar, 10)
  • action (varchar, 100)
  • resource_type (varchar, 100, nullable)
  • resource_id (int, 11, nullable)

下面我们将查看驱动程序的完整实现。注意,对于 getCallerPermissions 方法,我们使用 PermissionFactory 类来轻松映射数据并从它们创建 Permission 对象。`PermissionFactory` 的 createFromData 方法可以接受数组和对象。

<?php

use BeatSwitch\Lock\Callers\Caller;
use BeatSwitch\Lock\Drivers\Driver;
use BeatSwitch\Lock\Permissions\Permission;
use BeatSwitch\Lock\Permissions\PermissionFactory;
use BeatSwitch\Lock\Roles\Role;
use CallerPermission;
use RolePermission;

class EloquentDriver implements Driver
{
    /**
     * Returns all the permissions for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @return \BeatSwitch\Lock\Permissions\Permission[]
     */
    public function getCallerPermissions(Caller $caller)
    {
        $permissions = CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->get();

        return PermissionFactory::createFromData($permissions->toArray());
    }

    /**
     * Stores a new permission into the driver for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function storeCallerPermission(Caller $caller, Permission $permission)
    {
        $eloquentPermission = new CallerPermission;
        $eloquentPermission->caller_type = $caller->getCallerType();
        $eloquentPermission->caller_id = $caller->getCallerId();
        $eloquentPermission->type = $permission->getType();
        $eloquentPermission->action = $permission->getAction();
        $eloquentPermission->resource_type = $permission->getResourceType();
        $eloquentPermission->resource_id = $permission->getResourceId();
        $eloquentPermission->save();
    }

    /**
     * Removes a permission from the driver for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function removeCallerPermission(Caller $caller, Permission $permission)
    {
        CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->delete();
    }

    /**
     * Checks if a permission is stored for a user
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return bool
     */
    public function hasCallerPermission(Caller $caller, Permission $permission)
    {
        return (bool) CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->first();
    }

    /**
     * Returns all the permissions for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @return \BeatSwitch\Lock\Permissions\Permission[]
     */
    public function getRolePermissions(Role $role)
    {
        $permissions = RolePermission::where('role', $role->getRoleName())->get();

        return PermissionFactory::createFromData($permissions->toArray());
    }

    /**
     * Stores a new permission for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function storeRolePermission(Role $role, Permission $permission)
    {
        $eloquentPermission = new RolePermission;
        $eloquentPermission->role = $role->getRoleName();
        $eloquentPermission->type = $permission->getType();
        $eloquentPermission->action = $permission->getAction();
        $eloquentPermission->resource_type = $permission->getResourceType();
        $eloquentPermission->resource_id = $permission->getResourceId();
        $eloquentPermission->save();
    }

    /**
     * Removes a permission for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function removeRolePermission(Role $role, Permission $permission)
    {
        RolePermission::where('role', $role->getRoleName())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->delete();
    }

    /**
     * Checks if a permission is stored for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return bool
     */
    public function hasRolePermission(Role $role, Permission $permission)
    {
        return (bool) RolePermission::where('role', $role->getRoleName())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->first();
    }
}

请注意,当我们尝试存储权限时,并没有检查权限是否已经存在。您无需担心这个问题,因为所有这些操作都在Lock实例中为您完成了。

现在我们有一个支持存储调用者权限和角色的驱动程序。

测试您的驱动器

确保您的驱动程序按预期工作非常简单。如果您正在构建持久化驱动程序,您可以通过创建一个继承自PersistentDriverTestCase类的PHPUnit测试来轻松地进行测试。

<?php

use BeatSwitch\Lock\Tests\PersistentDriverTestCase;

class EloquentDriverTest extends PersistentDriverTestCase
{
    public function setUp()
    {
        // Don't forget to reset your DB here.
        
        // Bootstrap your driver.
        $this->driver = new EloquentDriver();

        parent::setUp();
    }
}

这就是您需要的一切!PersistentDriverTestCase包含了您确保驱动程序按预期工作的所有测试。因此,如果所有这些测试都通过,那么您的驱动程序设置正确。无需进行任何模拟,这是一个纯粹的集成测试用例。当然,在这个具体的例子中,为了Eloquent能够工作,您需要启动Laravel。使用类似sqlite这样的数据库进行驱动程序的测试将是最佳方式。

维护者

Lock目前未维护。

此包目前由Dries Vints维护。
如果您有任何问题,请不要犹豫,请在问题中提问

贡献

有关详细信息,请参阅贡献文件

变更日志

您可以在我们的变更日志文件中查看每个版本的更改列表。

许可证

MIT许可。请参阅许可文件以获取更多信息。