Hướng dẫn dùng get-acl trong PHP

Trong những bài trước tôi đã từng nói tới và sử dụng Auth component, giờ tôi sẽ viết tiếp về Access Control List (ACL) trong CakePHP. Có thể hiểu đại khái Auth là xác nhận có cho phép ai đó vào hệ thống hay không, còn ACL sẽ quyết định xem ai đó sẽ được làm những gì trong hệ thống, nên ACL sẽ là phần sau của Auth. Hay khái quát lên thì ACL sẽ bao gồm 2 đối tượng là Đối tượng muốn một thứ gì đó (Access Request Object - ARO) và Đối tượng mà đối tượng khác muốn (Access Control Object - ACO).

Trước đây, người ta hay sử dụng cách quản lý quyền thông qua một bảng ma trận bao gồm 1 chiều chứa các ARO, chiều còn lại chứa các ACO. Kiểu như ví dụ bên dưới :

ARO\ACOViewAddUpdateDelete
Admin yes yes yes yes
User yes yes no no
Guest yes no no no

Thoạt nhìn thì thấy rất đơn gỉan nhưng chỉ là đối với những hệ thống có ít các đối tượng ACO, ARO. Còn những hệ thống nhiều đối tượng thì việc quản lý bằng bảng ma trận này sẽ rất mệt. Như ví dụ trên, nếu Admin có hàn chục loại, quản lý những khu vực riêng, có quyền riêng và khác nhau ở từng nơi chẳng hạn thì việc phân quyền sẽ trở nên rắc rối hơn nếu dùng bảng ma trận. Nên người ta đã nghĩ đến cách sử dụng đến ACL, bằng việc nhóm lại và phân quyền theo các cấp dưới dạng cây :

  • Admin (Allow : all for both POST, USER)
  • User (Allow: view, add for POST)
  • Free user
  • Silver user (allow : update)
  • Gold user (allow: update, delete)
  • Guest (allow :view)

Như các bạn thấy, chúng ta có thể phân quyền một cách linh hoạt theo từng cấp. Ví dụ như bình thường user sẽ không được cấp quyền update và delete các bài post, mà chỉ được phép view và add. Nhưng với ACL, Free user sẽ có quyền mặc định như thế, còn Silver user sẽ có thêm quyền update, cuối cùng Gold user sẽ có đặc quyền được delete. Giờ chúng ta sẽ đi vào chi tiết hơn cách cài đặt và sử dụng ACL trong CakePHP

Định nghĩa quyền qua INI ACL

Trong CakePHP, bạn có thể sử dụng database để phân quyền theo ACL nhưng còn có một cách khác là sử dụng file INI, nhưng chỉ nên sử dụng nếu ứng dụng của bạn đơn gỉan, có số lượng các đối tượng ACO, ARO ít và cố định. Với những hệ thống phức tạp hơn thì dùng database vẫn sẽ ổn định hơn, có khả năng thêm mới các ARO, ACO.

Để sử dụng cách dùng INI file, bạn cần sửa dòng code dưới ở trong _ app/Config/core.php_ :

// tìm đến 2 dòng này
Configure::write('Acl.classname', 'DbAcl');
Configure::write('Acl.database', 'default');

// thay đổi chúng thành dạng như  dưới
Configure::write('Acl.classname', 'IniAcl');
//Configure::write('Acl.database', 'default'); // comment lại vì chúng ta ko dùng database ACL

Tiếp theo, bạn cần định nghĩa nhưng ARO, ACO mà bạn có trong hệ thống ở file app/Config/acl.ini.php, chúng bao gồm 3 thuộc tính :

  • groups : là tên của nhóm ARO
  • allow : tên của ACO mà ARO có quyền truy cập
  • deny : tên của ACO mà ARO không có quyền truy cập

Với ví dụ trên bạn có thể định nghĩa như sau

/*
 * list những ARO
 */
[admin]
groups = admin

[freeuser]
groups = user
deny = add

[silveruser]
groups = user

[golduser]
groups = user
allow = update

[guest]
groups = guest

/*
 * list các nhóm ARO
 */
[admin]
allow = view, add, update, delete

[user]
allow = view, add

[guest]
allow = view

Việc sau đó sẽ là kiểm tra quyền theo ACL như trên trong những tình huống, chỗ cụ thể và nó sẽ là dùng chung cho cả phần dùng database ACL nên tôi sẽ trình bày sau.

