वीर

24 เมษายน 2008

CakePHP 1.2.0.6311b: Group + ACL + AuthComponent

หลังจากที่ใช้ ACL กับ AuthComponent ได้แล้ว … ก็จะลองเปลี่ยนตัวอย่างเก่าให้ใช้ Group ได้ดู ก็แก้เยอะเหมือน :-P.

ผมเริ่มจากการแก้ database schema ก่อน. หลังจากที่ลองใช้ migration อยู่พักใหญ่แล้วผมก็งงๆ เลยตัดสินใจสละ table: aros และ users ไป.

เริ่มจากลบของใน table: aros และ aros_acos ก่อน ด้วยคำสั่ง:

mysql -u<your_username> -p<your_password> my_project -e 'delete from aros;'
mysql -u<your_username> -p<your_password> my_project -e 'delete from aros_acos;'

แล้วก็แก้ CakeSchema ของ user ใน folder: app/config/sql, ไฟล์: users.php แก้เป็นแบบนี้:

class UsersSchema extends CakeSchema {

    var $name = 'users';

    function before($event = array()) {
        return true;
    }

    function after($event = array()) {
    }

    var $users =
        array('id' => array('type'=>'integer',
                            'null' => false,
                            'key' => 'primary',
                            'extra' => 'auto_increment'),
              'username' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255),
              'password' =>  array('type'=>'string',
                                   'null' => false,
                                   'length' => 255),
              'group_id' => array('type' => 'integer',
                                  'null' => false),
              'indexes' => array('PRIMARY' => array('column' => 'id',
                                                    'unique' => 1)));
    var $groups =
        array('id' => array('type' => 'integer',
                            'null' => false,
                            'key' => 'primary',
                            'extra' => 'auto_increment'),
              'name' => array('type' => 'string',
                              'null' => 'false',
                              'length' => 255,
                              'unique' => true),
              'parent_id' => array('type' => 'integer'),
              'indexes' => array('PRIMARY' => array('column' => 'id',
                                                    'unique' => true),
                                 'GRP_NAME_KEY' => array('column' => 'name',
                                                         'unique' => true)));

ของใหม่อีกอย่างหนึ่งในตัวอย่างนี้คือ AclBehavior ที่จะช่วย sync ระหว่าง users, groups และ acl. แต่เท่าที่ใช้มา AclBehavior ไม่แก้ค่า alias ใน Aro ก็เลยสร้าง AclaBehavior จากการดัดแปลง AclBehavior นิดๆ หน่อยๆ โดยเพิ่มไฟล์ acla.php ลงใน folder: app/model/behaviors แบบข้างล่าง (จริงแล้วต้องเปิด <?php และปิดด้วย ?> ด้วย … แต่ว่าใส่ใน wordpress แล้วเหมือนเพี้ยนๆ ผมก็แก้ไม่เป็นด้วย.)

/* SVN FILE: $Id: acl.php 6311 2008-01-02 06:33:52Z phpnut $ */
/**
 * ACL behavior class.
 *
 * Enables objects to easily tie into an ACL system
 *
 * PHP versions 4 and 5
 *
 * CakePHP :  Rapid Development Framework <http ://www.cakephp.org/>
 * Copyright 2006-2008, Cake Software Foundation, Inc.
 *                                1785 E. Sahara Avenue, Suite 490-204
 *                                Las Vegas, Nevada 89104
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright        Copyright 2006-2008, Cake Software Foundation, Inc.
 * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
 * @package            cake
 * @subpackage        cake.cake.libs.model.behaviors
 * @since            CakePHP v 1.2.0.4487
 * @version            $Revision: 6311 $
 * @modifiedby        $LastChangedBy: phpnut $
 * @lastmodified    $Date: 2008-01-02 00:33:52 -0600 (Wed, 02 Jan 2008) $
 * @license            http://www.opensource.org/licenses/mit-license.php The MIT License
 */
/**
 * Short description for file
 *
 * Long description for file
 *
 * @package        cake
 * @subpackage    cake.cake.libs.model.behaviors
 */
class AclaBehavior extends ModelBehavior {

/**
 * Maps ACL type options to ACL models
 *
 * @var array
 * @access protected
 */
    var $__typeMaps = array('requester' => 'Aro', 'controlled' => 'Aco');
/**
 * Sets up the configuation for the model, and loads ACL models if they haven't been already
 *
 * @param mixed $config
 */
    function setup(&$model, $config = array()) {
        if (is_string($config)) {
            $config = array('type' => $config);
        }
        $this->settings[$model->alias] = array_merge(array('type' => 'requester'), (array)$config);
        $type = $this->__typeMaps[$this->settings[$model->alias]['type']];

        if (!ClassRegistry::isKeySet($type)) {
            uses('model' . DS . 'db_acl');
            $object =& new $type();
        } else {
            $object =& ClassRegistry::getObject($type);
        }
        $model->{$type} =& $object;
        if (!method_exists($model, 'parentNode')) {
            trigger_error("Callback parentNode() not defined in {$model->alias}", E_USER_WARNING);
        }
    }
/**
 * Retrieves the Aro/Aco node for this model
 *
 * @param mixed $ref
 * @return array
 */
    function node(&$model, $ref = null) {
        $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
        if (empty($ref)) {
            $ref = array('model' => $model->alias, 'foreign_key' => $model->id);
        }

        return $model->{$type}->node($ref);
    }
/**
 * Creates a new ARO/ACO node bound to this record
 *
 * @param boolean $created True if this is a new record
 */
    function afterSave(&$model, $created) {
        if ($created) {
            $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
            $parent = $model->parentNode();

            if (!empty($parent)) {
                $parent = $this->node($model, $parent);
            } else {
                $parent = null;
            }

            $acl_node_alias = null;

            if(method_exists($model, "aclNodeAlias"))
                $acl_node_alias = $model->aclNodeAlias();

            $model->{$type}->create();
            $model->{$type}->save(array(
                'parent_id'        => Set::extract($parent, "0.{$type}.id"),
                'model'            => $model->alias,
                'foreign_key'    => $model->id,
                'alias'         => $acl_node_alias
            ));
        }
    }
/**
 * Destroys the ARO/ACO node bound to the deleted record
 *
 */
    function afterDelete(&$model) {
        $type = $this->__typeMaps[strtolower($this->settings[$model->alias]['type'])];
        $node = Set::extract($this->node($model), "0.{$type}.id");
        if (!empty($node)) {
            $model->{$type}->delete($node);
        }
    }
}

