cakePHP チュートリアル「ACL を制御するシンプルなアプリケーション」 その2

概要

前回はACLの制御『誰が何に対してアクセス可能か制御する』仕組みの『誰が』の部分を作成しました。
今回は『何に対してアクセス可能か』の部分についてみていきたいと思います。

しかし、何度マニュアルを読んでも理解不明。ちなみに現在日本語サイトはリンク切れです。
そんな時に救世主が現れました。
ブラジルの大地 > CakePHPのACLにはまる...でも、出てくる?!
さっぱりチンプンカンプンだったのが、ぼんやりと理解できてきたような・・・。

それでは早速作業にかかりましょう。

ACO の作成

ACOは『何に対してアクセス可能か』の"何"の部分です。

チュートリアルではまず シェル や AclComponent で ACO を作成する方法が紹介されていますが、これでは「controllers」という名のトップレベルの ACO (あるいは根ノード)しか作成できません。各コントローラーとアクションをひとつずつ登録していくのは面倒なので、次の「自動化するツール」で作成していきます。

  1. [\app\app_controller.php]ファイルに以下の内容を追加します。

    function build_acl() {
      if (!Configure::read('debug')) {
        return $this->_stop();
      }
      $log = array();
      $aco =& $this->Acl->Aco;
      $root = $aco->node('controllers');
      if (!$root) {
        $aco->create(array('parent_id' => null, 'model' => null, 'alias' => 'controllers'));
        $root = $aco->save();
        $root['Aco']['id'] = $aco->id; 
        $log[] = 'Created Aco node for controllers';
      } else {
        $root = $root[0];
      }   
      App::import('Core', 'File');
      $Controllers = Configure::listObjects('controller');
      $appIndex = array_search('App', $Controllers);
      if ($appIndex !== false ) {
        unset($Controllers[$appIndex]);
      }
      $baseMethods = get_class_methods('Controller');
      $baseMethods[] = 'build_acl';
      $Plugins = $this->_getPluginControllerNames();
      $Controllers = array_merge($Controllers, $Plugins);
      // look at each controller in app/controllers
      foreach ($Controllers as $ctrlName) {
        $methods = $this->_getClassMethods($this->_getPluginControllerPath($ctrlName));
        // Do all Plugins First
        if ($this->_isPlugin($ctrlName)){
          $pluginNode = $aco->node('controllers/'.$this->_getPluginName($ctrlName));
          if (!$pluginNode) {
            $aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginName($ctrlName)));
            $pluginNode = $aco->save();
            $pluginNode['Aco']['id'] = $aco->id;
            $log[] = 'Created Aco node for ' . $this->_getPluginName($ctrlName) . ' Plugin';
          }
        }
        // find / make controller node
        $controllerNode = $aco->node('controllers/'.$ctrlName);
        if (!$controllerNode) {
          if ($this->_isPlugin($ctrlName)){
            $pluginNode = $aco->node('controllers/' . $this->_getPluginName($ctrlName));
            $aco->create(array('parent_id' => $pluginNode['0']['Aco']['id'], 'model' => null, 'alias' => $this->_getPluginControllerName($ctrlName)));
            $controllerNode = $aco->save();
            $controllerNode['Aco']['id'] = $aco->id;
            $log[] = 'Created Aco node for ' . $this->_getPluginControllerName($ctrlName) . ' ' . $this->_getPluginName($ctrlName) . ' Plugin Controller';
          } else {
            $aco->create(array('parent_id' => $root['Aco']['id'], 'model' => null, 'alias' => $ctrlName));
            $controllerNode = $aco->save();
            $controllerNode['Aco']['id'] = $aco->id;
            $log[] = 'Created Aco node for ' . $ctrlName;
          }
        } else {
          $controllerNode = $controllerNode[0];
        }
        //clean the methods. to remove those in Controller and private actions.
        foreach ($methods as $k => $method) {
          if (strpos($method, '_', 0) === 0) {
            unset($methods[$k]);
            continue;
          }
          if (in_array($method, $baseMethods)) {
            unset($methods[$k]);
            continue;
          }
          $methodNode = $aco->node('controllers/'.$ctrlName.'/'.$method);
          if (!$methodNode) {
            $aco->create(array('parent_id' => $controllerNode['Aco']['id'], 'model' => null, 'alias' => $method));
            $methodNode = $aco->save();
            $log[] = 'Created Aco node for '. $method;
          }
        }
      }
      if(count($log)>0) {
        debug($log);
      }
    }
    function _getClassMethods($ctrlName = null) {
      App::import('Controller', $ctrlName);
      if (strlen(strstr($ctrlName, '.')) > 0) {
        // plugin's controller
        $num = strpos($ctrlName, '.');
        $ctrlName = substr($ctrlName, $num+1);
      }
      $ctrlclass = $ctrlName . 'Controller';
      return get_class_methods($ctrlclass);
    }
    function _isPlugin($ctrlName = null) {
      $arr = String::tokenize($ctrlName, '/');
      if (count($arr) > 1) {
        return true;
      } else {
        return false;
      }
    }
    function _getPluginControllerPath($ctrlName = null) {
      $arr = String::tokenize($ctrlName, '/');
      if (count($arr) == 2) {
        return $arr[0] . '.' . $arr[1];
      } else {
        return $arr[0];
      }
    }
    function _getPluginName($ctrlName = null) {
      $arr = String::tokenize($ctrlName, '/');
      if (count($arr) == 2) {
        return $arr[0];
      } else {
        return false;
      }
    }
    function _getPluginControllerName($ctrlName = null) {
      $arr = String::tokenize($ctrlName, '/');
      if (count($arr) == 2) {
        return $arr[1];
      } else {
        return false;
      }
    }
    /**
     * Get the names of the plugin controllers ...
     * This function will get an array of the plugin controller names, and
     * also makes sure the controllers are available for us to get the 
     * method names by doing an App::import for each plugin controller.
     * @return array of plugin names.
     */
    function _getPluginControllerNames() {
      App::import('Core', 'File', 'Folder');
      $paths = Configure::getInstance();
      $folder =& new Folder();
      $folder->cd(APP . 'plugins');
      // Get the list of plugins
      $Plugins = $folder->read();
      $Plugins = $Plugins[0];
      $arr = array();
      // Loop through the plugins
      foreach($Plugins as $pluginName) {
        // Change directory to the plugin
        $didCD = $folder->cd(APP . 'plugins'. DS . $pluginName . DS . 'controllers');
        // Get a list of the files that have a file name that ends
        // with controller.php
        $files = $folder->findRecursive('.*_controller\.php');
        // Loop through the controllers we found in the plugins directory
        foreach($files as $fileName) {
          // Get the base file name
          $file = basename($fileName);
          // Get the controller name
          $file = Inflector::camelize(substr($file, 0, strlen($file)-strlen('_controller.php')));
          if (!preg_match('/^'. Inflector::humanize($pluginName). 'App/', $file)) {
            if (!App::import('Controller', $pluginName.'.'.$file)) {
              debug('Error importing '.$file.' for plugin '.$pluginName);
            } else {
              /// Now prepend the Plugin name ...
              // This is required to allow us to fetch the method names.
              $arr[] = Inflector::humanize($pluginName) . "/" . $file;
            }
          }
        }
      }
      return $arr;
    }
    
  2. ブラウザで http://localhost/cake_tutorial/groups/build_acl にアクセスして、ACO テーブルを自動的に構築します。

    コントローラーを$scaffoldで設定しているとアクションの登録が出来ませんのでご注意。

  3. ビューを作成していませんのでエラーが表示されますが、「acos」テーブルにデータが格納されると成功です。

    先ほど追加したコードを削除しておきましょう。
    またこの機能は、削除したアクションをノードから取り除きませんのでアクションを削除したときは手動でお願いします。