Định nghĩa quyền qua Database ACL

Khởi tạo database

Database ACL bao gồm tập hợp các model để tương tác với database và những câu lệnh console để khởi tạo database, tương tác với ARO, ACO tổ chức dưới dạng cây.

Sau khi bạn đã thiết lập thông tin về DB trong /app/Config/database.php thì có thể dùng CakePHP console để tạo ACL database cho ứng dụng của bạn :

-- di chuyển đến thư mục app
cd /path/to/app
-- tạo DB
Console/cake schema create DbAcl
-- khi chạy lệnh trên, bạn sẽ nhận được 2 câu hỏi (drop bảng và tạo lại) hãy gõ y và enter

Sau khi chạy thành công, chúng ta sẽ có 3 bảng acos, aros, aros_acos trong database của ứng dụng.

Tạo AROs và ACOs

Chúng ta sẽ có 2 cách chính để đặt tên và truy cập khi tạo ra một đối tượng ACL (bao gồm ARO và ACO) mới. Đầu tiên là có thể link một đối tượng ACL trực tiếp tới một bản ghi trong bằng cách chỉ định rõ một tên model và khóa ngoại. Thứ hai là có thể cung cấp một alias cho đối tượng trong trường hợp không có mối quan hệ trực tiếp tới 1 bản ghi.

Và để tạo ra đối tượng ACL thì chúng ta sẽ dùng đến ACL model, và sẽ lưu vào một số trường sau : model, foreign_key, alias, and parent_id.

model và foreign_key cho phép tạo link đối tượng tới model của record (cách 1). Ví dụ như bạn có các AROs tương ứng tới các bản ghi của Group nên việc thiết lập foreign_key của một ARO link đến id của một Group sẽ cho phép tạo liên kết ARO và thông tin Group với một find() call trong User model.

Còn alias chỉ là một label thân thiện với người đọc mà chúng ta có thể dùng để chứng minh một đối tượng ACL không có mối quan hệ trực tiếp với bản ghi trong model. Và thường alias sẽ hữu ích trong việc đặt tên user group hay các ACO.

Về parent_id cho một đối tượng ACL sẽ cho phép điền vào cấu trúc của tree. Khi bạn cung cấp một parent_id của node cha tức là bạn sẽ tạo ra một node con mới.

OK, việc gỉai thích ý nghĩa đã xong, giờ chúng ta khai báo sử dụng ACL component trong controller :

public $components = array('Acl');

Tiếp theo chúng ta sẽ tạo ra các AROs group dạng như đoạn code sau thông qua các action trong controller. Do chúng không có link trực tiếp đến bản ghi nào nên chúng ta sẽ dùng thuộc tính alias :

// tạo ARO group
public function someAction() {
    $aro = $this->Acl->Aro;

    // gỉa sử bạn nhận được từ trên giao diện một mảng các group nhập từ người dùng

    $groups = array(
        0 => array(
            'alias' => 'admin'
        ),
        1 => array(
            'alias' => 'user'
        ),
        2 => array(
            'alias' => 'guest'
        )
     );

    // lặp và dùng hàm create() của ARO để tạo ra các đối tượng ARO group
    foreach ($groups as $group) {
        $aro->create();
        $aro->save($group);
    }
}

Để kiểm tra kết qủa chúng ta sẽ dùng lệnh sau trong CakePHP console :

cake acl view aro

Vậy là đã có những node đầu tiên và trong chúng chưa có child nào cả, hãy thêm cho chúng :

public function someOtherAction() {
    $aro = new Aro();

    // để ví dụ chúng ta sẽ dùng mảng tĩnh, coi như là nhận được từ phía người dùng thao tác trên màn hình
    $childs = array(
        0 => array(
            'alias' => 'freeuser',
            'parent_id' => 1
        ),
        1 => array(
            'alias' => 'silveruser',
            'parent_id' => 1
        ),
        2 => array(
            'alias' => 'golduser',
            'parent_id' => 1
        )
    );

    // tương tự như trên chúng ta sẽ lặp để tạo ra các ARO con
    foreach ($childs as $data) {
        $aro->create();
        $aro->save($data);
    }
}
Nếu bạn muốn thêm nhiều nhóm admin nhỏ như admin phụ trách các bài post, admin phụ trách về người dùng thì thêm child hoàn toàn tương tự

