アクセス制御リスト(ACL)

複雑なアプリケーションでは、アクセスしてきたユーザ(Token)のみに基づいてアクセスの可否を判断することができないこともあります。アクセスの可否の判断は、アクセスを行ったドメインオブジェクトが関わるときもあります。こういったときに ACL(Access Control List:アクセス制御リスト) システムが必要になります。

ブログの設計をしていて、ユーザが投稿にコメントができるようにしたいとしましょう。そしてユーザに他人のコメントは編集できないが、自分のコメントを編集できるようにしたいとします。また、ブログの管理者であるあなたには全てのコメントを編集できるようにしたいとします。このシナリオでは、 Comment はアクセスを制限したいドメインオブジェクトになります。 Symfony2 を使用すれば、いろんなアプローチでこれを実現できますが、次の2つが一般的です。

  • ビジネスロジックのセキュリティを強化する: このアプローチでは、個々の Comment 内にアクセス権を持つユーザを参照できるようにしておき、ユーザの持つ Token を比較します。
  • 権限のセキュリティを強化する: このアプローチでは、個々の Comment オブジェクトに権限を追加します。例えば、 ROLE_COMMENT_1ROLE_COMMENT_2 のようにです。

両方のアプローチは、どちらも有効です。しかし、認証のロジックとビジネスロジックのコードの結合が密になってしまい、他で再利用しにくくなりますし、ユニットテストもしにくくなります。さらに、ユーザの数が多ければ、1つのドメインオブジェクトにアクセスが増えてしまい、パフォーマンス悪化の問題にもつながります。

幸運にも、上記の方法よりも良い方法がありますので、これから説明をしていきます。

ブートストラップをする

まず始める前に、ブートストラップをしましょう。まず、 ACL システムが使用する接続を設定する必要があります。

  • YAML
    # app/config/security.yml
    security:
        acl:
            connection: default
    
  • XML
    <!-- app/config/security.xml -->
    <acl>
        <connection>default</connection>
    </acl>
    
  • PHP
    // app/config/security.php
    $container->loadFromExtension('security', 'acl', array(
        'connection' => 'default',
    ));
    

Note

ACL システムを使用するには、少なくとも1つは Doctrine DBAL 接続を設定しておく必要があります。しかし、ドメインオブジェクトをマップするのに、 Doctrine を使わなければならないわけではありません。オブジェクトのマッパーには、好きなものを使用できますので、 Doctrine ORM, Mongo ODM, Propel, 生の SQL など選択ができます。

接続を設定したら、データベース構造をインポートします。幸運にも、次のコマンドを走らせることによって、これを行うことができます。

php app/console init:acl

開始する

最初の小さな例に戻って、ACL を実装しましょう。

ACL を作成して、ACE(Access Control Entry:アクセス制御エントリー) を追加する

use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
// ...

// BlogController.php
public function addCommentAction(Post $post)
{
    $comment = new Comment();

    // setup $form, and bind data
    // ...

    if ($form->isValid()) {
        $entityManager = $this->get('doctrine.orm.default_entity_manager');
        $entityManager->persist($comment);
        $entityManager->flush();

        // creating the ACL
        $aclProvider = $this->get('security.acl.provider');
        $objectIdentity = ObjectIdentity::fromDomainObject($comment);
        $acl = $aclProvider->createAcl($objectIdentity);

        // retrieving the security identity of the currently logged-in user
        $securityContext = $this->get('security.context');
        $user = $securityContext->getToken()->getUser();
        $securityIdentity = UserSecurityIdentity::fromAccount($user);

        // grant owner access
        $acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
        $aclProvider->updateAcl($acl);
    }
}

上記のコードスニペットには、いくつかの重要な実装があります。ここでは、それに関して2つ取り上げましょう。

まず、 ->createAcl() メソッドの呼び出しが直接ドメインオブジェクトではなく ObjectIdentityInterface の実装のみを受け取っていることに気づくでしょう。間接的な受け渡しによって、ドメインオブジェクトのインスタンスを持っていなくても、 ACL を操作することができます。実際にこれらのオブジェクトをハイドレートしなくても多くのオブジェクトのパーミッションをチェックできるので、非常に便利です。

また、 ->insertObjectAce() メソッドの呼び出しも興味深い点です。この例では、現在ログインしているユーザに Comment へのオーナーアクセスを与えています。 MaskBuilder::MASK_OWNER は、整数値のビットマスク(bitmask) として前もって定義してあります。マスクビルダー(MaskBuilder)は、ほとんどの技術的な詳細を抽象化しており、この技術を使えば、多くの異なるパーミッションを1列に格納するだけなので、パフォーマンスの改善の享受ができるようになっています。

Tip

ACE がチェックされる順番は重要です。一般的に、始めにより特定したエントリを入れることが推奨されます。

アクセスをチェックする

// BlogController.php
public function editCommentAction(Comment $comment)
{
    $securityContext = $this->get('security.context');

    // check for edit access
    if (false === $securityContext->isGranted('EDIT', $comment))
    {
        throw new AccessDeniedException();
    }

    // retrieve actual comment object, and do your editing here
    // ...
}

この例では、ユーザが EDIT パーミッションを持ってるかどうかチェックしています。内部的に Symfony2 は、パーミッションをいくつもの整数値のビットマスク(bitmask)にマップしており、ユーザの持ってるパーミッションをチェックします。

Note

32ものパーミッションを定義することができます(動作している OS の PHP によって30もしくは32になります)。さらに累積(cumulative)パーミッションも定義することができます。

累積(cumulative)パーミッション

上記の最初の例では、ユーザに OWNER パーミッションを与えるのみでした。確かにこれでユーザに対して、ドメインオブジェクトの参照、編集などのオペレーションを行うことを可能にさせることができます。しかし、より明示的にこれらのパーミッションを与たいときなどもあると思います。

MaskBuilder を使って、パーミッションのベースを結合させることで簡単にビットマスク(bitmask)を作成することができます。

$builder = new MaskBuilder();
$builder
    ->add('view')
    ->add('edit')
    ->add('delete')
    ->add('undelete')
;
$mask = $builder->get(); // int(15)

整数値のビットマスク(bitmask)は、上記でユーザに追加したパーミッションとして使われます。

$acl->insertObjectAce(new UserSecurityIdentity('johannes'), $mask);

これでユーザはオブジェクトに対して、参照、編集、削除、そして削除の取り消しができるようになりました。

このページのコンテンツ

ソース



クイックリンク

コメントリスト


ご質問や翻訳不備等お気軽にコメントください。


現在、翻訳が古くなっている箇所が多くあります。1箇所、1行などほんの少量でもかまいませんので、ドキュメント翻訳にご協力いただける方を募集しています。日本 Symfony ユーザー会メーリングリストまでご連絡ください。