高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

Zend Framework之Zend_ACL

在Zend_Acl當中有兩個重要的部份:Resource(資源) 與 Role(角色),其中「角色」存取「資源」,舉個白話的例子,在一個進出受管制的電梯大樓,小明可以進入101樓的辦公室,表示小明這個「角色」,他的 通行證可以刷卡進入101樓的電梯,進入101樓這個「資源」。

比較常見的是網站管理系統的權限實作,例如編輯部的同仁只能用網站的內容編輯系統,廣告部則可以使用廣告刊播系統以及報表系統,但沒有使用內容編輯的權限。那在ZF中,Zend_Acl如何實作這樣的概念呢?

一、建立資源:

Zend_Acl裡有個Zend_Acl_Resource_Interface這個介面可以方便的來建立資源。

實作自這個介面的類別,會有一個getResourceId()這個方法,代表它是一個資源。另外,Zend_Acl_Resource類別中也包含一些Zend_Acl的一些實作,讓開發者可以直接繼承來使用。

Zend_Acl使用樹狀結構的方式來管理資源,透過這個樹狀結構,可以新增新的資源進來,並儲存在結構中。

對樹狀結構上層的修改,亦可影響至下層,不過即使下層不想繼承上層的規則,也可以簡單的加上一些例外規則,在這結構中,Resource只能一次繼承自一個Parent,不過每個Parent都可以有各自的Parent,以此類推。

二、建立角色:

透過Zend_Acl_Role_Interface的實作,建立角色也是相當容易的。如同資源的建立,角色可透過實作的getRoleId()方法來建 立。同Zend_Acl_Resource,Zend_Acl裡也有個Zend_Acl_Role的類別可以直接繼承來使用。

不過與資源不同的,一個角色可以一次繼承自一個或多個角色。

以上面那個網站管理系統的例子來說,假設eddie可能只是編輯部的同事(單一角色),而joanne是企劃部門的主管,她除了管企劃部門的案子之外,亦可能需要看一下網站的新聞是不是有錯字,則可以說她的角色同時屬於”企劃部”及”編輯部”(多重角色)。

其 實透過角色繼承的方式,可以在設計系統時不用指定特定某個人的使用權限。例如設定”編輯部”的權限是可以用內容管理系統,然後只要讓eddie”屬於”這 個部門,eddie這個帳號也可以有使用的權限了;若同像上面joanne的例子,只要讓她同時”屬於”企劃部跟編輯部即可,在設定上相當簡潔且方便。

我們以上面這個例子來實作一小段程式碼,設定三種角色:編輯部editor、企劃部planner以及網站系統管理員administrator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
  require_once 'Zend/Acl.php';
  require_once 'Zend/Acl/Role.php';
  require_once 'Zend/Acl/Resource.php';

  //建立一個Access Control List(ACL)物件
  $acl = new Zend_Acl();

  //在ACL中建立三個角色:editor、planner及administrator
  $acl->addRole(new Zend_Acl_Role('editor'))
      ->addRole(new Zend_Acl_Role('planner'))
      ->addRole(new Zend_Acl_Role('administrator'));

  //建立joanne角色,並設定此角色同時屬於editor及planner兩個角色
  $group1 = array('editor', 'planner');
  $acl->addRole(new Zend_Acl_Role('joanne'), $group1);

  //在ACL中建立三個資源:內容管理系統ContentManager、廣告系統ADManager及系統管理SystemAdmin
  $acl->add(new Zend_Acl_Resource('ContentManager'))
      ->add(new Zend_Acl_Resource('ADManager'))
      ->add(new Zend_Acl_Resource('SystemAdmin'));

  //設定角色與資源之關係
  $acl->allow('editor', 'ContentManager');     //editor角色允許使用ContentManager資源
  $acl->allow('planner', 'ADManager');          //planner角色允許使用ADManager資源
  $acl->allow('administrator', 'SystemAdmin'); //administrator角色允許使用SystemAdmin資源

  //測試權限
  //joanne不屬administrator角色....
  echo $acl->isAllowed('joanne', 'SystemAdmin')?'allowed':'denined';     //得到denined

  //joanne屬於editor角色...
  echo $acl->isAllowed('joanne', 'ContentManager')?'allowed':'denined';   //得到allowed

