popphp/pop-acl 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

popphp/pop-acl

Composer 安装命令:

composer require popphp/pop-acl

包简介

Pop ACL Component for Pop PHP Framework

README 文档

README

Build Status Coverage Status

Join the chat at https://discord.gg/TZjgT74U7E

Overview

pop-acl is a full-featured component that supports ACL/RBAC user access concepts. Beyond allowing or denying basic user access, it provides support for roles, resources, permissions as well as assertions and policies for fine-grain access-control.

pop-acl is a component of the Pop PHP Framework.

Top

Install

Install pop-acl using Composer.

composer require popphp/pop-acl

Or, require it in your composer.json file

"require": {
    "popphp/pop-acl" : "^4.1.3"
}

Top

Quickstart

The basic concepts involve role and resource objects and then defining what permissions are allowed (or denied) between them. The main ACL object will determine if the requested action by a role on a resource is permitted or not.

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

The above also works with the string value names of the roles and resources:

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

Top

Roles

Besides being a store for a role name, a role object serves as a simple data object, should additional data need to be stored about the role or the user currently assigned to the role.

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

This is useful for deeper evaluations like assertions and policies.

Top

Resources

Like roles, the resource object serves as a simple data object to store additional data that may be needed.

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

This is useful for deeper evaluations like assertions and policies.

Top

Strict

Setting the strict flag strictly enforces any permissions that have been set and requires permissions to be explicitly set. If the strict flag is set to false, then ACL checks may pass as true if a rule is not explicitly set. Consider the following examples:

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)

Both evaluations result in true, as there is no explicit rule preventing the editor from adding a page. In order to prevent the editor from adding a page, you would either have to set a deny rule:

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)

Or, set the ACL to strict:

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)

Top

Multiple Roles

If a user is assigned multiple roles at one time, those roles can all be evaluated at the same time. If we wire up a similar example from above:

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

we can then call the isAllowedMulti() method to evaluate multiple roles at once:

var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'add'));  // true
var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'edit')); // true

If one of the roles is permitted to perform the requested action on the resource, it will pass as true.

Multi-Strict

When evaluating multiple roles at once, if the requirement is such that all roles must be permitted to perform the requested action on the resource, using the multi-strict flag will ensure that.

$acl->setMultiStrict(true);

var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'add'));  // false
var_dump($acl->isAllowedMulti([$admin, $editor], $page, 'edit')); // true

Top

Inheritance

Roles can be constructed to inherit rules from other roles.

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

Top

Assertions

If you want more fine-grain control over permissions and who is allowed to do what, you can use assertions. First, define the assertion class, which implements the Pop\Acl\Assertion\AssertionInterface. In this example, we want to check that the user "owns" the resource via a matching user 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));
    }

}

Then, within the application, you can use assertions like this:

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')) { }

Top

Policies

An alternate way to achieve even more specific fine-grain control is to use policies. Similar to assertions, you have to write the policy class and it needs to use the Pop\Acl\Policy\PolicyTrait. Unlike assertions that are centered around the single assert() method, policies allow you to write separate methods that will be called and evaluated via the can() method in the PolicyTrait. Consider the following example policy class:

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));
    }

}

It defines specific evaluations that are required for three different actions create(), update() and delete(). Then the user role and policy can be added to the main ACL object:

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

Once the polices are added to the ACL object, they will be automatically evaluated on the isAllowed() or isDenied() method calls:

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

A deeper look into what is happening under the hood, the ACL object is calling the method evaluatePolicy() to determine if the requested action is allowed:

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

Which, in turn, the evaluatePolicy() method calls are calling the can() method on the actual policy objects themselves:

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

Top

popphp/pop-acl 适用场景与选型建议

popphp/pop-acl 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 20.05k 次下载、GitHub Stars 达 5, 最近一次更新时间为 2014 年 12 月 07 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「acl」 「access control list」 「pop」 「pop php」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 popphp/pop-acl 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 popphp/pop-acl 我们能提供哪些服务?
定制开发 / 二次开发

基于 popphp/pop-acl 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 20.05k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 5
  • 点击次数: 27
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 5
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: BSD-3-Clause
  • 更新时间: 2014-12-07