2010-01-12 58 views
42

語境:實用Zend_Acl中+ Zend_Auth的實施和最佳做法

我的問題涉及到一個論壇,我開發非常酷似SO,那裏有:

誰有權訪問
  1. 客人查看線索但無法回覆或投票
  2. 成員誰擁有足夠的代表可以編輯/投票其他線程,默認情況下他們可以回覆並擁有與客人相同的權限
  3. 管理員誰可以漂亮m什麼都做

我想要這個ACL應用於整個網站,默認情況下拒絕所有資源。

我讀了使用Zend_Acl的基礎知識 - 基本上創建了角色(guest,member,admin),並且拒絕或允許這些角色的資源(控制器,方法)。該文檔不是你應該如何真正實現在應用程序中的ACL代碼非常具體的,所以我去尋找對SO ...

跨越非常有用的計算器answer from marek這揭示了這個問題的一些光來了,但是由於我的不熟悉,我仍然無法完全理解如何在考慮到最佳實踐的情況下正確實施。

的海報具有在初始化ACL對象,將角色的應用程序根目錄的靜態文件configAcl.php,創建資源每控制器,使admin獲得的一切,讓normal獲得的一切,但管理員和存儲訪問控制列表對象在註冊表中供以後使用。

$acl = new Zend_Acl(); 

$roles = array('admin', 'normal'); 

// Controller script names. You have to add all of them if credential check 
// is global to your application. 
$controllers = array('auth', 'index', 'news', 'admin'); 

foreach ($roles as $role) { 
    $acl->addRole(new Zend_Acl_Role($role)); 
} 
foreach ($controllers as $controller) { 
    $acl->add(new Zend_Acl_Resource($controller)); 
} 

// Here comes credential definiton for admin user. 
$acl->allow('admin'); // Has access to everything. 

// Here comes credential definition for normal user. 
$acl->allow('normal'); // Has access to everything... 
$acl->deny('normal', 'admin'); // ... except the admin controller. 

// Finally I store whole ACL definition to registry for use 
// in AuthPlugin plugin. 
$registry = Zend_Registry::getInstance(); 
$registry->set('acl', $acl); 

問題#1 - 如果這種代碼是在引導,或在一個獨立的文件,如本?如果是這樣,它會更好,如果它在裏面說,圖書館目錄?

它的第二部分是一個擴展Zend控制器插件抽象類的新類,它允許它掛鉤到auth/login,邏輯基本上是在登錄失敗時重定向。否則它會從該類中獲取acl對象註冊表,抓取身份,並確定用戶是否被允許查看此資源。

$identity = $auth->getIdentity(); 

$frontController->registerPlugin(new AuthPlugin()); 

問題2 - 我究竟會如何編寫實際返回用戶身份的身份驗證插件的一部分嗎?我意識到他有一些代碼生成了一個Auth適配器db表對象,它將通過用戶ID和憑證(散列通過檢查)來查詢數據庫表的列。我很困惑這適合於getIdentity部分。

比方說,我的用戶表中包括這個數據:

user_id user_name level 
1   superadmin 3 
2   john   2 
3   example.com 1 

其中3級=管理員,2 =成員,1 =來賓。

問題#3 - 哪裏恰好是放置上述認證代碼的好地方?登錄控制器內部?

問題4 - 另一個海報replies與他就如何ACL邏輯應該模型內部完成的文章,但他採用的是其本身不支持,需要一個解決辦法,這是可行的具體方法?這究竟是如何理想地完成的?

回答

75

我的實現:

問題#1

class App_Model_Acl extends Zend_Acl 
{ 
    const ROLE_GUEST  = 'guest'; 
    const ROLE_USER   = 'user'; 
    const ROLE_PUBLISHER = 'publisher'; 
    const ROLE_EDITOR  = 'editor'; 
    const ROLE_ADMIN  = 'admin'; 
    const ROLE_GOD   = 'god'; 

    protected static $_instance; 

    /* Singleton pattern */ 
    protected function __construct() 
    { 
     $this->addRole(new Zend_Acl_Role(self::ROLE_GUEST)); 
     $this->addRole(new Zend_Acl_Role(self::ROLE_USER), self::ROLE_GUEST); 
     $this->addRole(new Zend_Acl_Role(self::ROLE_PUBLISHER), self::ROLE_USER); 
     $this->addRole(new Zend_Acl_Role(self::ROLE_EDITOR), self::ROLE_PUBLISHER); 
     $this->addRole(new Zend_Acl_Role(self::ROLE_ADMIN), self::ROLE_EDITOR); 

     //unique role for superadmin 
     $this->addRole(new Zend_Acl_Role(self::ROLE_GOD)); 

     $this->allow(self::ROLE_GOD); 

     /* Adding new resources */ 
     $this->add(new Zend_Acl_Resource('mvc:users')) 
      ->add(new Zend_Acl_Resource('mvc:users.auth'), 'mvc:users') 
      ->add(new Zend_Acl_Resource('mvc:users.list'), 'mvc:users'); 

     $this->allow(null, 'mvc:users', array('index', 'list')); 
     $this->allow('guest', 'mvc:users.auth', array('index', 'login')); 
     $this->allow('guest', 'mvc:users.list', array('index', 'list')); 
     $this->deny(array('user'), 'mvc:users.auth', array('login')); 


     /* Adding new resources */ 
     $moduleResource = new Zend_Acl_Resource('mvc:snippets'); 
     $this->add($moduleResource) 
      ->add(new Zend_Acl_Resource('mvc:snippets.crud'), $moduleResource) 
      ->add(new Zend_Acl_Resource('mvc:snippets.list'), $moduleResource); 

     $this->allow(null, $moduleResource, array('index', 'list')); 
     $this->allow('user', 'mvc:snippets.crud', array('create', 'update', 'delete', 'read', 'list')); 
     $this->allow('guest', 'mvc:snippets.list', array('index', 'list')); 

     return $this; 
    } 