上面這段程式碼中,因為joanne這個角色沒有SystemAdmin資源的使用權限,所以以isAllowed方法測試時會得到false。

Zend_Acl 在判斷isAllowed的時候,除了會去看被查的那個角色本身(上例中則為joanne)對資源的存取權限,也會從該角色所屬的角色群來詢問該角色是否 有足夠權限。上例中,其實只是將joanne繼承了editor角色,並沒有直接的定義joanne與ContentManager之間的關係,但因為 editor是有權限可以使用ContentManager,所以joanne也跟著有相同的權限。

不過要特別注意的是,上面的例子中,$group1的前後順序是有差別的,在某些情況下,可能會造成非預期的答案,舉例如下:

1
2
3
4
5
6
7
8
<?php
  $group2 = array('editor', 'planner', ‘administrator’);
  $acl->addRole(new Zend_Acl_Role(‘eddie’), $group2);
  $acl->allow('editor', 'ContentManager');  //設定editor可以使用ContentManager
  $acl->deny('planner', 'ContentManager');   //設定planner不可以使用ContentManager

  //測試結果
  echo $acl->isAllowed(‘eddie’, 'ContentManager')?'allowed':'denined';  //得到denined

但如果改一下$group2的順序則會得到不同的結果:

1
2
3
<?php
  $group2 = array('planner', 'editor', ‘administrator’);
  echo $acl->isAllowed(‘eddie’, 'ContentManager')?'allowed':'denined';  //得到allowed

第 一個例子中,因為eddie並沒有”直接”的被定義規則,所以Zend_Acl往它所繼承的上層角色搜尋,它會先搜尋”administrator”角 色,結果沒符合的規則,它會再往下一個”planner”搜尋,發現了”deny”的規則,搜尋結束,回傳false;而第二個例子中,先搜 尋”administrator”角色,然後再搜尋”editor”,發現”allow”的定義,搜尋結束,回傳true。 。

Zend_Acl在搜尋角色規則時,會在搜尋到第一個有定義規則時回傳答案。

所以如果直接定義如下:

1
2
3
<?php
$acl->allow(“eddie”, “ContentManager”);
echo $acl->isAllowed(‘eddie’, 'ContentManager')?'allowed':'denined';  //得到allowed

如同上面我們規納出來的規則,它在遇到第一個定義時(就是它本身的角色)就回傳true了。

存取控制清單(Access Control List – ACL)的建立

我們在上一段已經可以設定某個帳號有權限可以使用網站的內容管理系統了,但如果還需要更細部的設定,例如企劃部的同仁帳號只能”檢視”文章,編輯部的帳號可以”檢視”及”編輯”文章,主管則有全部的功能。

我們假設把內容管理系統的權限再細分為三種:vieweditdelete

角色 權限
planner view
editor view, edit
manager view, edit, delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
  require_once 'Zend/Acl.php';
  require_once 'Zend/Acl/Role.php';

  //建立ACL
  $acl = new Zend_Acl();

  //建立三個角色 planner、editor及manager
  $acl->addRole(new Zend_Acl_Role('planner'));
  $acl->addRole(new Zend_Acl_Role('editor'));
  $acl->addRole(new Zend_Acl_Role('manager'));

  $acl->allow('planner', null, 'view');
  $acl->allow('editor', null, array('view','edit'));
  $acl->allow('manager', null , array('view', 'edit', 'delete'));

  //測試:企劃部能檢視文章?
  echo $acl->isAllowed('planner', null, 'view') ? "allowed" : "denied";  //allowed
  //測試:企劃部能編輯文章?
  echo $acl->isAllowed('planner', null, 'edit') ? "allowed" : "denied";  //denined
  //測試:編輯部能編文章?
  echo $acl->isAllowed('editor', null, 'edit') ?  "allowed" : "denied";  //allowed
  //測試:編輯部能刪除文章?
  echo $acl->isAllowed('editor', null, 'delete') ?  "allowed" : "denied"; //denined
  //測試:主管可刪除文章?
  echo $acl->isAllowed('manager', null, 'delete') ?  "allowed" : "denied"; //allowed

上面這段程式碼分別對三個角色(planner、editor及manager)定義了使用的權限。如果仔細觀察權限列表,用角色繼承的方式可以更有彈性的完成實作。

