PHPで認可、権限管理(その1、ZfcRbacのサンプルを動かす)

はじめに

今まで、権限管理(認証から認可処理)をするときには、ZF1Zend_Aclで作っていた。設定箇所が多い実装をしていたので、もうちょっと何か無いかな、と感じていた。今回、仕事でまたまた要り用が出てきた。→ 他のライブラリを試してみることにした。

予備知識

権限管理のサンプルユースケース

  • 某ホームページの管理をしていると仮定。
  • 外部のライターさんが、ホームページ内の一部の枠を更新できるようなCMSツールを作る(よくある)。
  • ライターさんは下書きで登録して、ホームページの担当者が、事前に記事をチェックしてからProduction環境に公開する、という業務フローがある(よくある)。

ロール Create Read Update Delete
システム管理者
担当者 × × ×

ロール
Create
(下書きまで)
Read
Update
(下書きまで)
Delete
(下書きまで)
A社のライターさん

(A社記事のみ)

(A社記事のみ)

(A社記事のみ)

(A社記事のみ)
以下、B社、C社と複数想定。

ロールには継承関係がある。

システム管理者 > 担当者 > A社ライターさん・B社ライターさん

環境

  • PHP5.4。今回は、BEAR.Saturday にのせる形にしたい。

ライブラリ

今回は、ZfcRbacを試してみる。

1. ZfcRbacインストール

2. ZrfRbacサンプルを動かす

間違い1.

Extending and adding roles via instantiation. の
ソースコード16 行目
(誤) var_dump($rbac->hasRole('foo')); // true
(正)var_dump($rbac->hasRole($foo)); // true

間違い2.

Dynamic Assertions
Checking permission using isGranted() with a class implementing Zend\Permissions\Rbac\AssertionInterface. の
ソースコード25 行目
(誤)    return $this->userId == $article->getUserId();
(正)   return $this->userId == $this->article->getUserId();

Rbacでやれること

  • Roles
    • ロールを登録できるよ
  • Permissions
    • 認可を設定できるよ
<?php
 use Zend\Permissions\Rbac\Rbac;
 use Zend\Permissions\Rbac\Role;

 $rbac = new Rbac();

 $staff  = new Role('staff');
 $staff->addPermission('read.article');

 // ロールがパーミッションを持っているか
 var_dump($staff->hasPermission('read.article')); // true
 var_dump($staff->hasPermission('edit.article')); // false

 $rbac->addRole($staff);

 // 指定の権限を許可設定しているか
 $rbac->isGranted('staff', 'read.article'); // true
 $rbac->isGranted('staff', 'edit.article'); // false
  • Dynamic Assertions
    • Zend\Permissions\Rbac\AssertionInterfaceを実装したクラスと一緒に isGranted() を使えば、動的な認可判定をさせることもできるよ

動くようにした公式サンプルベースのコード

Dynamic Assertions

/**
*
* ZF-Commons のライセンスは下記です。
* 
* Commit Practices & Contributions
* All ZF-Commons modules are released under the 3-clause BSD license, unless otherwise stated. Contributions in the form of pull requests, feedback, and ideas are welcome from anyone.
* 
* Once a module reaches its first tagged release, the following rules shall apply:
* 
* All work should be done on feature / hotfix branches (NOT MASTER!) and pushed to your own fork.
* When a feature is ready to be merged, submit a pull request.
* Those with commit access must not push their commits directly to the canonical repository or merge their own pull requests.
* Each pull request should be peer-reviewed by other member in order to keep high code quality and prevent mistakes and ommisions. Once reviewed it is ready to be merged.
* 
* 
* http://framework.zend.com/manual/2.0/en/modules/zend.permissions.rbac.examples.html
* 上記公式サンプルコードを改変したものです。(kumamidori)
*/

use Zend\Permissions\Rbac\AssertionInterface;
use Zend\Permissions\Rbac\Rbac;
require_once('vendor/autoload.php');
class AssertUserIdMatches implements AssertionInterface
{
    protected $userId;
    protected $article;

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

    public function setArticle($article)
    {
        $this->article = $article;
    }

    public function assert(Rbac $rbac)
    {
        if (!$this->article) {
            return false;
        }
        // edits his own article
        // can not edit another users article
        return $this->userId == $this->article->getUserId();
    }
}

//(略)
// User is assigned the foo role with id 5
// News article belongs to userId 5
// Jazz article belongs to userId 6

$rbac = new Rbac();
$user = $mySessionObject->getUser();
$news = $articleService->getArticle(5);
$jazz = $articleService->getArticle(6);

$rbac->addRole($user->getRole());
$rbac->getRole($user->getRole())->addPermission('edit.article');

$assertion = new AssertUserIdMatches($user->getId());
$assertion->setArticle($news);

//Determines if access is granted by checking the role and child roles for permission.

// bad!!! true always - bad !!! 
if ($rbac->isGranted($user->getRole(), 'edit.article')) {
    // hacks another users article
    // NG!!!
    p('1. This is BAD!!! sample. another users article');
}

// true for user id 5, because he belongs to write group and user id matches
if ($rbac->isGranted($user->getRole(), 'edit.article', $assertion)) {
    // edits his own article
    p('2. This is GOOD Sample. edits his own article');
}

$assertion->setArticle($jazz);

// false for user id 5
if ($rbac->isGranted($user->getRole(), 'edit.article', $assertion)) {
    // can not edit another users article
} else {
    p('3. This is GOOD Sample. can not edit another users article');
}

TODO(今後の課題)

  • BEAR.Saturday で rbacで上記ユースケースについて実装してみる (今日ここまでいかなかった・・・)
    • DB設計まではやってあるのでもうちょっと。できたらこのエントリーを更新する。
  • BEARのAnnotationで実装した場合とよくある方の実装との比較
  • ZF2 の方のZend-Permission-Acl との比較
  • Another way:silex/SecurityServiceProvider

情報源メモ

p.s.

今回、@koriymさんと @futurista999さんから、ライブラリ情報を教えて頂きました。ありがとうございました!