    protected static $_user; 

    public static function setUser(Users_Model_User $user = null) 
    { 
     if (null === $user) { 
      throw new InvalidArgumentException('$user is null'); 
     } 

     self::$_user = $user; 
    } 

    /** 
    * 
    * @return App_Model_Acl 
    */ 
    public static function getInstance() 
    { 
     if (null === self::$_instance) { 
      self::$_instance = new self(); 
     } 
     return self::$_instance; 
    } 

    public static function resetInstance() 
    { 
     self::$_instance = null; 
     self::getInstance(); 
    } 
} 



class Smapp extends Bootstrap // class Bootstrap extends Zend_Application_Bootstrap_Bootstrap 
{ 
    /** 
    * @var App_Model_User 
    */ 
    protected static $_currentUser; 

    public function __construct($application) 
    { 
     parent::__construct($application); 
    } 

    public static function setCurrentUser(Users_Model_User $user) 
    { 
     self::$_currentUser = $user; 
    } 

    /** 
    * @return App_Model_User 
    */ 
    public static function getCurrentUser() 
    { 
     if (null === self::$_currentUser) { 
      self::setCurrentUser(Users_Service_User::getUserModel()); 
     } 
     return self::$_currentUser; 
    } 

    /** 
    * @return App_Model_User 
    */ 
    public static function getCurrentUserId() 
    { 
     $user = self::getCurrentUser(); 
     return $user->getId(); 
    } 

} 

class bootstrap

protected function _initUser() 
{ 
    $auth = Zend_Auth::getInstance(); 
    if ($auth->hasIdentity()) { 
     if ($user = Users_Service_User::findOneByOpenId($auth->getIdentity())) { 
      $userLastAccess = strtotime($user->last_access); 
      //update the date of the last login time in 5 minutes 
      if ((time() - $userLastAccess) > 60*5) { 
       $date = new Zend_Date(); 
       $user->last_access = $date->toString('YYYY-MM-dd HH:mm:ss'); 
       $user->save(); 
      } 
      Smapp::setCurrentUser($user); 
     } 
    } 
    return Smapp::getCurrentUser(); 
} 

protected function _initAcl() 
{ 
    $acl = App_Model_Acl::getInstance(); 
    Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($acl); 
    Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Smapp::getCurrentUser()->role); 
    Zend_Registry::set('Zend_Acl', $acl); 
    return $acl; 
} 

Front_Controller_Plugin

class App_Plugin_Auth extends Zend_Controller_Plugin_Abstract 
{ 
    private $_identity; 

    /** 
    * the acl object 
    * 
    * @var zend_acl 
    */ 
    private $_acl; 

    /** 
    * the page to direct to if there is a current 
    * user but they do not have permission to access 
    * the resource 
    * 
    * @var array 
    */ 
    private $_noacl = array('module' => 'admin', 
          'controller' => 'error', 
          'action' => 'no-auth'); 

    /** 
    * the page to direct to if there is not current user 
    * 
    * @var unknown_type 
    */ 
    private $_noauth = array('module' => 'users', 
          'controller' => 'auth', 
          'action' => 'login'); 


    /** 
    * validate the current user's request 
    * 
    * @param zend_controller_request $request 
    */ 
    public function preDispatch(Zend_Controller_Request_Abstract $request) 
    { 
     $this->_identity = Smapp::getCurrentUser(); 
     $this->_acl = App_Model_Acl::getInstance(); 

     if (!empty($this->_identity)) { 
      $role = $this->_identity->role; 
     } else { 
      $role = null; 
     } 

     $controller = $request->controller; 
     $module = $request->module; 
     $controller = $controller; 
     $action = $request->action; 

     //go from more specific to less specific 
     $moduleLevel = 'mvc:'.$module; 
     $controllerLevel = $moduleLevel . '.' . $controller; 
     $privelege = $action; 


     if ($this->_acl->has($controllerLevel)) { 
      $resource = $controllerLevel; 
     } else { 
      $resource = $moduleLevel; 
     } 

     if ($module != 'default' && $controller != 'index') { 
      if ($this->_acl->has($resource) && !$this->_acl->isAllowed($role, $resource, $privelege)) { 
       if (!$this->_identity) { 
        $request->setModuleName($this->_noauth['module']); 
        $request->setControllerName($this->_noauth['controller']); 
        $request->setActionName($this->_noauth['action']); 
        //$request->setParam('authPage', 'login'); 
       } else { 
        $request->setModuleName($this->_noacl['module']); 
        $request->setControllerName($this->_noacl['controller']); 
        $request->setActionName($this->_noacl['action']); 
        //$request->setParam('authPage', 'noauth'); 
       } 
       throw new Exception('Access denied. ' . $resource . '::' . $role); 
      } 
     } 
    } 
} 

