權限控制表(ACL)

第一節

了解ACL運作方式

大部分強大又重要的東西都需要一些權限控制。 權限控制表是一種分級明確又好管理的權限控制方式。 權限控制表(或簡稱ACL)主要管理二種事物:需要使用服務的事物,和被控制的事物。 以ACL的角度看,需要使用服務的事物(通常是使用者)叫access request object,簡稱ARO。 而系統內被控制的事物(通常是action或資料)叫access control object,簡單ACO。 每一個對象被稱為一個物件(object),因為有時候這些對象並不是人-有時候可能是程式中另一部分的邏輯。 ACO可以是任何你想要控制的東西,從controller的action 到一項web service,甚至是你祖母的線上日記裡的一行文字。

用一句話來說明這三個簡稱:ACL是用來決定ARO是否可以使用ACO的規範。

為了讓你更加了解這個概念,讓我們舉個實用的例子。 一群魔戒中的勇者們共同使用一組電腦系統。隊長必須在努力衝次任務的同時,在成員間守住一定程度的秘密與安全性。 ARO包含下面幾位:

Gandalf
Aragorn
Bilbo
Frodo
Gollum
Legolas
Gimli
Pippin
Merry

這些是系統中會要求使用服務的使用者(ARO),他們要使用的東西就是ACO。 這裡可以注意到,ACL並不是一種使用者認証系統。 你應該早有另一套存放使用者資料,並在他們進入系統時確認身份的方法。 在確認道使用者身份之後,才是ACL 站出發揮功用的時機。好,話題回到我們的勇者們身上。

Gandalf下一步得做的事是列出系統要管理的物品列表(ACO)。看起來會像這樣:

Weapons
The One Ring
Salted Pork
Diplomacy
Ale

傳統的方法裡,系統會用矩陣的方式管理,矩陣中顯示使用者與各物品間的使用權限。 若將這些資料成列成表,看起來就像下面所示,'X'表示拒絕存取,'O'表示允許存取:

         Weapons    The One Ring    Salted Pork      Diplomacy       Ale
Gandalf     X             X              O               O            O
Aragorn     O             X              O               O            O
Bilbo       X             X              X               X            O
Frodo       X             O              X               X            O
Gollum      X             X              O               X            X
Legolas     O             X              O               O            O
Gimli       O             X              O               X            X
Pippin      X             X              X               O            O
Merry       X             X              X               X            O

這樣的設計,看起來很完善。既可以保守機密(只有Frodo可以拿到魔戒),又可以防止意外發生(哈比人碰不到肉乾)。 權限似忽設計的很好,也很容易看得懂,對吧?

對這麼一個小系統而言,矩陣的的設定方式也許已經足夠。 但面對會慢慢變大的系統或系統裡有一大堆資源(ACO)和使用者(ARO)時,就無法這麼輕易的建立這個表了,比如說要為每個單位分配使用上百個營地。 矩陣的另一個缺點是無法真的為使用者分組,或依不同的組別給與不同的權限。 例如,我們想在戰爭結束後解除哈比人對酒和肉乾的禁令: 若只能分別更改每個使用者的權限,不但要改的項目很多,出錯的機率也非常高;若有辦法直接更改'哈比人'這一組的權限,一切就簡單多了。

ACL 以樹狀結構進行設計,通常會有一棵ARO的樹和一棵ACO的樹。 把物件整理成樹狀圖,不但可以做細部的設定,還可以以巨觀的方式管理。 身為一個叡智的領導者,Gandalf選擇在新系統中使用ACL,把他的物件整理如下:

Fellowship of the Ring
    Warriors                
        Aragorn             
        Legolas
        Gimli
    Wizards                 
        Gandalf
    Hobbits                 
        Frodo               
        Bilbo
        Merry              
        Pippin              
    Vistors                 
        Gollum

將我們的團隊以這種方式整理之後,便可以對這棵樹的每個結點定義使用權限了。 預設的權限是所有東西都不能使用,接者向下層一項一項權限開放。 最後開放的一定是最在意的事物。所以,依據ARO樹,Gandalf把權限如此設計:

Fellowship of the Ring: [Deny: ALL]
    Warriors                [Allow: Weapons, Ale, Elven Rations, Salted Pork]
        Aragorn             
        Legolas
        Gimli
    Wizards                 [Allow: Salted Pork, Diplomacy, Ale]
        Gandalf
    Hobbits                 [Allow: Ale]
        Frodo               
        Bilbo
        Merry               
        Pippin              
    Vistors                 [Allow: Salted Pork]
        Gollum

若想透過ACL 看看Pippin是否可以拿到Ale,首先查出他在樹中的路徑是Fellowship->Hobbits->Pippin。 然後看看每個節結的權限,其中對Pippin最明確的設定就是我們想得知的答案。

  1. Fellowship = DENY Ale,所以不允許(因為被設成所有東西都不允許)

  2. Hobbits = ALLOW Ale,所以允許

  3. Pippin = ?; 這裡沒有做任何特別的設定。

  4. 最終結論:允許使用ale。

我們還可以透過這棵樹對更底層做更細節的設定,變更上層的設定:

Fellowship of the Ring: [Deny: ALL]
    Warriors                [Allow: Weapons, Ale, Elven Rations, Salted Pork]
        Aragorn             [Allow: Diplomacy]
        Legolas
        Gimli
    Wizards                 [Allow: Salted Pork, Diplomacy, Ale]
        Gandalf
    Hobbits                 [Allow: Ale]
        Frodo               [Allow: Ring]
        Bilbo
        Merry               [Deny: Ale]
        Pippin              [Allow: Diplomacy]
    Vistors                 [Allow: Salted Pork]
        Gollum

你可以發現,Aragorn的權限雖然和所屬的Warrior組一樣,但仍可以單獨給他特別的權限。 再說明一次,我們把預設權限設成DENY,然後一層層向下,將想開放的設回ALLOW。 想看看Merry是否可以拿到Ale,先查一下Merry在這棵樹的路徑為:Fellowship->Hobbits->Merry, 然後查出每個節點所設與ale相關的權限:

  1. Fellowship = DENY 因為設定為全部拒絕

  2. Hobbits = ALLOW: ale, 所以允許

  3. Merry = DENY ale, 所以拒絕

  4. 最終結論:不能使用ale。

第二節

定義權限:Cake的INI型ACL

Cake的第一套ACL系統是透過存於Cake安裝設定裡的INI檔進行設定。 雖然它既實用又穩定,我們還是建議您使用以資料庫為基礎的ACL,最大原因是它可以動態建立ACO和ARO。 舊的ACL(ini版)在簡單的應用中還是非常好用,特別是對那些不想使用資料庫的人而言。

ARO/ACO的權限被定義在 permissions are specified in/app/config/acl.ini.php中。 使用的設定指定可以在acl.ini.php檔的開頭找到說明:

; acl.ini.php - Cake ACL 設定
; ----------------------------------------
; 使用這個檔案指定使用者權限
; aco = access control object (系統裡的某樣事物)
; aro = access request object (需要使用服務的某樣事物)
;
; 使用者資料加入方法如下:
;
; [使用者名稱]
; groups = group1, group2, group3
; allow = aco1, aco2, aco3
; deny = aco4, aco5, aco6
;
; 群組資料加入方法也差不多:
;
; [群組名稱]
; allow = aco1, aco2, aco3
; deny = aco4, aco5, aco6
;
; allow, deny, 和 groups 都可以省略。
; 注意:群組名稱千萬*不可以*和使用者名稱相同!!
; ACL 權限檢查順序如下:
; 1. 檢查使用者不允許的(如果找到穩合的,就決定不允許)
; 2. 檢查使用者允許的(的(如果找到穩合的,就決定允許)
; 3. 看看使用者屬於那些群組
; 4. 檢查群組不允許的(如果找到穩合的,就決定不允許)
; 5. 檢查群組允許的(如果找到穩合的,就決定允許)
; 6. 如果全部都沒找到,則決定不允許

這INI檔可用來定義使用者(ARO)所屬的群組與權限,也可用於定義群組的權限。

譯註:原文要大家去11.4節找更詳細的資料,不過譯者沒找到。乾脆直接把在acl.ini.php檔發現的說明文字放進來讓大家參考。

第三節

定義權限:Cake的資料庫型ACL

開始

ACL權限資料預設存放於資料庫中。資料庫型ACL(或叫dbACL)含有一組核心model,和可由命令列執行的script。 這model用來與資料互動,存放與讀取ACL的樹狀資料。 命令列的script則幫你開個頭,讓你在一開始學習就能和這棵樹互動。

一開始,你必需確定/app/config/database.php有存在,且設定正確。 對於一個剛剛安裝好的Cake系統來說,分辨這件事最容易的方法就是用web流覽器連到安裝目錄。 在這一頁最上方應該會看到"Your database configuration file is present."和"Cake is able to connect to the database."。 如果有錯誤,請參考4.1節,會教您如何設定資料庫。

接著,使用ACL命令列script將資料庫初始化,以便儲存ACL資料。 /cake/scripts/acl.php這支script可以幫助完成這件事。 在/cake/scripts/目錄下執行以下的命令:

使用acl.php將資料庫初始化

$ php acl.php initdb

Initializing Database...
Creating access control objects table (acos)...
Creating access request objects table (acos)...
Creating relationships table (aros_acos)...

Done.

此時,在這個專案的資料庫中應該可以看到新的資料表。 如果你對Cake建立的樹狀資料感到好奇,可以打開資料庫看個清楚。 基本上,它存放的是各節點與節點在樹中的位置。 而acos和aros資料表則存放與他們相關的節點,aros_acos資料表則將aros和acos連結起來。

現在,你可以開始建立自己的ARO和ACO樹了。

建立ARO和ACO

有二種方法取用某個ARO或ACO。 其一是指定一個數字的id,而這個數字通常就是資料表的主鍵。 另一個方法是指定一個字串別稱。 這二種方法並不是互斥的。

建立ARO的方法是使用Aro model裡的方法。 Aro類別的create()方法有三個參數:$link_id,$parent_id,和$alias。 這個方法會在$parent_id下建立新的ACL物件,如果$parent_id是null,則表示這是個根物件。 $link_id則可以將使用者物件連結到ACL結構內。 $alias則可以讓你透過別名取用ARO物件,而不是沒意義的數字。

在建立ACO和ARO之前,必需先載入這些類別。 最簡單的方法是在controller裡使用$components陣列把ACL component包含進來使用:

var $components = array('Acl');

完成之後,讓我們看看建立這些物件的程式看起來是什麼樣子。 以下的程式碼可以放在controller action的任何地方:

$aro = new Aro();

// 首先,建立幾個ARO
// 這些物件的父層一開始並沒有東西

$aro->create( 1, null, 'Bob Marley' );
$aro->create( 2, null, 'Jimi Hendrix');
$aro->create( 3, null, 'George Washington');
$aro->create( 4, null, 'Abraham Lincoln');

// 現在,建立幾個群組將這些使用者組織起來:
// 注音,這些物件的ID是0,因為他們永遠不會代表系統內任何一個使用者。

$aro->create(0, null, 'Presidents');
$aro->create(0, null, 'Artists');

//再來,把所有的ARO與相對的群組連起來:

$aro->setParent('Presidents', 'George Washington');
$aro->setParent('Presidents', 'Abraham Lincoln');
$aro->setParent('Artists', 'Jimi Hendrix');
$aro->setParent('Artists', 'Bob Marley');

//簡而言之,建立ARO的方法如下:
$aro = new Aro();
$aro->create($user_id, $parent_id, $alias);

你也可以在命令列模式下用$acl.php create aro <link_id> <parent_id> <alias> 這個script建立ARO。

ACO的建立方法也類似:

$aco = new Aco();

//建立一些ACO物件
$aco->create(1, null, 'Electric Guitar');
$aco->create(2, null, 'United States Army');
$aco->create(3, null, 'Fans');

// 要為這些物件建立群組,可以使用setParent(),在這個範例如就不示範了。

//所以,建立ACO的方法為:
$aco = new Aco();
$aco->create($id, $parent, $alias);

命令列模式下的script:

$acl.php create aco <link_id> <parent_id> <alias>

指定權限

建立ACO和ARO後,我們終於可以開始指定二個群組間的權限了。 負責做這件事的,是Cake的核心Acl component。 繼續我們剛剛的範例:

// 首先,我們必需在controller裡使用Cake的ACL component

class SomethingsController extends AppController
{
    // 宣告對Acl component的使用權

    var $components = array('Acl');

    // 記得:ACL在沒有任何資訊的情況下,預設是拒絕所有的事。
    // 所有的存取動作都會被禁止。讓我們對ARO開啟一些對ACO的
    // 存取權限。

    function someAction()
    {
        // 允許

        // 這兒是給予ARO對ACO完整控制權的地方
        $this->Acl->allow('Jimi Hendrix', 'Electric Guitar');
        $this->Acl->allow('Bob Marley',   'Electric Guitar');

        // 我們也可以對群組指配權限,記得嗎?
        $this->Acl->Allow('Presidents', 'United States Army');

        // allow()方法其實有第三個參數,$action。
        // 可以使用這個參數只指定部分存取功能。
        // $action可以設成create, read, update 或 delete。
        // 如果沒有指定,則假設全部一起指定。

        $this->Acl->allow('George Washington', 'Electric Guitar', 'read');
        $this->Acl->allow('Abraham Lincoln',   'Electric Guitar', 'read');

        // 拒絕

        // 設定拒絕的方法和上面類似:
        $this->Acl->deny('Abraham Lincoln', 'United States Army');


    }
}
 

這麼一個特殊的範例也許沒什麼特別的功用,但它可以向您展示整個流程。 把Acl component和使用者管理的controller合在一起使用最有用。 當系統建立了一個使用者,他的ARO可以同時被建立,並放在正確的節點上。 接著依他的狀況將權限設到某個ACO或ACO群組上。

權限也可以在命令列下透過script指派。 語法和model裡的函式類似,若實在不懂,直接執行$php acl.php help也會列出使用方法。

第四節

檢查權限:ACL Component

檢查權限是使用Cake ACL裡最簡單的一部分:只有一個函式,check()。 在AppController的某個地方進行ACL檢查是個不錯的主意,只要放在那兒, 就可以在應用程式的任何地方進行權限檢查。下面的個範例:

class AppController extends Controller
{
    // 取得component
    var $components = array('Acl');

    function checkAccess($aco)
    {
        // 使用component檢查權限
        $access = $this->Acl->check($this->Session->read('user_alias'), $aco, $action = "*");

        //拒絕存取
        if ($access === false)
        {
            echo "access denied";
            exit;
        }
        //允許存取
        else
        {
            echo "access allowed";
            exit;
        }
    }
}

基本上,只要讓Acl component在AppController裡可以使用,應用程式的其他地方就都可以使用。

// 這是基本的呼叫法:
$this->Acl->Check($aro, $aco, $action = '*');

附錄:讀者筆記

FW ACL 

阿比說:Cool , 寫的真是不錯 很詳細呦