パーミッションの設定

『何に対してアクセス可能か』の"何"の部分が出来ましたのでそれに対してアクセスの可否を登録していきます。

  1. [\app\controllers\users_controller.php]の一番下に以下の内容を追加します。

      function initDB() {
        $group =& $this->User->Group;
        // 管理者グループには全てを許可する
        $group->id = 1;     
        $this->Acl->allow($group, 'controllers');
        
        // マネージャグループには posts と widgets に対するアクセスを許可する
        $group->id = 2;
        $this->Acl->deny($group, 'controllers');
        $this->Acl->allow($group, 'controllers/Posts');
        $this->Acl->allow($group, 'controllers/Widgets');
        
        // ユーザグループには posts と widgets に対する追加と編集を許可する
        $group->id = 3;
        $this->Acl->deny($group, 'controllers');        
        $this->Acl->allow($group, 'controllers/Posts/add');
        $this->Acl->allow($group, 'controllers/Posts/edit');        
        $this->Acl->allow($group, 'controllers/Widgets/add');
        $this->Acl->allow($group, 'controllers/Widgets/edit');
      }
    
  2. 次にusers/indexにアクセスしたとき上記のinitDBを呼び出せるように、[\app\controllers\users_controller.php]の function index() に以下の内容を追加します。

      function index() {
        $this->User->recursive = 0;
        $this->set('users', $this->paginate());
        $this->initDB();
      }
    
  3. ブラウザで http://localhost/cake_tutorial/users にアクセスして、パーミッションを構築します。

  4. indexページが表示され、「aros_acos」テーブルにデータが格納されると成功です。

    こちらも追加したコードを削除しておきましょう。

  5. あとは各コントローラーのパブリック設定です。

    [\app\controllers\]の「users_controller.php」と「groups_controller.php」に追加した以下のコードを削除しておきましょう。

    function beforeFilter() {
      parent::beforeFilter(); 
      $this->Auth->allowedActions = array('*');
    }
    

    そして[\app\controllers\]の「posts_controller.php」と「widgets_controller.php」に追加した以下のコードを追加します。これで一覧、詳細ページは誰でも閲覧可能になります。

    function beforeFilter() {
      parent::beforeFilter(); 
      $this->Auth->allowedActions = array('index', 'view');
    }
    

これでバッチリと思いきや、アクセス権のないアクションへアクセスするとリダイレクトの無限ループになってしまいました。

[\app\config\routes.php]でルートパスの設定を行ったら、ループは解消しました。

投稿日:

ページのトップへ戻る