CakePHPにACL入れました
苦心惨憺の末、ようやくACL入れられました。
参考にした主な記事。
http://cake.zista.jp/max/blog/view/0000000098
http://cake.zista.jp/max/blog/view/0000000099
AuthComponent + AclComponent + AclBehavior CakePHP1.2RC2 - 忍び歩く男 - SLYWALKER
CakePHPのACLにはまる...でも、出てくる?! | ブラジルの大地
http://book.cakephp.org/ja/view/648/Setting-up-permissions
特にZiSTA様の記事は、本当に、助かりました(T-T)必読です。
以下、長文です・・・
混迷した主な原因が、ACLの設定方法に、複数のやり方があることでした。
このなかで、まず「DBに権限設定の登録」
- PHPから行なう
- シェルで行なう
ざっくりと、この2種類があります。
いずれか1つを行なって、DBの上記3テーブルにデータが正しく登録されればOK*2。
また、「Authのauthorize設定」
こちらも、
- アクション・モード
- CRUD・モード
の2種類があって、どちらのモードにするかで、ACOとパーミッションの設定方法が変わります。
今回のDBへの登録方法は、以下の通りです。
その他主な設定方針
- authorize設定
- CRUD・モード
- ARO
- 権限GROUPを作り、個々のUserはいずれかのGROUPに所属させる
- ACO
- controllers(全controller)ー各controllerーアクションの階層構造にする
- パーミッション
- 権限はGROUPごとに設定する。
- USER個別には設定しない
- コントローラ単位での権限設定を行なえるようにする
- 権限はGROUPごとに設定する。
DB定義の変更
slywakerさんの記事を参考に、groupsテーブルを追加。
group_idの紐付けカラムを追加してusersテーブル作り直し。
注意点は、CakePHPのお約束:紐付けカラム名がgroup_idになることくらい。
そしてACL関連のテーブル追加。
app/config/db_acl.sqlからmysqlコマンドで追加。
[cake@cake app]$ mysql -u root -p DBNAME < app/config/db_acl.sql
コレだけで済みます。確かに(笑)
AROの登録
AclBehaviorを使って、groupsとusersにデータが追加・更新された時、同時にAROが自動設定されるようにします。
groupsとusersの各モデルは、slywakerさんの記事に倣って作成・改修。
そしてgroupsのcontrollerとviewを、bakeを使って作成。Authはまだいれません。
ブラウザから、http://CAKE_ROOT/groups/addにアクセスして、parent_id=0で権限グループを作成。
データ追加に伴って、arosテーブルにデータが自動挿入されたことを確認。
mysql> SELECT * FROM groups; +----+----------+-----------+ | id | name | parent_id | +----+----------+-----------+ | 1 | admin | 0 | | 2 | subadmin | 0 | | 3 | watcher | 0 | | 4 | member | 0 | +----+----------+-----------+ 4 rows in set (0.00 sec) mysql> SELECT * FROM aros; +----+-----------+-------+-------------+----------+------+------+ | id | parent_id | model | foreign_key | alias | lft | rght | +----+-----------+-------+-------------+----------+------+------+ | 1 | NULL | Group | 1 | Group::1 | 1 | 2 | | 2 | NULL | Group | 2 | Group::2 | 3 | 4 | | 3 | NULL | Group | 3 | Group::3 | 5 | 6 | | 4 | NULL | Group | 4 | Group::4 | 7 | 8 | +----+-----------+-------+-------------+----------+------+------+ 4 rows in set (0.00 sec)
users_controller.phpには、addでgroup_idのセットを追加。
デフォルトのgroup_id=4(一般member)。*3
Index: controllers/users_controller.php =================================================================== --- controllers/users_controller.php (revision 167) +++ controllers/users_controller.php (working copy) @@ -192,7 +192,10 @@ $this->set('users', $this->paginate()); } - function _add() { + function _add($group_id=4) { + // ACL設定(デフォルト:一般ユーザ) + $this->data['User']['group_id'] = $group_id; + // バリデーション $this->User->set($this->data); if ($this->User->validates()) {
http://CAKE_ROOT/users/add*4からuserを登録すると、arosが自動的に登録されました*5
mysql> SELECT id, name, group_id FROM users; +----+---------------+----------+ | id | name | group_id | +----+---------------+----------+ | 1 | Administrator | 1 | | 2 | Cake | 4 | +----+---------------+----------+ 2 rows in set (0.00 sec) mysql> SELECT * FROM aros; +----+-----------+-------+-------------+----------+------+------+ | id | parent_id | model | foreign_key | alias | lft | rght | +----+-----------+-------+-------------+----------+------+------+ | 1 | NULL | Group | 1 | Group::1 | 1 | 4 | | 2 | NULL | Group | 2 | Group::2 | 5 | 6 | | 3 | NULL | Group | 3 | Group::3 | 7 | 8 | | 4 | NULL | Group | 4 | Group::4 | 9 | 12 | | 5 | 1 | User | 1 | User::1 | 2 | 3 | | 6 | 4 | User | 2 | User::2 | 10 | 11 | +----+-----------+-------+-------------+----------+------+------+ 6 rows in set (0.00 sec)
↑これら↓を見ると、arosの構成がなんとなく把握できます。
[cake@cake console]$ ./cake acl view aro Aro tree: --------------------------------------------------------------- [1]Group::1 [5]User::1 [2]Group::2 [3]Group::3 [4]Group::4 [6]User::2 ---------------------------------------------------------------
ACOの登録
次にacosの登録。
追加するコントローラ・アクションは権限別の設定が必要なもののみ。
とりあえず動作確認用のみ。
以下の通り*6。
[cake@cake app]$ ./cake acl create aco root controllers [cake@cake app]$ ./cake acl create aco controllers Users [cake@cake app]$ ./cake acl create aco Users delete [cake@cake console]$ ./cake acl create aco controllers Groups
追加結果。
[cake@cake console]$ ./cake acl view aco Aco tree: --------------------------------------------------------------- [1]controllers [2]Users [3]delete [4]Groups ---------------------------------------------------------------
コントローラとアクションの階層図になっています。
このときのDB acosの状態
mysql> SELECT * FROM acos; +----+-----------+-------+-------------+-------------+------+------+ | id | parent_id | model | foreign_key | alias | lft | rght | +----+-----------+-------+-------------+-------------+------+------+ | 1 | NULL | | NULL | controllers | 1 | 8 | | 2 | 1 | | NULL | Users | 2 | 5 | | 3 | 2 | | NULL | delete | 3 | 4 | | 4 | 1 | | NULL | Groups | 6 | 7 | +----+-----------+-------+-------------+-------------+------+------+ 4 rows in set (0.00 sec)
全コントローラのmasterであるcontrollersの設定を、app/controller/app_controller.phpのbeforeFilterに追加。
Index: controllers/app_controller.php =================================================================== --- controllers/app_controller.php (revision 167) +++ controllers/app_controller.php (working copy) class AppController extends Controller { var $isAdmin = false; function beforeFilter() { parent::beforeFilter(); + // ACLのTopNode + $this->AuthPlus->actionPath = 'controllers/'; + // 認証関連設定 if (Configure::read('mobile')) { $this->AuthPlus->loginAction = '/m/users/login';
* AuthPlusは私が使っているAuthコンポーネントの拡張です。通常なら
$this->Auth->actionPath = 'controllers/';
パーミッションの設定
シェルから登録する場合の基本コマンド
cake acl {grant|deny} {model}.{foreign_key} {alias} {create|read|update|delete|all}
actionは指定しなくても良い。その場合controller単位での設定になる。
SuperAdministrator用の全権allow
[cake@cake console]$ ./cake acl grant Group.1 controllers all
一般ユーザ用の設定(試験用&一部)
- 基本deny
- Usersの読込みのみ許可
[cake@cake console]$ ./cake acl deny Group.4 controllers all [cake@cake console]$ ./cake acl grant Group.4 Users read
その結果
mysql> SELECT * FROM aros_acos; +----+--------+--------+---------+-------+---------+---------+ | id | aro_id | aco_id | _create | _read | _update | _delete | +----+--------+--------+---------+-------+---------+---------+ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | 2 | 4 | 1 | 0 | 0 | 0 | 0 | | 3 | 4 | 2 | 0 | 1 | 0 | 0 | +----+--------+--------+---------+-------+---------+---------+ 3 rows in set (0.00 sec)
id=1 aro_id=1(Group.1)にaco_id=1(controllers)にall allow。
id=2 aro_id=4(Group.4)にaco_id=1(controllers)にall deny。
id=3 aro_id=4(Group.4)にaco_id=2(Users)にread allow 他 deny。
・・・と正しく設定されています。
ACL設定をDBで確認すると言う手法は、この本由来です。
オープンソース徹底活用CakePHPによるWebアプリケーション開発
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2009/03/13
- メディア: 単行本
- 購入: 6人 クリック: 59回
- この商品を含むブログ (4件) を見る
Authの設定
最後の仕上げ。
Authコンポーネントを改修して、Auth認証が通った後ACLによる権限チェックを行なうように設定します。
具体的には、app_controler.phpで、
class AppController extends Controller { + var $components = array('AuthPlus', 'Acl'); (中略) function beforeFilter() { parent::beforeFilter(); if ($this->AuthPlus) { + // ACL関連 + $this->AuthPlus->actionPath = 'controllers/'; + $this->AuthPlus->authorize = 'crud';
コントローラ単位でのパーミッション設定を有効にするために、crud設定が必須です*8。
index, view, add, edit, deleteのような基本アクション*9だけならこれだけでOKですが、
独自のアクション名を追加してる場合、追加アクションのcrudマッピング追加が必要になります。
これは、各コントローラで設定して、Authの拡張コンポーネントで読み込みます。
Index: controllers/users_controller.php =================================================================== --- controllers/users_controller.php (revision 169) +++ controllers/users_controller.php (working copy) + /* ACL */ + // 追加アクション用 crudMap + var $actionMapPlus = array( + 'listview' => 'read', + 'change_password' => 'update', + ); Index: controllers/components/auth_plus.php =================================================================== --- controllers/components/auth_plus.php (revision 169) +++ controllers/components/auth_plus.php (working copy) @@ -20,6 +20,18 @@ function initialize(&$controller) { + // ACL: controllerごとのactionMap設定マージ + $this->actionMap = array_merge($this->actionMap, $controller->actionMapPlus); + $admin = Configure::read('Routing.admin'); + if (!empty($admin)) { + foreach ($controller->actionMapPlus as $k => $v) { + $this->actionMap = array_merge( + $this->actionMap, + array($admin . '_'. $k => $v) + ); + } + } + parent::initialize($controller); // ログイン後リダイレクト設定 Index: controllers/app_controller.php =================================================================== --- controllers/app_controller.php (revision 169) +++ controllers/app_controller.php (working copy) @@ -19,19 +19,32 @@ { var $isAdmin = false; + var $components = array('AuthPlus', 'Acl'); + + /* ACL */ + // 追加アクション用 crudMap + var $actionMapPlus = array();
追加アクションの内容に応じて、read, create, update, deleteを適宜割り当てます。
動作確認
以上を設定して、SuperAdministor権限のid=1でログインすると、Users,Groupsのすべてのアクションにアクセスできます。
一方、一般ユーザ権限のid=2でログインすると、readのみのusers/indexやusers/viewは表示できますが、delete権限のusers/delete、update権限のchange_passwordなどは実行できません。
実行できないアクションにアクセスした場合、前の画面に戻りますが、AuthコンポーネントのauthErrorに設定のメッセージがSession.Flushに入っています。
ビューに表示したい場合は、$session->flash('auth'); をechoさせると表示されます。入れるならlayoutかなと思います。
ひとまず区切り
全アクションの権限設定、実際に運用する場合の多少の問題(Usersから削除した際にaroから消えない、adminからパスワード操作した際の挙動)など、
環境に合わせての細かい修正がまだ要りますが、
「基本のACL」実装としては、ここで一区切り。
最後に全改修分です*10。
[cake@cake app]$ svn diff Index: models/group.php =================================================================== --- models/group.php (revision 0) +++ models/group.php (revision 0) @@ -0,0 +1,41 @@ +<?php +class Group extends AppModel { + + var $name = 'Group'; + var $actsAs = array( + 'Acl' => array('requester'), + ); + + function parentNode() { + if (!$this->id) { + return null; + } + $data = $this->read(); + if (!$data['Group']['parent_id']){ + return null; + } else { + return array('model' => 'Group', 'foreign_key' => $data['Group']['parent_id']); + } + } + + // 更新時に親IDを変更する + function save($data = null, $validate = true, $fieldList = array()) + { + if (parent::save($data, $validate, $fieldList)) { + $conditions = array( + 'model' => $this->name, + 'foreign_key' => $this->id, + ); + + App::import('Component', 'Acl'); + $Aro = new Aro; + $Aro->id = $Aro->field('id', $conditions); + $Aro->saveField('parent_id', $data['Group']['parent_id']); + $Aro->saveField('alias', $this->name . '::' . $this->id); + return true; + } + return false; + } + +} +?> Index: models/user.php =================================================================== --- models/user.php (revision 169) +++ models/user.php (working copy) @@ -2,7 +2,9 @@ class User extends AppModel { var $name = 'User'; + var $belongsTo = array('Group'); var $actsAs = array( + 'Acl' => 'requester', 'Cakeplus.AddValidationRule', ); @@ -50,7 +52,42 @@ ) ); + // ACL + function parentNode() + { + if (!$this->id && empty($this->data)) { + return null; + } + $data = $this->data; + if (empty($this->data)) { + $data = $this->read(); + } + if (!$data['User']['group_id']) { + return null; + } else { + return array('model' => 'Group', 'foreign_key' => $data['User']['group_id']); + } + } + // 更新時に親IDを変更する + function save($data = null, $validate = true, $fieldList = array()) + { + if (parent::save($data, $validate, $fieldList)) { + $conditions = array( + 'model' => $this->name, + 'foreign_key' => $this->id, + ); + App::import('Component', 'Acl'); + $Aro = new Aro; + $Aro->id = $Aro->field('id', $conditions); + $Aro->saveField('parent_id', $data['User']['group_id']); + $Aro->saveField('alias', $this->name . '::' . $this->id); + return true; + } + return false; + } + + /* validation */ function betweenUsername($data) { $idLength = Configure::read('User.UserId.Length'); Index: controllers/components/auth_plus.php =================================================================== --- controllers/components/auth_plus.php (revision 169) +++ controllers/components/auth_plus.php (working copy) @@ -20,6 +20,18 @@ function initialize(&$controller) { + // ACL: controllerごとのactionMap設定マージ + $this->actionMap = array_merge($this->actionMap, $controller->actionMapPlus); + $admin = Configure::read('Routing.admin'); + if (!empty($admin)) { + foreach ($controller->actionMapPlus as $k => $v) { + $this->actionMap = array_merge( + $this->actionMap, + array($admin . '_'. $k => $v) + ); + } + } + parent::initialize($controller); // ログイン後リダイレクト設定 Index: controllers/app_controller.php =================================================================== --- controllers/app_controller.php (revision 169) +++ controllers/app_controller.php (working copy) @@ -19,19 +19,31 @@ { var $isAdmin = false; + var $components = array('AuthPlus', 'Acl'); + + /* ACL */ + // 追加アクション用 crudMap + var $actionMapPlus = array(); + function beforeFilter() { parent::beforeFilter(); + if ($this->AuthPlus) { + // ACL関連 + $this->AuthPlus->actionPath = 'controllers/'; + $this->AuthPlus->authorize = 'crud'; Index: controllers/users_controller.php =================================================================== --- controllers/users_controller.php (revision 169) +++ controllers/users_controller.php (working copy) @@ -3,9 +3,16 @@ var $name = 'Users'; var $helpers = array('Html', 'Form'); - var $components = array('AuthPlus'); + /* ACL */ + // 追加アクション用 crudMap + var $actionMapPlus = array( + 'listview' => 'read', + 'change_password' => 'update', + ); + function beforeFilter() { @@ -192,7 +199,10 @@ $this->set('users', $this->paginate()); } - function _add() { + function _add($group_id=4) { + // ACL設定(デフォルト:一般ユーザ) + $this->data['User']['group_id'] = $group_id; + // バリデーション $this->User->set($this->data); if ($this->User->validates()) { Index: views/layouts/mobile_default.ctp =================================================================== --- views/layouts/mobile_default.ctp (revision 169) +++ views/layouts/mobile_default.ctp (working copy) @@ -18,6 +18,9 @@ <?php if ($session->flash()): ?> <div><?php $session->flash(); ?></div> <?php endif; ?> +<?php if ($session->check('Message.auth')): ?> +<div><?php $session->flash('auth'); ?></div> +<?php endif; ?> <?php echo $content_for_layout; ?> <div id="footer"></div> <?php echo $cakeDebug; ?> Index: views/layouts/default.ctp =================================================================== --- views/layouts/default.ctp (revision 169) +++ views/layouts/default.ctp (working copy) @@ -33,6 +33,9 @@ </div> <div id="content"> <?php $session->flash(); ?> + <?php if ($session->check('Message.auth')): ?> + <div><?php $session->flash('auth'); ?></div> + <?php endif; ?> <?php echo $content_for_layout; ?> </div> <div id="footer">
*1:抜けてたらすみません
*2:でもこれが判らなくて、2つとも行なってデータがグチャグチャになったり・・・
*3:そしてふと思った。メール登録制のセッション管理、これで対応しようか。usersに途中やめのデータ残りますが。
*4:現状、認証不要($this->Auth->allow)設定
*5:User::1=管理人は、試験用に$group_idいじって登録
*6:完了メッセージ略
*7:実装は全然別の方法でやったわけですが ^^;
*9:他にもあり。詳細はcake/libs/controller/components/auth.phpの$actionMap、$this->actionMap関連を参照
*10:関係ない差分は省いてます