1
2
3
4
5
6
7
8
9
10
<?php
//建立三個角色,manager繼承editor,editor繼承planner
  $rolePlanner = $acl->addRole(new Zend_Acl_Role('planner');
  $roleEditor = $acl->addRole(new Zend_Acl_Role('editor'), $rolePlanner);
  $roleManager = $acl->addRole(new Zend_Acl_Role('manager'), $roleEditor);

  //對各別角色定義權限
  $acl->allow($rolePlanner, null, 'view');
  $acl->allow($roleEditor, null, 'edit');
  $acl->allow($roleManager, null, 'delete');

如此一來,雖然editor只有定義了”edit”的權限,但因為它繼承自planner角色,所以它也有”view”的功能,同理,manager則同時擁有繼承別的角色的”view”、”edit”及自己定義的”delete”權限。

更複雜的權限設定

前面的章節說明了如何定義角色與資源之間的關係,除了指定某帳號可使用某資源外,也可以定義某帳號可以使用該資源的特定功能,如檢視、修改、刪除等。不過我 們可能會遇到更複雜的例子,例如同樣是編輯部的三個同事:eddie、lindsay及jessica,其中eddie負責的是電子報的工作,還偶爾幫忙 lindsay看一下她的電子報有沒有錯字,lindsay負責的是網站最新消息的發布,jessica則是負責網站一般文章的撰寫,所以接下來我們要來 針對內容管理系統這個資源做更細部的權限設定。

我們將上述的角色及工作列一個簡單的表格:

角色 功能 權限
eddie epaper view, edit, send
news view
lindsay news view, edit, publish
jessica content view, edit, publish

程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
  require_once 'Zend/Acl.php';
  require_once 'Zend/Acl/Role.php';
  require_once 'Zend/Acl/Resource.php';
  $acl = new Zend_Acl();

  //定義三個角色
  $acl->addRole(new Zend_Acl_Role('eddie'));
  $acl->addRole(new Zend_Acl_Role('lindsay'));
  $acl->addRole(new Zend_Acl_Role('jessica'));

  //定義三個資源
  $acl->add(new Zend_Acl_Resource('epaper'));
  $acl->add(new Zend_Acl_Resource('news'));
  $acl->add(new Zend_Acl_Resource('content'));

  //定義規則
  $acl->allow('eddie', 'epaper', array('view', 'edit', 'send'));
  $acl->allow('eddie', 'news', 'view');
  $acl->allow('lindsay', 'news', array('view', 'edit', 'publish'));
  $acl->allow('jessica', 'content', array('view', 'edit', 'publish'));

  //測試:eddie是否可編輯電子報?
  echo $acl->isAllowed('eddie', 'epaper', 'edit') ? "allowed" : "denied"; // allowed
  //測試:lindsay是否可編輯網站文章?
  echo $acl->isAllowed('lindsay', 'content', 'edit') ? "allowed" : "denied"; // denied
  //測試:jessica是否可發布網站文章?
  echo $acl->isAllowed('jessica', 'content', 'publish') ? "allowed" : "denied"; // allowed

上面這段,我們是針對各別角色來做資源及權限的設定,不過只要人數一多,這樣的做法顯然不切實際,建議就改用之前提到的角色繼承的方式讓它更有彈性。

移除權限

要移除權限設定也是相當容易的,只要用removeAllow()removeDeny()這兩個方法就行了。

1
2
3
4
<?php
  //..延用上一章節的程式碼..
  //移除eddie對電子報的編輯權限
  $acl->removeDeny('eddie', 'epaper', 'edit');

進階應用

一、以資料庫管理ACL

Zend_Acl在角色及資源的權限設定上相當容易,但如果能配合資料庫系統做管理,則可更方便管理每個角色及資源之間之關係。

二、進階權限設定

除了原本Zend_Acl裡定義的角色、資源、權限的關係外,也可以再結合其它程式功能(例如IP白名單),或是只能在限定時間內使用該權限等等設定,使整個權限管控更加安全。

本文主要是參照Zend Framework的官方文件自己試寫的,新手上路難免有誤,若有謬誤還請指教

官方網站上也有附了一段程式碼:http://framework.zend.com/manual/en/zend.acl.advanced.html,有興趣的可以上去看看 :)

Comments