beatswitch / lock
适用于 PHP 5.4+ 的灵活的基于驱动器的 Acl 包
Requires
- php: >=5.4.0
Requires (Dev)
- codeclimate/php-test-reporter: ^0.1.2
- phpspec/phpspec: ~2.1
- phpunit/phpunit: ~4.0
This package is auto-updated.
Last update: 2024-08-29 04:03:22 UTC
README
我很难过地说,Lock 目前不再维护。在当前时期,我无法提供支持或接受新的贡献。其他优先事项使我无法将应有的工作投入到 Lock 中。最终,我会尝试重新开始工作,但很抱歉,我无法确定具体时间。感谢所有贡献者和用户。
-- Dries
Lock 是一个适用于 PHP 5.4+ 的灵活的基于驱动器的 Acl 包。
由 Dries Vints 创建。多亏了 BeatSwitch 的帮助。受 Authority 的启发,由 Matthew Machuga 开发。Logo 由 Jerry Low 设计。
目录
术语
Lock
: 为主题提供 acl 实例。此包目前包含CallerLock
和RoleLock
Caller
: 可以拥有执行某些操作权限的标识对象Driver
: 存储权限的存储系统,可以是静态或持久化Permission
: 权限包含一个操作和一个可选(唯一)的资源。可以是Restriction
或Privilege
Restriction
: 限制阻止你执行操作(可选资源)Privilege
: 特权允许你执行操作(可选资源)Action
: 操作是你被允许或拒绝执行的事情Resource
: 资源可以是你可以执行一个或多个操作的对象。它可以针对特定类型的资源或通过其唯一标识符指定特定资源Role
: 角色也可以包含多个权限。调用者可以有多个角色。角色可以从其他角色继承权限
特性
- 为多个身份(调用者)提供灵活的 acl 权限
- 静态或持久化驱动器来存储权限
- 操作别名
- 角色
- 条件(断言)
- 通过特性轻松在调用者或角色上实现 acl 功能
介绍
Lock 与其他 acl 包不同之处在于,它试图提供与多个权限调用者和权限存储最灵活的协作方式。
通过使用 Lock 的 Caller
协议,你可以在多个身份上设置权限。
Driver
合同允许以简单的方式将权限存储到持久或静态存储系统中。本包附带默认的静态 ArrayDriver
。查看以下列表以获取更多已为您准备好的驱动。或者通过实现 Driver
合约来构建自己的驱动。
您可以通过手动传递资源的类型和(可选的)标识符来设置和检查资源的权限,或者您可以在您的对象上实现 Resource
合约,以便更容易地传递它们。
Manager
允许以简单的方式实例化新的 Lock
实例、设置操作别名或注册角色。
驱动器
如果您需要特定框架的实现,请从以下已准备好的驱动中选择一个。
- ArrayDriver(随本包提供)
- Laravel 5
路线图
- 分组权限
- 更多驱动(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.
使用条件
条件实际上是断言,您可以为权限设置额外的检查。您可以将它们作为最后一个参数传递给allow
和deny
的数组。所有条件都必须实现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
实例上的allowed
和denied
方法。
$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
对象。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 维护。
如果您有任何问题,请随时 在 issue 中提出。
贡献
有关详细信息,请参阅 贡献文件。
变更日志
您可以在 我们的变更日志文件 中查看每个版本的更改列表。
许可证
MIT 许可证。有关更多信息,请参阅 许可文件。