CakePHPのACLを使って権限を管理していると、権限がないアクションへのリンクを隠したくなることがあるので私はこんな感じにしてます。
※注: ユーザーロールによるACLを対象に書いてますので個々のユーザーによるACLだとそのままでは動きません
HtmlHelper::link()を叩く度にACLで権限チェックをしていると大変よろしくないので、まずは以下のようなComponentのinit()を叩いてログインユーザーの権限情報を整理してセッションに格納します。
class PermissionComponent extends Object {
public $components = array('Session', 'Auth', 'Acl');
private $C = null;
public function startup(&$controller) {
$this->C = &$controller;
}
public function init() {
$this->clear();
$aro = $this->Acl->Aro->find('first', array(
'conditions' => array(
'Aro.model' => 'Role',
'Aro.foreign_key' => $this->Auth->user('role_id'),
),
));
$permissions = $this->Acl->Aro->Permission->find('all', array(
'conditions' => array(
'Permission.aro_id' => $aro['Aro']['id'],
),
));
foreach ($permissions as $p) {
if ($p['Permission']['_create'] == 1 && $p['Permission']['_read'] == 1 &&
$p['Permission']['_update'] == 1 && $p['Permission']['_delete'] == 1) {
$allow = true;
} else {
$allow = false;
}
if (!empty($p['Aco']['alias']) && $p['Aco']['alias'] == 'controllers') {
$this->Session->write('Auth.Permissions.controllers', $allow);
} elseif(!empty($p['Aco']['parent_id'])) {
$parent = $this->Acl->Aco->findById($p['Aco']['parent_id']);
$key = 'Auth.Permissions.' . $parent['Aco']['alias'] . '.' . $p['Aco']['alias'];
$this->Session->write($key, $allow);
}
}
}
public function clear() {
$this->Session->delete('Auth.Permissions');
}
}
このPermissionComponent::init()は、ユーザーがログインした時点で実行するようにしています。AuthComponentのデフォルト設定であれば以下のようになるはずです。
class UsersController extends AppController {
public $components = array('Auth', 'Permission');
public function login() {
$user = $this->Auth->user();
if ($user) {
$this->Permission->init();
$this->redirect($this->Auth->loginRedirect);
}
}
public function logout() {
$this->redirect($this->Auth->logout());
}
}
次に、セッションに格納した権限情報をViewからチェックする為のHelperを作ります。
このPermissionHelper::check()は、ログインユーザーが引数に取ったURLへアクセスする権限を持つ場合にtrueを返します。
class PermissionHelper extends AppHelper {
public $helpers = array('Session');
public function check($path){
if(!is_array($path)) {
$path = Router::parse($path);
}
if(empty($path['controller'])) {
$path['controller'] = $this->params['controller'];
}
if(empty($path['action'])) {
$path['action'] = 'index';
}
if(!empty($path['prefix']) && $path['prefix'] == 'admin') {
return true;
}
$allkey = 'Auth.Permissions.controllers';
if($this->Session->check($allkey) && $this->Session->read($allkey) === true){
return true;
}
$key = 'Auth.Permissions.' . Inflector::camelize($path['controller']) . '.' . $path['action'];
if($this->Session->check($key) && $this->Session->read($key) === true){
return true;
}
return false;
}
}
しかし、これでいちいちチェックしてからHtmlHelper::link()も呼ぶのは非常にめんどくさいので、このPermissionHelperを内部で使うようにHtmlHelperを継承したMyHtmlHelperを作ります。
class MyHtmlHelper extends HtmlHelper {
public $helpers = array('Permission');
public function link($title, $url = null, $options = array(), $confirmMessage = false) {
$options = array_merge(array(
'escape' => false,
), $options);
$url = ($url !== null) ? $url : $title;
if($this->Permission->check($url)) {
return parent::link($title, $url, $options, $confirmMessage);;
}
return null;
}
}
このMyHtmlHelper::link()はリンクを出力する前にPermissionHelperを用いて権限チェックを行い、アクセス権限がある場合のみリンクを出力します。
使い方は通常のHtmlHelperと何ら変わりありません。
しかし、わざわざViewで$this->MyHtml->link();などと打つのは大変煩わしいので、@hiromi2424のHackPluginを貰ってきてAppControllerで以下のようにするとviewを変更する必要がなくなるのでとても幸せになれます。
class AppController extends Controller {
public $helpers = array('Hack.alias' => array('Html' => 'MyHtml'));
}
全然関係ない宣伝ですが、今年もPHPMatsuriの準備が本格化してきてます。
PHP Matsuri 2011
今年はPHP5.4, Symfony2, CakePHP2.0, CodeIgniter2.0とPHP界隈が盛り上がってますし、PHP以外にもTitanium mobileやTDD関連のセッション、ワークショップも開催予定です。
協賛企業様も絶賛募集中ですのでぜひ一度お気軽にご連絡ください。
Cake2.0ももうすぐRCですし私もアクティビティAGEAGEで参ります。