Để ví dụ cách sử dụng model, foreign_key thì cuối cùng là khi tạo ra từng user cụ thể bạn hãy chỉ định user đó thuộc loại user (child) nào. Còn trong thực tế bạn sẽ chỉ cần thiết lập user thuộc loại nào (group) là được, trừ khi có vài user đặc biệt cần thêm quyền hay bỏ quyền mặc định của group đi thì có thể dùng như sau :

// trong UserController.php, sau khi tạo User ta sẽ gọi callback tới afterAddUser
public function afterAddUser() {
    $aro = new Aro();
	// thông tin user nhận về từ người dùng gỉa sử là free
	$userInfo = array(
    	'parent_id' => 5, // 5 là các Free user
        'model' => 'User',
        'foreign_key' => 2356, // id của User
    );
    // tạo ARO
    $aro->create();
    $aro->save($userInfo);
    }
}

Sau đó là tạo ra những ACOs trong tree, việc này là tương tự với việc tạo ra các AROs :

// tạo ACO
public function someAcoAction() {
    $aro = $this->Acl->Aco;

    // gỉa sử bạn nhận được từ trên giao diện một mảng các ACO nhập từ người dùng
    // đó là ACO về 2 controller Post và User
    $acos = array(
        0 => array(
            'alias' => 'Post'
        ),
        1 => array(
            'alias' => 'User'
     );

    // lặp và dùng hàm create() của ACO để tạo ra các đối tượng ACO
    foreach ($acos as $aco) {
        $aco->create();
        $aco->save($aco);
    }
}
Việc tạo ra các child node trong ACO hoàn toàn tương tự ARO nhưng cần cân nhắc vì càng để list các ACO đơn gỉan sẽ gíup bạn càng dễ maintain và sử dụng.

Gán quyền

Chúng ta đã có các ARO, ACO nên việc còn lại là gán quyền (mapping) giữa 2 nhóm này. Tiếp tục với ví dụ trên ta sẽ có :

class UserController extends AppController {
	// tất cả các Controller kế thừa từ AppController nên bạn có thể khai báo đoạn code sau ở đó nếu tất cả các controller đều dùng đến Acl
    public $components = array('Acl');

    public function setAcl() {
    	//chúng ta sẽ cho phép user nói chúng có tất cả các quyền trong POST
		$this->Acl->allow('user', 'POST', '*');
        // riêng đối với Free user sẽ ko được delete
		$this->Acl->deny('freeuser', 'POST', 'delete');
		// ngoài ra, chúng ta còn cho phép Gold  user update list này
        $this->Acl->allow('golduser', 'delete');
    }

Như trên các bạn sẽ thấy cách khai báo ở đây sẽ dung allow() và deny() để thiết lập quyền. Đối số đầu tiên sẽ là các group, thứ hai là một ARO, thứ 3 là quyền. Mặc định CakePHP đã có 4 quyền là create, read, update và delete. Trong vd này thì chúng tương ứng với add(), view(), update() và delete() trong controller. Bạn có thể thêm vào bằng cách add thêm cột vào bảng aros_acos với tiền tố dấu gạch dưới (như là _something). Nếu muốn cấp quyền all thì dùng * như code trên.

Kiểm tra quyền

Sau khi thiết lập xong thì đến bước kiểm tra xem ARO có quyền ACO nào đó hay không. Cấu trúc tương tự phần trên, chỉ là dùng hàm khác : check().

public function index() {
    // tất cả phần check dưới sẽ trả về true
    $this->Acl->check('user', 'POST');
    $this->Acl->check('user', 'POST', 'create');
    $this->Acl->check('user', 'POST', 'read');
    $this->Acl->check('user', 'POST', 'update');
    $this->Acl->check('user', 'POST', 'delete');

    // thay thế cho cách dùng tên ARO thì hoàn toàn có thể dùng 2 thuộc tính model và foreign_id đã khai báo để check như dòng sau
    // trả về false vì Free user ko có quyền xóa
    $this->Acl->check(array('User' => array('id' => 2356)), 'POST', 'delete');

    /*
     * check quyền khác nữa ở đây ...
     */

     /*
     * logic sau khi check quyền lâm ở đây ...
     */
}

Đến đây chúng ta đã xong phần lý thuyết về ACL bao gồm cách cài đặt, thiết lập các đối tượng, gán quyền và kiểm tra quyền. Trong bài sau, tôi sẽ viết demo cho phần ví dụ của bài này. Thanks !