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

0.2.0 2015-09-16 08:28 UTC

This package is auto-updated.

Last update: 2024-08-29 04:03:22 UTC


README

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

我很难过地说,Lock 目前不再维护。在当前时期,我无法提供支持或接受新的贡献。其他优先事项使我无法将应有的工作投入到 Lock 中。最终,我会尝试重新开始工作,但很抱歉,我无法确定具体时间。感谢所有贡献者和用户。

-- Dries

Lock 是一个适用于 PHP 5.4+ 的灵活的基于驱动器的 Acl 包。

Dries Vints 创建。多亏了 BeatSwitch 的帮助。受 Authority 的启发,由 Matthew Machuga 开发。Logo 由 Jerry Low 设计。

目录

术语

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

特性

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

介绍

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

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

Driver 合同允许以简单的方式将权限存储到持久或静态存储系统中。本包附带默认的静态 ArrayDriver。查看以下列表以获取更多已为您准备好的驱动。或者通过实现 Driver 合约来构建自己的驱动。

您可以通过手动传递资源的类型和(可选的)标识符来设置和检查资源的权限,或者您可以在您的对象上实现 Resource 合约,以便更容易地传递它们。

Manager 允许以简单的方式实例化新的 Lock 实例、设置操作别名或注册角色。

驱动器

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

路线图

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

安装

通过 Composer 安装此包。

$ composer require beatswitch/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 的数据库组件将权限存储到数据库中。通过创建您自己的用户界面,您可以轻松地将此包的 acl 功能附加到创建用户管理系统,其中不同的用户有不同的权限。

让我们看看一个非常基本的用户管理控制器来了解它是如何完成的。我们将假设我们有一个带有 Laravel DB 驱动的启动锁管理器实例。

<?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" 方法调用都将验证为这个调用者为真。

使用角色

锁提供了一种简单的方式与角色一起工作。您可以直接使用角色,但如果要使用继承,则需要将角色注册到管理器实例。

$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');

这将引导一个SimpleRole对象,该对象已经内置了LockAware特质。

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, 可空)
  • resource_id (int, 11, 可空)

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

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

下面是驱动程序完整实现的示例。请注意,对于 getCallerPermissions 方法,我们正在使用 PermissionFactory 类来轻松映射数据并从它们创建 Permission 对象。PermissionFactorycreateFromData 方法将接受数组和对象。

<?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 维护。
如果您有任何问题,请随时 在 issue 中提出

贡献

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

变更日志

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

许可证

MIT 许可证。有关更多信息,请参阅 许可文件