ไม่พอก็ยังต้องแก้ CakeShell ต่อใน folder: app/vendors/shells

แก้ user.php:

uses ('controller'.DS.'components'.DS.'auth');

class UserShell extends Shell {
    var $uses = array("User", "Group");
    function add() {
        $auth = new AuthComponent();
        $username = $this->args[0];
        $password = $auth->password($this->args[1]);
        $grp_name = $this->args[2];
        $grp = $this->get_group($grp_name);
        $data = array("User" =>
                      array("username" => $username,
                            "password" => $password,
                            "group_id" => $grp["id"]));
        if($this->User->save($data))
            print "Success: $username was added\n";
        else
            print "Fail\n";
    }

    function get_group($name) {
        $data = $this->Group->findByName($name);
        if(!$data)
            return null;
        else
            return $data["Group"];

    }
}

สร้าง group.php ขึ้นมาใหม่:

class GroupShell extends Shell {

    var $uses = array("Group", "Aro");

    function add() {
        $name = $this->args[1];
        $parent_name = $this->args[0];
        if($parent_name == '/')
            $parent_id = null;
        else
            $parent_id = $this->get_parent($parent_name);
        $data = array("Group" =>
                      array("parent_id" => $parent_id,
                            "name" => $name));
        if($this->Group->save($data))
            print "Success: group: $name has been added\n";
        else
            print "Fail\n";
    }

    function get_parent($name) {
        $data = $this->Group->findByName($name, null, null, False);
        if(!$data['Group']['id'])
            return null;
        else
            return $data['Group']['id'];
    }
}

ยังไม่หมดต้องมาแก้ model ต่อใน folder: app/model

แก้ user.php:

class User extends AppModel {
    var $actsAs = array('Acla');
    var $belongsTo = array('Group');

    function parentNode() {
        if(!$this->id)
            return null;

        $data = $this->read();
        if(!$data['Group']['id'])
            return null;
        else
            return array('model' => 'Group',
                         'foreign_key' => $data['Group']['id']); 
    }
}

ตามด้วยเขียน group.php ขึ้นมาใหม่:

class Group extends AppModel {
    var $actsAs = array('Acla');
    var $hasMany = array('User');
    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']);
    }

    function aclNodeAlias() {
        if(!$this->id)
            return null;
        $data = $this->read();
        if(!$data['Group']['name'])
            return null;
        else
            return $data['Group']['name'];
    }

}

จาก code ที่แก้มามากมายก็จะเริ่มเรียกใช้แล้ว. โดย cd เข้าไปใน cake/console สั่ง: ./cake schema run create users แล้วก็กด y ไปเรื่อยๆ. เพื่อที่จะ drop table: users เก่าทิ้งไป แล้วสร้าง table: users และ groups ขึ้นมาใหม่.

ตามด้วยสร้าง group ขึ้นมาใหม่แบ่งเป็น admins กับ users ตามนี้:

./cake group add ‘/’ users

./cake group add ‘/’ admins

แล้วก็ add user ไว้ทดลอง group ละ 1 user:

./cake user add myadmin mypassword admins
./cake user add myuser mypassword users

grant ให้ admins ดูได้ทั้ง site (ทุกหน้า, ทุก action):

./cake acl grant admins site ‘*’

grant ให้ users ดู Pages/display (เอาไว้ redirect มาเวลาดูหน้าอื่นไม่ได้). และ ดู Books/display1:

./cake acl grant users ‘Pages/display’ ‘*’

./cake acl grant users ‘Books/display1′ ‘*’

ถ้าลอง login ด้วย myadmin ก็จะดูได้ทุกหน้า. แต่ถ้า login ด้วย myuser ก็จะดู http://localhost/my_project/books/display2 ไม่ได้.

ก็เป็นอันว่า users แต่ละกลุ่ม ก็ดูหน้าเว็บได้ต่างๆ กันตามที่กำหมดไว้ :-).

อ้างอิง:

http://lemoncake.wordpress.com/2007/07/19/acl-with-groups/

2 ความคิดเห็น »

  1. เจ๋งมากเลยครัีบ แต่ขอเป็น comment ภาษาไทยแทรกใน code จะดีกว่าครัีบ

    เพราะคำสั่งมันเยอะอะครับ นั่งวิเคราะห์เอง บางคำสั่งก็ไม่รู้จักเลยงงเลย

    ความเห็น โดย หนุ่ม — 9 กันยายน 2008 @ 06:52

  2. คำสั่งไหนไม่รู้ถามได้นะครับ จะได้เพิ่ม comment เป็นจุดๆ ไป

    ความเห็น โดย वीर — 9 กันยายน 2008 @ 11:22


RSS feed สำหรับความคิดเห็นในกระทู้นี้ TrackBack URI

เขียนความคิดเห็นของคุณ

บลอกที่ WordPress.com .