Symfony2で権限の組み合わせを満たす場合のみアクセスを許可したい
Symfony2では権限によるアクセス制御はapp/config/security.ymlなどにある"access_control"で指定しますが、直下のrolesには複数の権限が設定できます。
security: access_control: - { path: ^/foo/bar/, roles: [ROLE_A, ROLE_B]}
ただし、このrolesって"OR条件"なので、上の例の場合だとROLE_AかROLE_Bを持っていれば"/foo/bar/"にアクセスできます。
で、rolesをAND条件にする方法はないかと
とツイートしたところ、id:Fivestarさんから
security.ymlのsecurity.access_control.rolesに複数書いた時はOR条件なのか。AND条件にならんのかな?
というリプライをもらってから試行錯誤していたところ、
@shimooka そしたらsecurity.access.simple_role_voterサービスを上書きするとかですかねw
というサンプルを提示してもらいました。で、
@shimooka こんな感じでクラス定義して、DIコンテナのパラメータ書き換えるだけでできると思います(実装は確認してないですが...) gist.github.com/1709098
とりあえず、期待する動作をするようになったので一度まとめておきます。間違いなどあれば指摘してください:-)
@fivestr おお、似たようなことやってる途中でしたw うまくいったらまとめときます
前提
- ユーザーは1つ以上の権限を持つ場合がある
- 特定のURLに対して、権限の組み合わせを満たす場合のみアクセスを許可したい
環境
- PHP5.3.9
- Symfony2.0.9 without venders
- ソースはgithubに上げてあります
- Acme/AndRoleVoterBundle
- app/config/parameters.iniがありませんので、試す場合は別途作成してください
手順1:security.ymlのaccess_controlを定義する
冒頭にも書きましたが、security.access_controlのrolesに複数指定します。
security: : access_control: - { path: ^/AndRoleVoterBundle/user/, roles: [ROLE_A]} - { path: ^/AndRoleVoterBundle/private/, roles: [ROLE_A, ROLE_B]}
また、サンプルでは以下のユーザーも併せて追加してます。
ユーザー名 | 権限 |
---|---|
role_a | ROLE_A |
role_both | ROLE_A、ROLE_B |
github上のソースは以下のとおりです。
手順2:RoleHierarchyVoter#voteのロジックを変更する
id:Fivestarさんからはsecurity.access_control.roles(Symfony\Component\Security\Core\Authorization\Voter\RoleVoter.php)という情報をもらいましたが、このクラスには権限があるかどうかを決定するvoteメソッドがあります。で、実際にはRoleVoterクラスを変更するのではなく、そのサブクラスであるsecurity.access.role_hierarchy_voter.class(Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter.php)が利用されているようでした。RoleHierarchyVoterクラスはvoteメソッドをオーバーライドしていません。
ということで、次のような感じでRoleHierarchyVoterクラスのサブクラスを作り、voteメソッドをオーバーライドします。
<?php namespace Acme\AndRoleVoterBundle\Component\Security\Authorization\Voter; use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; class AndRoleHierarchyVoter extends RoleHierarchyVoter { /** * {@inheritdoc} */ public function vote(TokenInterface $token, $object, array $attributes) { $satisfied_roles = true; $roles = $this->extractRoles($token); foreach ($attributes as $attribute) { if (!$this->supportsAttribute($attribute)) { continue; } $has_role = false; foreach ($roles as $role) { if ($attribute === $role->getRole()) { $has_role = true; } } $satisfied_roles &= $has_role; } return ($satisfied_roles ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED); } }
github上のソースは以下のとおりです。
手順3:サービスのパラメータの上書き設定を追加する
アクセス制限をかけるBundleのResources/config/services.xmlに追加します。
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="security.access.role_hierarchy_voter.class">Acme\AndRoleVoterBundle\Component\Security\Authorization\Voter\AndRoleHierarchyVoter</parameter> </parameters> </container>
で。。。
最後のservices.xmlですが、services.ymlでも動作するハズと思い試してみたのですがxml形式でないと動作しませんでした。。。
ちなみに、Entityを使った認証するBundle環境だとservices.xmlでもservices.ymlファイルでもちゃんと動作してるんですよねぇ。。。なんだろ。。。
@shimooka バンドルのResources/configにあるサービス定義ファイルは、バンドルのDependencyInjection/**Extension.php内で読み込んでいます。形式ごとのローダーでファイルを読み込んでいるだけなので、app側とは独立しています
追記(2012/02/02 11:35)
@hidenorigotoさんからもらったpullリクエストをマージしました。ありがとうございます:-)
- YAMLでの設定のロードやテストコードなどを追加してみました by hidenorigoto · Pull Request #1 · shimooka/Symfony2_sample · GitHub
現在のソースではymlファイルでもDIコンテナのパラメータ(security.access.role_hierarchy_voter.class)を上書きできます。
なぜこのような違いになったかは次のエントリで。