popphp / pop-acl
Pop PHP 框架的 Pop ACL 组件
Requires
- php: >=8.1.0
- popphp/pop-utils: ^2.1.0
Requires (Dev)
- phpunit/phpunit: ^10.0.0
README
概述
pop-acl
是一个功能齐全的组件,支持 ACL/RBAC 用户访问概念。除了允许或拒绝基本用户访问外,它还提供了对角色、资源、权限以及细粒度访问控制的断言和策略的支持。
pop-acl
是 Pop PHP 框架 的一个组件。
安装
使用 Composer 安装 pop-acl
。
composer require popphp/pop-acl
或者在 composer.json 文件中要求它
"require": {
"popphp/pop-acl" : "^4.0.0"
}
快速入门
基本概念涉及角色和资源对象,然后定义它们之间允许(或拒绝)的权限。主要的 ACL 对象将确定角色在资源上请求的操作是否被允许。
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $admin = new Role('admin'); $editor = new Role('editor'); $reader = new Role('reader'); $page = new Resource('page'); $acl->addRoles([$admin, $editor, $reader]); $acl->addResource($page); $acl->allow('admin', 'page') // Admin can do anything to a page ->allow('editor', 'page', 'edit') // Editor can only edit a page ->allow('reader', 'page', 'read'); // Reader can only read a page var_dump($acl->isAllowed($admin, $page, 'add')); // true var_dump($acl->isAllowed($editor, $page, 'edit')); // true var_dump($acl->isAllowed($editor, $page, 'add')); // false var_dump($acl->isAllowed($reader, $page, 'edit')); // false var_dump($acl->isAllowed($reader, $page, 'read')); // true
上述内容也适用于角色和资源的字符串值名称
var_dump($acl->isAllowed('admin', 'page', 'add')); // true var_dump($acl->isAllowed('editor', 'page', 'edit')); // true var_dump($acl->isAllowed('editor', 'page', 'add')); // false var_dump($acl->isAllowed('reader', 'page', 'edit')); // false var_dump($acl->isAllowed('reader', 'page', 'read')); // true
角色
除了作为角色名称的存储库外,角色对象还充当一个简单的数据对象,如果需要存储关于角色或当前分配给该角色的用户的其他数据。
use Pop\Acl\AclRole as Role; $admin = new Role('admin'); $admin->id = 1; // Define the role ID $admin->user_id = 2; // Define the current user ID
资源
与角色类似,资源对象也充当一个简单的数据对象,以存储可能需要存储的附加数据。
use Pop\Acl\AclResource as Resource; $page = new Resource('page'); $page->id = 1; // Define the role ID $page->user_id = 2; // Define the page owner user ID
严格模式
设置 strict
标志将严格执行已设置的任何权限,并要求明确设置权限。如果将 strict
标志设置为 false
,则如果未明确设置规则,ACL 检查可能会通过 true
。考虑以下示例
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $admin = new Role('admin'); $editor = new Role('editor'); $page = new Resource('page'); $acl->addRoles([$admin, $editor]); $acl->addResource($page); $acl->allow($admin, $page) // Admin can do anything to a page ->allow($editor, $page, 'edit'); // Editor can edit a page var_dump($acl->isAllowed($admin, $page, 'add')); // bool(true) var_dump($acl->isAllowed($editor, $page, 'add')); // bool(true)
两次评估的结果都是 true
,因为没有明确的规则阻止编辑器添加页面。为了防止编辑器添加页面,您必须设置拒绝规则
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $admin = new Role('admin'); $editor = new Role('editor'); $page = new Resource('page'); $acl->addRoles([$admin, $editor]); $acl->addResource($page); $acl->allow($admin, $page) // Admin can do anything to a page ->allow($editor, $page, 'edit'); // Editor can edit a page $acl->deny($editor, $page, 'add'); var_dump($acl->isAllowed($admin, $page, 'add')); // bool(true) var_dump($acl->isAllowed($editor, $page, 'add')); // bool(false)
或者,将 ACL 设置为严格模式
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $acl->setStrict(); $admin = new Role('admin'); $editor = new Role('editor'); $page = new Resource('page'); $acl->addRoles([$admin, $editor]); $acl->addResource($page); $acl->allow($admin, $page) // Admin can do anything to a page ->allow($editor, $page, 'edit'); // Editor can edit a page var_dump($acl->isAllowed($admin, $page, 'add')); // bool(true) var_dump($acl->isAllowed($editor, $page, 'add')); // bool(false)
多重角色
如果用户同时分配了多个角色,则可以同时评估这些角色。如果我们从上面的类似示例中建立联系
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $admin = new Role('admin'); $editor = new Role('editor'); $page = new Resource('page'); $acl->addRoles([$admin, $editor]) ->addResource($page); $acl->allow('admin', 'page') // Admin can do anything to a page ->allow('editor', 'page', 'edit') // Editor can only edit a page
然后我们可以调用 isAllowedMulti()
方法来同时评估多个角色
var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'add')); // true var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'edit')); // true
如果其中一个角色被允许在资源上执行请求的操作,它将通过 true
。
多重严格模式
在同时评估多个角色时,如果要求所有角色都必须被允许在资源上执行请求的操作,则使用 multi-strict
标志将确保这一点。
$acl->setMultiStrict(true); var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'add')); // false var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'edit')); // true
继承
可以构建角色以继承其他角色的规则。
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $editor = new Role('editor'); $reader = new Role('reader'); // Add the $reader role as a child role of $editor. // The role $reader will now inherit the access rules // of the role $editor, unless explicitly overridden. $editor->addChild($reader); $page = new Resource('page'); $acl->addRoles([$editor, $reader]); $acl->addResource($page); // Neither the editor or reader can add a page $acl->deny('editor', 'page', 'add'); // The editor can edit a page $acl->allow('editor', 'page', 'edit'); // Both the editor or reader can read a page $acl->allow('editor', 'page', 'read'); // Over-riding deny rule so that a reader cannot edit a page $acl->deny('reader', 'page', 'edit'); var_dump($acl->isAllowed('editor', 'page', 'add')); // false var_dump($acl->isAllowed('reader', 'page', 'add')); // false var_dump($acl->isAllowed('editor', 'page', 'edit')); // true var_dump($acl->isAllowed('reader', 'page', 'edit')); // false var_dump($acl->isAllowed('editor', 'page', 'read')); // true var_dump($acl->isAllowed('reader', 'page', 'read')); // true
断言
如果您想更细粒度地控制权限以及谁可以做什么,可以使用断言。首先,定义断言类,该类实现 Pop\Acl\Assertion\AssertionInterface
。在这个例子中,我们想检查用户是否通过匹配的用户 ID 拥有资源。
use Pop\Acl\Acl; use Pop\Acl\AclRole; use Pop\Acl\AclResource; use Pop\Acl\Assertion\AssertionInterface; class UserCanEditPage implements AssertionInterface { public function assert( Acl $acl, AclRole $role, AclResource $resource = null, $permission = null ) { // Check that the resource owner (user_id) is the same as the current role user (user_id) return ((null !== $resource) && ($resource->user_id == $role->user_id)); } }
然后,在应用程序中,您可以像这样使用断言
use Pop\Acl\Acl; use Pop\Acl\AclRole as Role; use Pop\Acl\AclResource as Resource; $acl = new Acl(); $admin = new Role('admin'); $editor = new Role('editor'); $page = new Resource('page'); $admin->id = 1001; $editor->id = 1002; $page->user_id = 1001; $acl->addRoles([$admin, $editor]); $acl->addResource($page); // Define the assertion(s) to use in the 4th parameter of the allow/deny method $acl->allow('admin', 'page', 'add') ->allow('admin', 'page', 'edit', new UserCanEditPage()) ->allow('editor', 'page', 'edit', new UserCanEditPage()) // Returns true because the assertion passes, // the admin's ID matches the page's user ID if ($acl->isAllowed('admin', 'page', 'edit')) { } // Although editors can edit pages, this returns false // because the assertion fails, as this editor's ID // does not match the page's user ID if ($acl->isAllowed('editor', 'page', 'edit')) { }
策略
另一种实现更具体细粒度控制的方法是使用策略。与断言类似,您必须编写策略类,并且它需要使用 Pop\Acl\Policy\PolicyTrait
。与集中在单个 assert()
方法上的断言不同,策略允许您编写将被调用并通过 PolicyTrait
中的 can()
方法评估的单独方法。考虑以下策略类示例
use Pop\Acl\Acl; use Pop\Acl\AclRole; use Pop\Acl\AclResource; class User extends AclRole { use Pop\Acl\Policy\PolicyTrait; public function __construct($name, $id, $isAdmin) { parent::__construct($name, ['id' => $id, 'isAdmin' => $isAdmin]); } public function create(User $user, AclResource $page) { return (($user->isAdmin) && ($page->getName() == 'page')); } public function update(User $user, AclResource $page) { return ($user->id === $page->user_id); } public function delete(User $user, AclResource $page) { return (($user->isAdmin) || ($user->id === $page->user_id)); } }
它定义了三个不同操作(create()
、update()
和delete()
)所需的特定评估。然后可以将用户角色和政策添加到主访问控制列表(ACL)对象中
$page = new AclResource('page', ['id' => 2001, 'user_id' => 1002]); $admin = new User('admin', 1001, true); $editor = new User('editor', 1002, false); $acl = new Acl(); $acl->addRoles([$admin, $editor]); $acl->addResource($page); $acl->addPolicy('create', $admin, $page); $acl->addPolicy('create', $editor, $page); $acl->addPolicy('update', $admin, $page); $acl->addPolicy('update', $editor, $page);
一旦政策被添加到ACL对象中,它们将在isAllowed()
或isDenied()
方法调用时自动评估
// Returns true, because the user is an admin var_dump($acl->isAllowed('admin', 'page', 'create')); // Returns false, because the user is an editor (not an admin) var_dump($acl->isAllowed('editor', 'page', 'create')); // Returns false, because the admin doesn't "own" the page var_dump($acl->isAllowed('admin', 'page', 'update')); // Returns true, because the editor does "own" the page var_dump($acl->isAllowed('editor', 'page', 'update'));
深入了解内部发生的操作,ACL对象会调用evaluatePolicy()
方法来确定请求的操作是否允许
// Returns true, because the user is an admin var_dump($acl->evaluatePolicy('create', 'admin', 'page')); // Returns false, because the user is an editor (not an admin) var_dump($acl->evaluatePolicy('create', 'editor', 'page')); // Returns false, because the admin doesn't "own" the page var_dump($acl->evaluatePolicy('update', 'admin', 'page')); // Returns true, because the editor does "own" the page var_dump($acl->evaluatePolicy('update', 'editor', 'page'));
反过来,evaluatePolicy()
方法会调用实际政策对象上的can()
方法
var_dump($admin->can('create', $page)); // true, because the user is an admin var_dump($editor->can('create', $page)); // false, because the user is an editor (not an admin) var_dump($admin->can('update', $page)); // false, because the admin doesn't "own" the page var_dump($editor->can('update', $page)); // true, because the editor does "own" the page