和finnaly - Auth_Cont roller` :)

class Users_AuthController extends Smapp_Controller_Action 
{ 
    //sesssion 
    protected $_storage; 

    public function getStorage() 
    { 
     if (null === $this->_storage) { 
      $this->_storage = new Zend_Session_Namespace(__CLASS__); 
     } 
     return $this->_storage; 
    } 

    public function indexAction() 
    { 
     return $this->_forward('login'); 
    } 

    public function loginAction() 
    { 
     $openId = null; 
     if ($this->getRequest()->isPost() and $openId = ($this->_getParam('openid_identifier', false))) { 
      //do nothing 
     } elseif (!isset($_GET['openid_mode'])) { 
      return; 
     } 

     //$userService = $this->loadService('User'); 

     $userService = new Users_Service_User(); 

     $result = $userService->authenticate($openId, $this->getResponse()); 

     if ($result->isValid()) { 
      $identity = $result->getIdentity(); 
      if (!$identity['Profile']['display_name']) { 
       return $this->_helper->redirector->gotoSimpleAndExit('update', 'profile'); 
      } 
      $this->_redirect('/'); 
     } else { 
      $this->view->errorMessages = $result->getMessages(); 
     } 
    } 

    public function logoutAction() 
    { 
     $auth = Zend_Auth::getInstance(); 
     $auth->clearIdentity(); 
     //Zend_Session::destroy(); 
     $this->_redirect('/'); 
    } 
} 

問題2

保持它裏面Zend_Auth

成功驗證後,在存儲中寫入身份。 $auth->getStorage()->write($result->getIdentity());

identity - 簡直是user_id

DB設計

CREATE TABLE `user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `open_id` varchar(255) NOT NULL, 
    `role` varchar(20) NOT NULL, 
    `last_access` datetime NOT NULL, 
    `created_at` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `open_id` (`open_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

CREATE TABLE `user_profile` (
    `user_id` bigint(20) NOT NULL, 
    `display_name` varchar(100) DEFAULT NULL, 
    `email` varchar(100) DEFAULT NULL, 
    `real_name` varchar(100) DEFAULT NULL, 
    `website_url` varchar(255) DEFAULT NULL, 
    `location` varchar(100) DEFAULT NULL, 
    `birthday` date DEFAULT NULL, 
    `about_me` text, 
    `view_count` int(11) NOT NULL DEFAULT '0', 
    `updated_at` datetime NOT NULL, 
    PRIMARY KEY (`user_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

一些糖

/** 
* SM's code library 
* 
* @category  
* @package  
* @subpackage 
* @copyright Copyright (c) 2009 Pavel V Egorov 
* @author  Pavel V Egorov 
* @link  http://epavel.ru/ 
* @since  08.09.2009 
*/ 


class Smapp_View_Helper_IsAllowed extends Zend_View_Helper_Abstract 
{ 
    protected $_acl; 
    protected $_user; 

    public function isAllowed($resource = null, $privelege = null) 
    { 
     return (bool) $this->getAcl()->isAllowed($this->getUser(), $resource, $privelege); 
    } 

    /** 
    * @return App_Model_Acl 
    */ 
    public function getAcl() 
    { 
     if (null === $this->_acl) { 
      $this->setAcl(App_Model_Acl::getInstance()); 
     } 
     return $this->_acl; 
    } 

    /** 
    * @return App_View_Helper_IsAllowed 
    */ 
    public function setAcl(Zend_Acl $acl) 
    { 
     $this->_acl = $acl; 
     return $this; 
    } 

    /** 
    * @return Users_Model_User 
    */ 
    public function getUser() 
    { 
     if (null === $this->_user) { 
      $this->setUser(Smapp::getCurrentUser()); 
     } 
     return $this->_user; 
    } 

    /** 
    * @return App_View_Helper_IsAllowed 
    */ 
    public function setUser(Users_Model_User $user) 
    { 
     $this->_user = $user; 
     return $this; 
    } 

} 

這樣的事情在任何視圖腳本

<?php if ($this->isAllowed('mvc:snippets.crud', 'update')) : ?> 
    <a title="Edit &laquo;<?=$this->escape($snippetInfo['title'])?>&raquo; snippet">Edit</a> 
<?php endif?> 

有問題? :)

+0

太棒了,我很欣賞你花時間來粘貼所有這些。我會嘗試實施它並讓你知道,再次感謝。 – 2010-01-12 15:08:34

+0

寫郵件。 (谷歌,只):) – SMka 2010-01-12 21:54:57

+0

好吧,這是比我想的更好。爲什麼App_Plugin_Auth中的acl對象沒有從註冊表中取回?感謝 – 2011-01-17 18:40:34