From 9158e110dad9a888a9988cfaeca75eb241424ef9 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 9 May 2013 17:21:18 +0400 Subject: [PATCH 01/14] Auth manager init version --- framework/base/Application.php | 6 +- framework/web/AuthAssignment.php | 97 +++++++++++++++ framework/web/AuthItem.php | 261 +++++++++++++++++++++++++++++++++++++++ framework/web/AuthManager.php | 143 +++++++++++++++++++++ framework/web/IAuthManager.php | 173 ++++++++++++++++++++++++++ framework/web/User.php | 40 ++++-- 6 files changed, 705 insertions(+), 15 deletions(-) create mode 100644 framework/web/AuthAssignment.php create mode 100644 framework/web/AuthItem.php create mode 100644 framework/web/AuthManager.php create mode 100644 framework/web/IAuthManager.php diff --git a/framework/base/Application.php b/framework/base/Application.php index ac7cc6a..6bd31f1 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -305,12 +305,12 @@ class Application extends Module } /** - * @return null|Component - * @todo + * Returns the auth manager for this application. + * @return \yii\web\AuthManager the auth manager for this application. */ public function getAuthManager() { - return $this->getComponent('auth'); + return $this->getComponent('authManager'); } /** diff --git a/framework/web/AuthAssignment.php b/framework/web/AuthAssignment.php new file mode 100644 index 0000000..c08e12d --- /dev/null +++ b/framework/web/AuthAssignment.php @@ -0,0 +1,97 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class AuthAssignment extends Object +{ + private $_auth; + private $_itemName; + private $_userId; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param IAuthManager $auth the authorization manager + * @param string $itemName authorization item name + * @param mixed $userId user ID (see [[User::id]]) + * @param string $bizRule the business rule associated with this assignment + * @param mixed $data additional data for this assignment + */ + public function __construct($auth, $itemName, $userId, $bizRule = null, $data = null) + { + $this->_auth = $auth; + $this->_itemName = $itemName; + $this->_userId = $userId; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * @return mixed user ID (see [[User::id]]) + */ + public function getUserId() + { + return $this->_userId; + } + + /** + * @return string the authorization item name + */ + public function getItemName() + { + return $this->_itemName; + } + + /** + * @return string the business rule associated with this assignment + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this assignment + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveAuthAssignment($this); + } + } + + /** + * @return mixed additional data for this assignment + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value additional data for this assignment + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveAuthAssignment($this); + } + } +} diff --git a/framework/web/AuthItem.php b/framework/web/AuthItem.php new file mode 100644 index 0000000..0d3553d --- /dev/null +++ b/framework/web/AuthItem.php @@ -0,0 +1,261 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class AuthItem extends Object +{ + const TYPE_OPERATION = 0; + const TYPE_TASK = 1; + const TYPE_ROLE = 2; + + private $_auth; + private $_type; + private $_name; + private $_description; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param IAuthManager $auth authorization manager + * @param string $name authorization item name + * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). + * @param string $description the description + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data for this item + */ + public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) + { + $this->_type = (int)$type; + $this->_auth = $auth; + $this->_name = $name; + $this->_description = $description; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * Checks to see if the specified item is within the hierarchy starting from this item. + * This method is expected to be internally used by the actual implementations + * of the [[IAuthManager::checkAccess()]]. + * @param string $itemName the name of the item to be checked + * @param array $params the parameters to be passed to business rule evaluation + * @return boolean whether the specified item is within the hierarchy starting from this item. + */ + public function checkAccess($itemName, $params = array()) + { + Yii::trace('Checking permission: ' . $this->_name, __METHOD__); + if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { + if ($this->_name == $itemName) { + return true; + } + foreach ($this->_auth->getItemChildren($this->_name) as $item) { + if ($item->checkAccess($itemName, $params)) { + return true; + } + } + } + return false; + } + + /** + * @return IAuthManager the authorization manager + */ + public function getAuthManager() + { + return $this->_auth; + } + + /** + * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). + */ + public function getType() + { + return $this->_type; + } + + /** + * @return string the item name + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string $value the item name + */ + public function setName($value) + { + if ($this->_name !== $value) { + $oldName = $this->_name; + $this->_name = $value; + $this->_auth->saveAuthItem($this, $oldName); + } + } + + /** + * @return string the item description + */ + public function getDescription() + { + return $this->_description; + } + + /** + * @param string $value the item description + */ + public function setDescription($value) + { + if ($this->_description !== $value) { + $this->_description = $value; + $this->_auth->saveAuthItem($this); + } + } + + /** + * @return string the business rule associated with this item + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this item + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveAuthItem($this); + } + } + + /** + * @return mixed the additional data associated with this item + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value the additional data associated with this item + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveAuthItem($this); + } + } + + /** + * Adds a child item. + * @param string $name the name of the child item + * @return boolean whether the item is added successfully + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + * @see IAuthManager::addItemChild + */ + public function addChild($name) + { + return $this->_auth->addItemChild($this->_name, $name); + } + + /** + * Removes a child item. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $name the child item name + * @return boolean whether the removal is successful + * @see IAuthManager::removeItemChild + */ + public function removeChild($name) + { + return $this->_auth->removeItemChild($this->_name, $name); + } + + /** + * Returns a value indicating whether a child exists + * @param string $name the child item name + * @return boolean whether the child exists + * @see IAuthManager::hasItemChild + */ + public function hasChild($name) + { + return $this->_auth->hasItemChild($this->_name, $name); + } + + /** + * Returns the children of this item. + * @return array all child items of this item. + * @see IAuthManager::getItemChildren + */ + public function getChildren() + { + return $this->_auth->getItemChildren($this->_name); + } + + /** + * Assigns this item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return AuthAssignment the authorization assignment information. + * @throws \yii\base\Exception if the item has already been assigned to the user + * @see IAuthManager::assign + */ + public function assign($userId, $bizRule = null, $data = null) + { + return $this->_auth->assign($this->_name, $userId, $bizRule, $data); + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether removal is successful + * @see IAuthManager::revoke + */ + public function revoke($userId) + { + return $this->_auth->revoke($this->_name, $userId); + } + + /** + * Returns a value indicating whether this item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether the item has been assigned to the user. + * @see IAuthManager::isAssigned + */ + public function isAssigned($userId) + { + return $this->_auth->isAssigned($this->_name, $userId); + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @return AuthAssignment the item assignment information. Null is returned if + * this item is not assigned to the user. + * @see IAuthManager::getAuthAssignment + */ + public function getAssignment($userId) + { + return $this->_auth->getAuthAssignment($this->_name, $userId); + } +} diff --git a/framework/web/AuthManager.php b/framework/web/AuthManager.php new file mode 100644 index 0000000..4888689 --- /dev/null +++ b/framework/web/AuthManager.php @@ -0,0 +1,143 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +abstract class AuthManager extends Component implements IAuthManager +{ + /** + * @var boolean Enable error reporting for bizRules. + */ + public $showErrors = false; + + /** + * @var array list of role names that are assigned to all users implicitly. + * These roles do not need to be explicitly assigned to any user. + * When calling [[checkAccess()]], these roles will be checked first. + * For performance reason, you should minimize the number of such roles. + * A typical usage of such roles is to define an 'authenticated' role and associate + * it with a biz rule which checks if the current user is authenticated. + * And then declare 'authenticated' in this property so that it can be applied to + * every authenticated user. + */ + public $defaultRoles = array(); + + /** + * Creates a role. + * This is a shortcut method to [[IAuthManager::createAuthItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return AuthItem the authorization item + */ + public function createRole($name, $description = '', $bizRule = null, $data = null) + { + return $this->createAuthItem($name, AuthItem::TYPE_ROLE, $description, $bizRule, $data); + } + + /** + * Creates a task. + * This is a shortcut method to [[IAuthManager::createAuthItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return AuthItem the authorization item + */ + public function createTask($name, $description = '', $bizRule = null, $data = null) + { + return $this->createAuthItem($name, AuthItem::TYPE_TASK, $description, $bizRule, $data); + } + + /** + * Creates an operation. + * This is a shortcut method to [[IAuthManager::createAuthItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return AuthItem the authorization item + */ + public function createOperation($name, $description = '', $bizRule = null, $data = null) + { + return $this->createAuthItem($name, AuthItem::TYPE_OPERATION, $description, $bizRule, $data); + } + + /** + * Returns roles. + * This is a shortcut method to [[IAuthManager::getAuthItems()]]. + * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user + * will be returned. Otherwise, all roles will be returned. + * @return array roles (name=>AuthItem) + */ + public function getRoles($userId = null) + { + return $this->getAuthItems(AuthItem::TYPE_ROLE, $userId); + } + + /** + * Returns tasks. + * This is a shortcut method to [[IAuthManager::getAuthItems()]]. + * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user + * will be returned. Otherwise, all tasks will be returned. + * @return array tasks (name=>AuthItem) + */ + public function getTasks($userId = null) + { + return $this->getAuthItems(AuthItem::TYPE_TASK, $userId); + } + + /** + * Returns operations. + * This is a shortcut method to [[IAuthManager::getAuthItems()]]. + * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user + * will be returned. Otherwise, all operations will be returned. + * @return array operations (name=>AuthItem) + */ + public function getOperations($userId = null) + { + return $this->getAuthItems(AuthItem::TYPE_OPERATION, $userId); + } + + /** + * Executes the specified business rule. + * @param string $bizRule the business rule to be executed. + * @param array $params parameters passed to [[IAuthManager::checkAccess()]]. + * @param mixed $data additional data associated with the authorization item or assignment. + * @return boolean whether the business rule returns true. + * If the business rule is empty, it will still return true. + */ + public function executeBizRule($bizRule, $params, $data) + { + return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); + } + + /** + * Checks the item types to make sure a child can be added to a parent. + * @param integer $parentType parent item type + * @param integer $childType child item type + * @throws Exception if the item cannot be added as a child due to its incompatible type. + */ + protected function checkItemChildType($parentType, $childType) + { + static $types = array('operation', 'task', 'role'); + if ($parentType < $childType) { + throw new Exception("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); + } + } +} diff --git a/framework/web/IAuthManager.php b/framework/web/IAuthManager.php new file mode 100644 index 0000000..1d2fbb9 --- /dev/null +++ b/framework/web/IAuthManager.php @@ -0,0 +1,173 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +interface IAuthManager +{ + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that we are checking access to + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()); + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @throws \yii\base\Exception if an item with the same name already exists + * @return AuthItem the authorization item + */ + public function createAuthItem($name, $type, $description = '', $bizRule = null, $data = null); + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeAuthItem($name); + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return array the authorization items of the specific type. + */ + public function getAuthItems($userId = null, $type = null); + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return AuthItem the authorization item. Null if the item cannot be found. + */ + public function getAuthItem($name); + /** + * Saves an authorization item to persistent storage. + * @param AuthItem $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + public function saveAuthItem($item, $oldName = null); + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + */ + public function addItemChild($itemName, $childName); + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName); + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName); + /** + * Returns the children of the specified item. + * @param mixed $itemName the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return array all child items of the parent + */ + public function getItemChildren($itemName); + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return AuthAssignment the authorization assignment information. + * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null); + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName); + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($userId, $itemName); + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return AuthAssignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAuthAssignment($userId, $itemName); + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return array the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAuthAssignments($userId); + /** + * Saves the changes to an authorization assignment. + * @param AuthAssignment $assignment the assignment that has been changed. + */ + public function saveAuthAssignment($assignment); + /** + * Removes all authorization data. + */ + public function clearAll(); + /** + * Removes all authorization assignments. + */ + public function clearAuthAssignments(); + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + public function save(); + /** + * Executes a business rule. + * A business rule is a piece of PHP code that will be executed when [[checkAccess()]] is called. + * @param string $bizRule the business rule to be executed. + * @param array $params additional parameters to be passed to the business rule when being executed. + * @param mixed $data additional data that is associated with the corresponding authorization item or assignment + * @return boolean whether the execution returns a true value. + * If the business rule is empty, it will also return true. + */ + public function executeBizRule($bizRule, $params, $data); +} diff --git a/framework/web/User.php b/framework/web/User.php index 88fc12d..7d8e300 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -40,11 +40,11 @@ class User extends Component */ public $enableAutoLogin = false; /** - * @var string|array the URL for login when [[loginRequired()]] is called. + * @var string|array the URL for login when [[loginRequired()]] is called. * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. - * The first element of the array should be the route to the login action, and the rest of + * The first element of the array should be the route to the login action, and the rest of * the name-value pairs are GET parameters used to construct the login URL. For example, - * + * * ~~~ * array('site/login', 'ref' => 1) * ~~~ @@ -86,6 +86,8 @@ class User extends Component */ public $returnUrlVar = '__returnUrl'; + private $_access = array(); + /** * Initializes the application component. @@ -449,19 +451,33 @@ class User extends Component } /** - * Checks whether the user has access to the specified operation. - * @param $operator - * @param array $params - * @return bool - * @todo + * Performs access check for this user. + * @param string $operation the name of the operation that need access check. + * @param array $params name-value pairs that would be passed to business rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to + * this array, which holds the value of [[id]] when [[DbAuthManager]] or + * [[PhpAuthManager]] is used. + * @param boolean $allowCaching whether to allow caching the result of access check. + * When this parameter is true (default), if the access check of an operation was performed + * before, its result will be directly returned when calling this method to check the same + * operation. If this parameter is false, this method will always call + * [[AuthManager::checkAccess()]] to obtain the up-to-date access result. Note that this + * caching is effective only within the same request and only works when `$params = array()`. + * @return boolean whether the operations can be performed by this user. */ - public function checkAccess($operation, $params = array()) + public function checkAccess($operation, $params = array(), $allowCaching = true) { $auth = Yii::$app->getAuthManager(); - if ($auth !== null) { - return $auth->checkAccess($this->getId(), $operation, $params); - } else { + if ($auth === null) { return false; } + if ($allowCaching && empty($params) && isset($this->_access[$operation])) { + return $this->_access[$operation]; + } + $access = $auth->checkAccess($this->getId(), $operation, $params); + if ($allowCaching && empty($params)) { + $this->_access[$operation] = $access; + } + return $access; } } From cd3387067155f34df056629c233fa895dfb1ba83 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 9 May 2013 18:53:17 +0400 Subject: [PATCH 02/14] Moved to framework/rbac + method names refactoring --- framework/rbac/Assignment.php | 97 ++++++++ framework/rbac/Item.php | 261 +++++++++++++++++++++ framework/rbac/Manager.php | 143 ++++++++++++ framework/rbac/PhpManager.php | 492 +++++++++++++++++++++++++++++++++++++++ framework/web/AuthAssignment.php | 97 -------- framework/web/AuthItem.php | 261 --------------------- framework/web/AuthManager.php | 143 ------------ framework/web/IAuthManager.php | 173 -------------- 8 files changed, 993 insertions(+), 674 deletions(-) create mode 100644 framework/rbac/Assignment.php create mode 100644 framework/rbac/Item.php create mode 100644 framework/rbac/Manager.php create mode 100644 framework/rbac/PhpManager.php delete mode 100644 framework/web/AuthAssignment.php delete mode 100644 framework/web/AuthItem.php delete mode 100644 framework/web/AuthManager.php delete mode 100644 framework/web/IAuthManager.php diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php new file mode 100644 index 0000000..c2a9f56 --- /dev/null +++ b/framework/rbac/Assignment.php @@ -0,0 +1,97 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class Assignment extends Object +{ + private $_auth; + private $_itemName; + private $_userId; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param IManager $auth the authorization manager + * @param string $itemName authorization item name + * @param mixed $userId user ID (see [[User::id]]) + * @param string $bizRule the business rule associated with this assignment + * @param mixed $data additional data for this assignment + */ + public function __construct($auth, $itemName, $userId, $bizRule = null, $data = null) + { + $this->_auth = $auth; + $this->_itemName = $itemName; + $this->_userId = $userId; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * @return mixed user ID (see [[User::id]]) + */ + public function getUserId() + { + return $this->_userId; + } + + /** + * @return string the authorization item name + */ + public function getItemName() + { + return $this->_itemName; + } + + /** + * @return string the business rule associated with this assignment + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this assignment + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveAssignment($this); + } + } + + /** + * @return mixed additional data for this assignment + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value additional data for this assignment + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveAssignment($this); + } + } +} diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php new file mode 100644 index 0000000..ba78e60 --- /dev/null +++ b/framework/rbac/Item.php @@ -0,0 +1,261 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class Item extends Object +{ + const TYPE_OPERATION = 0; + const TYPE_TASK = 1; + const TYPE_ROLE = 2; + + private $_auth; + private $_type; + private $_name; + private $_description; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param IManager $auth authorization manager + * @param string $name authorization item name + * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). + * @param string $description the description + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data for this item + */ + public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) + { + $this->_type = (int)$type; + $this->_auth = $auth; + $this->_name = $name; + $this->_description = $description; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * Checks to see if the specified item is within the hierarchy starting from this item. + * This method is expected to be internally used by the actual implementations + * of the [[IManager::checkAccess()]]. + * @param string $itemName the name of the item to be checked + * @param array $params the parameters to be passed to business rule evaluation + * @return boolean whether the specified item is within the hierarchy starting from this item. + */ + public function checkAccess($itemName, $params = array()) + { + Yii::trace('Checking permission: ' . $this->_name, __METHOD__); + if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { + if ($this->_name == $itemName) { + return true; + } + foreach ($this->_auth->getItemChildren($this->_name) as $item) { + if ($item->checkAccess($itemName, $params)) { + return true; + } + } + } + return false; + } + + /** + * @return IManager the authorization manager + */ + public function getManager() + { + return $this->_auth; + } + + /** + * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). + */ + public function getType() + { + return $this->_type; + } + + /** + * @return string the item name + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string $value the item name + */ + public function setName($value) + { + if ($this->_name !== $value) { + $oldName = $this->_name; + $this->_name = $value; + $this->_auth->saveItem($this, $oldName); + } + } + + /** + * @return string the item description + */ + public function getDescription() + { + return $this->_description; + } + + /** + * @param string $value the item description + */ + public function setDescription($value) + { + if ($this->_description !== $value) { + $this->_description = $value; + $this->_auth->saveItem($this); + } + } + + /** + * @return string the business rule associated with this item + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this item + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveItem($this); + } + } + + /** + * @return mixed the additional data associated with this item + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value the additional data associated with this item + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveItem($this); + } + } + + /** + * Adds a child item. + * @param string $name the name of the child item + * @return boolean whether the item is added successfully + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + * @see IManager::addItemChild + */ + public function addChild($name) + { + return $this->_auth->addItemChild($this->_name, $name); + } + + /** + * Removes a child item. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $name the child item name + * @return boolean whether the removal is successful + * @see IManager::removeItemChild + */ + public function removeChild($name) + { + return $this->_auth->removeItemChild($this->_name, $name); + } + + /** + * Returns a value indicating whether a child exists + * @param string $name the child item name + * @return boolean whether the child exists + * @see IManager::hasItemChild + */ + public function hasChild($name) + { + return $this->_auth->hasItemChild($this->_name, $name); + } + + /** + * Returns the children of this item. + * @return array all child items of this item. + * @see IManager::getItemChildren + */ + public function getChildren() + { + return $this->_auth->getItemChildren($this->_name); + } + + /** + * Assigns this item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws \yii\base\Exception if the item has already been assigned to the user + * @see IManager::assign + */ + public function assign($userId, $bizRule = null, $data = null) + { + return $this->_auth->assign($userId, $this->_name, $bizRule, $data); + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether removal is successful + * @see IManager::revoke + */ + public function revoke($userId) + { + return $this->_auth->revoke($userId, $this->_name); + } + + /** + * Returns a value indicating whether this item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether the item has been assigned to the user. + * @see IManager::isAssigned + */ + public function isAssigned($userId) + { + return $this->_auth->isAssigned($userId, $this->_name); + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Assignment the item assignment information. Null is returned if + * this item is not assigned to the user. + * @see IManager::getAssignment + */ + public function getAssignment($userId) + { + return $this->_auth->getAssignment($userId, $this->_name); + } +} diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php new file mode 100644 index 0000000..2f77e10 --- /dev/null +++ b/framework/rbac/Manager.php @@ -0,0 +1,143 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +abstract class Manager extends Component implements IManager +{ + /** + * @var boolean Enable error reporting for bizRules. + */ + public $showErrors = false; + + /** + * @var array list of role names that are assigned to all users implicitly. + * These roles do not need to be explicitly assigned to any user. + * When calling [[checkAccess()]], these roles will be checked first. + * For performance reason, you should minimize the number of such roles. + * A typical usage of such roles is to define an 'authenticated' role and associate + * it with a biz rule which checks if the current user is authenticated. + * And then declare 'authenticated' in this property so that it can be applied to + * every authenticated user. + */ + public $defaultRoles = array(); + + /** + * Creates a role. + * This is a shortcut method to [[IManager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createRole($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_ROLE, $description, $bizRule, $data); + } + + /** + * Creates a task. + * This is a shortcut method to [[IManager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createTask($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_TASK, $description, $bizRule, $data); + } + + /** + * Creates an operation. + * This is a shortcut method to [[IManager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createOperation($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_OPERATION, $description, $bizRule, $data); + } + + /** + * Returns roles. + * This is a shortcut method to [[IManager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user + * will be returned. Otherwise, all roles will be returned. + * @return array roles (name=>AuthItem) + */ + public function getRoles($userId = null) + { + return $this->getItems($userId, Item::TYPE_ROLE); + } + + /** + * Returns tasks. + * This is a shortcut method to [[IManager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user + * will be returned. Otherwise, all tasks will be returned. + * @return array tasks (name=>AuthItem) + */ + public function getTasks($userId = null) + { + return $this->getItems($userId, Item::TYPE_TASK); + } + + /** + * Returns operations. + * This is a shortcut method to [[IManager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user + * will be returned. Otherwise, all operations will be returned. + * @return array operations (name=>AuthItem) + */ + public function getOperations($userId = null) + { + return $this->getItems($userId, Item::TYPE_OPERATION); + } + + /** + * Executes the specified business rule. + * @param string $bizRule the business rule to be executed. + * @param array $params parameters passed to [[IManager::checkAccess()]]. + * @param mixed $data additional data associated with the authorization item or assignment. + * @return boolean whether the business rule returns true. + * If the business rule is empty, it will still return true. + */ + public function executeBizRule($bizRule, $params, $data) + { + return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); + } + + /** + * Checks the item types to make sure a child can be added to a parent. + * @param integer $parentType parent item type + * @param integer $childType child item type + * @throws Exception if the item cannot be added as a child due to its incompatible type. + */ + protected function checkItemChildType($parentType, $childType) + { + static $types = array('operation', 'task', 'role'); + if ($parentType < $childType) { + throw new Exception("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); + } + } +} diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php new file mode 100644 index 0000000..08f5c83 --- /dev/null +++ b/framework/rbac/PhpManager.php @@ -0,0 +1,492 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class PhpManager extends Manager +{ + /** + * @var string the path of the PHP script that contains the authorization data. + * If not set, it will be using 'protected/data/auth.php' as the data file. + * Make sure this file is writable by the Web server process if the authorization + * needs to be changed. + * @see loadFromFile + * @see saveToFile + */ + public $authFile; + + private $_items = array(); // itemName => item + private $_children = array(); // itemName, childName => child + private $_assignments = array(); // userId, itemName => assignment + + /** + * Initializes the application component. + * This method overrides parent implementation by loading the authorization data + * from PHP script. + */ + public function init() + { + parent::init(); + if ($this->authFile === null) { + $this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; + } + $this->load(); + } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This can be either an integer or a string representing + * @param string $itemName the name of the operation that need access check + * the unique identifier of a user. See [[User::id]]. + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. + * Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of $userId. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()) + { + if (!isset($this->_items[$itemName])) { + return false; + } + $item = $this->_items[$itemName]; + Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); + if (!isset($params['userId'])) { + $params['userId'] = $userId; + } + if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if (in_array($itemName, $this->defaultRoles)) { + return true; + } + if (isset($this->_assignments[$userId][$itemName])) { + $assignment = $this->_assignments[$userId][$itemName]; + if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + return true; + } + } + foreach ($this->_children as $parentName => $children) { + if (isset($children[$itemName]) && $this->checkAccess($userId, $parentName, $params)) { + return true; + } + } + } + return false; + } + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the item is added successfully + * @throws Exception if either parent or child doesn't exist or if a loop has been detected. + */ + public function addItemChild($itemName, $childName) + { + if (!isset($this->_items[$childName], $this->_items[$itemName])) { + throw new Exception("Either '$itemName' or '$childName' does not exist."); + } + $child = $this->_items[$childName]; + $item = $this->_items[$itemName]; + $this->checkItemChildType($item->getType(), $child->getType()); + if ($this->detectLoop($itemName, $childName)) { + throw new Exception("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + } + if (isset($this->_children[$itemName][$childName])) { + throw new Exception("The item '$itemName' already has a child '$childName'."); + } + $this->_children[$itemName][$childName] = $this->_items[$childName]; + return true; + } + + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName) + { + if (isset($this->_children[$itemName][$childName])) { + unset($this->_children[$itemName][$childName]); + return true; + } else { + return false; + } + } + + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName) + { + return isset($this->_children[$itemName][$childName]); + } + + /** + * Returns the children of the specified item. + * @param mixed $names the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return array all child items of the parent + */ + public function getItemChildren($names) + { + if (is_string($names)) { + return isset($this->_children[$names]) ? $this->_children[$names] : array(); + } + + $children = array(); + foreach ($names as $name) { + if (isset($this->_children[$name])) { + $children = array_merge($children, $this->_children[$name]); + } + } + return $children; + } + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws Exception if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null) + { + if (!isset($this->_items[$itemName])) { + throw new Exception("Unknown authorization item '$itemName'."); + } elseif (isset($this->_assignments[$userId][$itemName])) { + throw new Exception("Authorization item '$itemName' has already been assigned to user '$userId'."); + } else { + return $this->_assignments[$userId][$itemName] = new Assignment($this, $itemName, $userId, $bizRule, $data); + } + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName) + { + if (isset($this->_assignments[$userId][$itemName])) { + unset($this->_assignments[$userId][$itemName]); + return true; + } else { + return false; + } + } + + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($userId, $itemName) + { + return isset($this->_assignments[$userId][$itemName]); + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAssignment($userId, $itemName) + { + return isset($this->_assignments[$userId][$itemName]) ? $this->_assignments[$userId][$itemName] : null; + } + + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return array the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAssignments($userId) + { + return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : array(); + } + + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return array the authorization items of the specific type. + */ + public function getItems($userId = null, $type = null) + { + if ($type === null && $userId === null) { + return $this->_items; + } + $items = array(); + if ($userId === null) { + foreach ($this->_items as $name => $item) { + if ($item->getType() == $type) { + $items[$name] = $item; + } + } + } elseif (isset($this->_assignments[$userId])) { + foreach ($this->_assignments[$userId] as $assignment) { + $name = $assignment->getItemName(); + if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->getType() == $type)) { + $items[$name] = $this->_items[$name]; + } + } + } + return $items; + } + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @return Item the authorization item + * @throws Exception if an item with the same name already exists + */ + public function createItem($name, $type, $description = '', $bizRule = null, $data = null) + { + if (isset($this->_items[$name])) { + throw new Exception('Unable to add an item whose name is the same as an existing item.'); + } + return $this->_items[$name] = new Item($this, $name, $type, $description, $bizRule, $data); + } + + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeItem($name) + { + if (isset($this->_items[$name])) { + foreach ($this->_children as &$children) { + unset($children[$name]); + } + foreach ($this->_assignments as &$assignments) { + unset($assignments[$name]); + } + unset($this->_items[$name]); + return true; + } else { + return false; + } + } + + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + public function getItem($name) + { + return isset($this->_items[$name]) ? $this->_items[$name] : null; + } + + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + * @throws Exception if an item with the same name already taken + */ + public function saveItem($item, $oldName = null) + { + if ($oldName !== null && ($newName = $item->getName()) !== $oldName) // name changed + { + if (isset($this->_items[$newName])) { + throw new Exception("Unable to change the item name. The name '$newName' is already used by another item."); + } + if (isset($this->_items[$oldName]) && $this->_items[$oldName] === $item) { + unset($this->_items[$oldName]); + $this->_items[$newName] = $item; + if (isset($this->_children[$oldName])) { + $this->_children[$newName] = $this->_children[$oldName]; + unset($this->_children[$oldName]); + } + foreach ($this->_children as &$children) { + if (isset($children[$oldName])) { + $children[$newName] = $children[$oldName]; + unset($children[$oldName]); + } + } + foreach ($this->_assignments as &$assignments) { + if (isset($assignments[$oldName])) { + $assignments[$newName] = $assignments[$oldName]; + unset($assignments[$oldName]); + } + } + } + } + } + + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + public function saveAssignment($assignment) + { + } + + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + public function save() + { + $items = array(); + foreach ($this->_items as $name => $item) { + $items[$name] = array( + 'type' => $item->getType(), + 'description' => $item->getDescription(), + 'bizRule' => $item->getBizRule(), + 'data' => $item->getData(), + ); + if (isset($this->_children[$name])) { + foreach ($this->_children[$name] as $child) { + $items[$name]['children'][] = $child->getName(); + } + } + } + + foreach ($this->_assignments as $userId => $assignments) { + foreach ($assignments as $name => $assignment) { + if (isset($items[$name])) { + $items[$name]['assignments'][$userId] = array( + 'bizRule' => $assignment->getBizRule(), + 'data' => $assignment->getData(), + ); + } + } + } + + $this->saveToFile($items, $this->authFile); + } + + /** + * Loads authorization data. + */ + public function load() + { + $this->clearAll(); + + $items = $this->loadFromFile($this->authFile); + + foreach ($items as $name => $item) { + $this->_items[$name] = new Item($this, $name, $item['type'], $item['description'], $item['bizRule'], $item['data']); + } + + foreach ($items as $name => $item) { + if (isset($item['children'])) { + foreach ($item['children'] as $childName) { + if (isset($this->_items[$childName])) { + $this->_children[$name][$childName] = $this->_items[$childName]; + } + } + } + if (isset($item['assignments'])) { + foreach ($item['assignments'] as $userId => $assignment) { + $this->_assignments[$userId][$name] = new Assignment($this, $name, $userId, $assignment['bizRule'], $assignment['data']); + } + } + } + } + + /** + * Removes all authorization data. + */ + public function clearAll() + { + $this->clearAssignments(); + $this->_children = array(); + $this->_items = array(); + } + + /** + * Removes all authorization assignments. + */ + public function clearAssignments() + { + $this->_assignments = array(); + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * @param string $itemName parent item name + * @param string $childName the name of the child item that is to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($itemName, $childName) + { + if ($childName === $itemName) { + return true; + } + if (!isset($this->_children[$childName], $this->_items[$itemName])) { + return false; + } + foreach ($this->_children[$childName] as $child) { + if ($this->detectLoop($itemName, $child->getName())) { + return true; + } + } + return false; + } + + /** + * Loads the authorization data from a PHP script file. + * @param string $file the file path. + * @return array the authorization data + * @see saveToFile + */ + protected function loadFromFile($file) + { + if (is_file($file)) { + return require($file); + } else { + return array(); + } + } + + /** + * Saves the authorization data to a PHP script file. + * @param array $data the authorization data + * @param string $file the file path. + * @see loadFromFile + */ + protected function saveToFile($data, $file) + { + file_put_contents($file, " - * @author Alexander Kochetov - * @since 2.0 - */ -class AuthAssignment extends Object -{ - private $_auth; - private $_itemName; - private $_userId; - private $_bizRule; - private $_data; - - /** - * Constructor. - * @param IAuthManager $auth the authorization manager - * @param string $itemName authorization item name - * @param mixed $userId user ID (see [[User::id]]) - * @param string $bizRule the business rule associated with this assignment - * @param mixed $data additional data for this assignment - */ - public function __construct($auth, $itemName, $userId, $bizRule = null, $data = null) - { - $this->_auth = $auth; - $this->_itemName = $itemName; - $this->_userId = $userId; - $this->_bizRule = $bizRule; - $this->_data = $data; - } - - /** - * @return mixed user ID (see [[User::id]]) - */ - public function getUserId() - { - return $this->_userId; - } - - /** - * @return string the authorization item name - */ - public function getItemName() - { - return $this->_itemName; - } - - /** - * @return string the business rule associated with this assignment - */ - public function getBizRule() - { - return $this->_bizRule; - } - - /** - * @param string $value the business rule associated with this assignment - */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - $this->_auth->saveAuthAssignment($this); - } - } - - /** - * @return mixed additional data for this assignment - */ - public function getData() - { - return $this->_data; - } - - /** - * @param mixed $value additional data for this assignment - */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - $this->_auth->saveAuthAssignment($this); - } - } -} diff --git a/framework/web/AuthItem.php b/framework/web/AuthItem.php deleted file mode 100644 index 0d3553d..0000000 --- a/framework/web/AuthItem.php +++ /dev/null @@ -1,261 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -class AuthItem extends Object -{ - const TYPE_OPERATION = 0; - const TYPE_TASK = 1; - const TYPE_ROLE = 2; - - private $_auth; - private $_type; - private $_name; - private $_description; - private $_bizRule; - private $_data; - - /** - * Constructor. - * @param IAuthManager $auth authorization manager - * @param string $name authorization item name - * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). - * @param string $description the description - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data for this item - */ - public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) - { - $this->_type = (int)$type; - $this->_auth = $auth; - $this->_name = $name; - $this->_description = $description; - $this->_bizRule = $bizRule; - $this->_data = $data; - } - - /** - * Checks to see if the specified item is within the hierarchy starting from this item. - * This method is expected to be internally used by the actual implementations - * of the [[IAuthManager::checkAccess()]]. - * @param string $itemName the name of the item to be checked - * @param array $params the parameters to be passed to business rule evaluation - * @return boolean whether the specified item is within the hierarchy starting from this item. - */ - public function checkAccess($itemName, $params = array()) - { - Yii::trace('Checking permission: ' . $this->_name, __METHOD__); - if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { - if ($this->_name == $itemName) { - return true; - } - foreach ($this->_auth->getItemChildren($this->_name) as $item) { - if ($item->checkAccess($itemName, $params)) { - return true; - } - } - } - return false; - } - - /** - * @return IAuthManager the authorization manager - */ - public function getAuthManager() - { - return $this->_auth; - } - - /** - * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). - */ - public function getType() - { - return $this->_type; - } - - /** - * @return string the item name - */ - public function getName() - { - return $this->_name; - } - - /** - * @param string $value the item name - */ - public function setName($value) - { - if ($this->_name !== $value) { - $oldName = $this->_name; - $this->_name = $value; - $this->_auth->saveAuthItem($this, $oldName); - } - } - - /** - * @return string the item description - */ - public function getDescription() - { - return $this->_description; - } - - /** - * @param string $value the item description - */ - public function setDescription($value) - { - if ($this->_description !== $value) { - $this->_description = $value; - $this->_auth->saveAuthItem($this); - } - } - - /** - * @return string the business rule associated with this item - */ - public function getBizRule() - { - return $this->_bizRule; - } - - /** - * @param string $value the business rule associated with this item - */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - $this->_auth->saveAuthItem($this); - } - } - - /** - * @return mixed the additional data associated with this item - */ - public function getData() - { - return $this->_data; - } - - /** - * @param mixed $value the additional data associated with this item - */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - $this->_auth->saveAuthItem($this); - } - } - - /** - * Adds a child item. - * @param string $name the name of the child item - * @return boolean whether the item is added successfully - * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - * @see IAuthManager::addItemChild - */ - public function addChild($name) - { - return $this->_auth->addItemChild($this->_name, $name); - } - - /** - * Removes a child item. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $name the child item name - * @return boolean whether the removal is successful - * @see IAuthManager::removeItemChild - */ - public function removeChild($name) - { - return $this->_auth->removeItemChild($this->_name, $name); - } - - /** - * Returns a value indicating whether a child exists - * @param string $name the child item name - * @return boolean whether the child exists - * @see IAuthManager::hasItemChild - */ - public function hasChild($name) - { - return $this->_auth->hasItemChild($this->_name, $name); - } - - /** - * Returns the children of this item. - * @return array all child items of this item. - * @see IAuthManager::getItemChildren - */ - public function getChildren() - { - return $this->_auth->getItemChildren($this->_name); - } - - /** - * Assigns this item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return AuthAssignment the authorization assignment information. - * @throws \yii\base\Exception if the item has already been assigned to the user - * @see IAuthManager::assign - */ - public function assign($userId, $bizRule = null, $data = null) - { - return $this->_auth->assign($this->_name, $userId, $bizRule, $data); - } - - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return boolean whether removal is successful - * @see IAuthManager::revoke - */ - public function revoke($userId) - { - return $this->_auth->revoke($this->_name, $userId); - } - - /** - * Returns a value indicating whether this item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return boolean whether the item has been assigned to the user. - * @see IAuthManager::isAssigned - */ - public function isAssigned($userId) - { - return $this->_auth->isAssigned($this->_name, $userId); - } - - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @return AuthAssignment the item assignment information. Null is returned if - * this item is not assigned to the user. - * @see IAuthManager::getAuthAssignment - */ - public function getAssignment($userId) - { - return $this->_auth->getAuthAssignment($this->_name, $userId); - } -} diff --git a/framework/web/AuthManager.php b/framework/web/AuthManager.php deleted file mode 100644 index 4888689..0000000 --- a/framework/web/AuthManager.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -abstract class AuthManager extends Component implements IAuthManager -{ - /** - * @var boolean Enable error reporting for bizRules. - */ - public $showErrors = false; - - /** - * @var array list of role names that are assigned to all users implicitly. - * These roles do not need to be explicitly assigned to any user. - * When calling [[checkAccess()]], these roles will be checked first. - * For performance reason, you should minimize the number of such roles. - * A typical usage of such roles is to define an 'authenticated' role and associate - * it with a biz rule which checks if the current user is authenticated. - * And then declare 'authenticated' in this property so that it can be applied to - * every authenticated user. - */ - public $defaultRoles = array(); - - /** - * Creates a role. - * This is a shortcut method to [[IAuthManager::createAuthItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return AuthItem the authorization item - */ - public function createRole($name, $description = '', $bizRule = null, $data = null) - { - return $this->createAuthItem($name, AuthItem::TYPE_ROLE, $description, $bizRule, $data); - } - - /** - * Creates a task. - * This is a shortcut method to [[IAuthManager::createAuthItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return AuthItem the authorization item - */ - public function createTask($name, $description = '', $bizRule = null, $data = null) - { - return $this->createAuthItem($name, AuthItem::TYPE_TASK, $description, $bizRule, $data); - } - - /** - * Creates an operation. - * This is a shortcut method to [[IAuthManager::createAuthItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return AuthItem the authorization item - */ - public function createOperation($name, $description = '', $bizRule = null, $data = null) - { - return $this->createAuthItem($name, AuthItem::TYPE_OPERATION, $description, $bizRule, $data); - } - - /** - * Returns roles. - * This is a shortcut method to [[IAuthManager::getAuthItems()]]. - * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user - * will be returned. Otherwise, all roles will be returned. - * @return array roles (name=>AuthItem) - */ - public function getRoles($userId = null) - { - return $this->getAuthItems(AuthItem::TYPE_ROLE, $userId); - } - - /** - * Returns tasks. - * This is a shortcut method to [[IAuthManager::getAuthItems()]]. - * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user - * will be returned. Otherwise, all tasks will be returned. - * @return array tasks (name=>AuthItem) - */ - public function getTasks($userId = null) - { - return $this->getAuthItems(AuthItem::TYPE_TASK, $userId); - } - - /** - * Returns operations. - * This is a shortcut method to [[IAuthManager::getAuthItems()]]. - * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user - * will be returned. Otherwise, all operations will be returned. - * @return array operations (name=>AuthItem) - */ - public function getOperations($userId = null) - { - return $this->getAuthItems(AuthItem::TYPE_OPERATION, $userId); - } - - /** - * Executes the specified business rule. - * @param string $bizRule the business rule to be executed. - * @param array $params parameters passed to [[IAuthManager::checkAccess()]]. - * @param mixed $data additional data associated with the authorization item or assignment. - * @return boolean whether the business rule returns true. - * If the business rule is empty, it will still return true. - */ - public function executeBizRule($bizRule, $params, $data) - { - return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); - } - - /** - * Checks the item types to make sure a child can be added to a parent. - * @param integer $parentType parent item type - * @param integer $childType child item type - * @throws Exception if the item cannot be added as a child due to its incompatible type. - */ - protected function checkItemChildType($parentType, $childType) - { - static $types = array('operation', 'task', 'role'); - if ($parentType < $childType) { - throw new Exception("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); - } - } -} diff --git a/framework/web/IAuthManager.php b/framework/web/IAuthManager.php deleted file mode 100644 index 1d2fbb9..0000000 --- a/framework/web/IAuthManager.php +++ /dev/null @@ -1,173 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -interface IAuthManager -{ - /** - * Performs access check for the specified user. - * @param mixed $userId the user ID. This should be either an integer or a string representing - * the unique identifier of a user. See [[User::id]]. - * @param string $itemName the name of the operation that we are checking access to - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. - * @return boolean whether the operations can be performed by the user. - */ - public function checkAccess($userId, $itemName, $params = array()); - - /** - * Creates an authorization item. - * An authorization item represents an action permission (e.g. creating a post). - * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing - * by lower level items. - * @param string $name the item name. This must be a unique identifier. - * @param integer $type the item type (0: operation, 1: task, 2: role). - * @param string $description description of the item - * @param string $bizRule business rule associated with the item. This is a piece of - * PHP code that will be executed when [[checkAccess()]] is called for the item. - * @param mixed $data additional data associated with the item. - * @throws \yii\base\Exception if an item with the same name already exists - * @return AuthItem the authorization item - */ - public function createAuthItem($name, $type, $description = '', $bizRule = null, $data = null); - /** - * Removes the specified authorization item. - * @param string $name the name of the item to be removed - * @return boolean whether the item exists in the storage and has been removed - */ - public function removeAuthItem($name); - /** - * Returns the authorization items of the specific type and user. - * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if - * they are not assigned to a user. - * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, - * meaning returning all items regardless of their type. - * @return array the authorization items of the specific type. - */ - public function getAuthItems($userId = null, $type = null); - /** - * Returns the authorization item with the specified name. - * @param string $name the name of the item - * @return AuthItem the authorization item. Null if the item cannot be found. - */ - public function getAuthItem($name); - /** - * Saves an authorization item to persistent storage. - * @param AuthItem $item the item to be saved. - * @param string $oldName the old item name. If null, it means the item name is not changed. - */ - public function saveAuthItem($item, $oldName = null); - - /** - * Adds an item as a child of another item. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - */ - public function addItemChild($itemName, $childName); - /** - * Removes a child from its parent. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the removal is successful - */ - public function removeItemChild($itemName, $childName); - /** - * Returns a value indicating whether a child exists within a parent. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the child exists - */ - public function hasItemChild($itemName, $childName); - /** - * Returns the children of the specified item. - * @param mixed $itemName the parent item name. This can be either a string or an array. - * The latter represents a list of item names. - * @return array all child items of the parent - */ - public function getItemChildren($itemName); - - /** - * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return AuthAssignment the authorization assignment information. - * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user - */ - public function assign($userId, $itemName, $bizRule = null, $data = null); - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether removal is successful - */ - public function revoke($userId, $itemName); - /** - * Returns a value indicating whether the item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether the item has been assigned to the user. - */ - public function isAssigned($userId, $itemName); - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return AuthAssignment the item assignment information. Null is returned if - * the item is not assigned to the user. - */ - public function getAuthAssignment($userId, $itemName); - /** - * Returns the item assignments for the specified user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return array the item assignment information for the user. An empty array will be - * returned if there is no item assigned to the user. - */ - public function getAuthAssignments($userId); - /** - * Saves the changes to an authorization assignment. - * @param AuthAssignment $assignment the assignment that has been changed. - */ - public function saveAuthAssignment($assignment); - /** - * Removes all authorization data. - */ - public function clearAll(); - /** - * Removes all authorization assignments. - */ - public function clearAuthAssignments(); - /** - * Saves authorization data into persistent storage. - * If any change is made to the authorization data, please make - * sure you call this method to save the changed data into persistent storage. - */ - public function save(); - /** - * Executes a business rule. - * A business rule is a piece of PHP code that will be executed when [[checkAccess()]] is called. - * @param string $bizRule the business rule to be executed. - * @param array $params additional parameters to be passed to the business rule when being executed. - * @param mixed $data additional data that is associated with the corresponding authorization item or assignment - * @return boolean whether the execution returns a true value. - * If the business rule is empty, it will also return true. - */ - public function executeBizRule($bizRule, $params, $data); -} From 17906e712c301e252343bfb74cf6a648d955096b Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 9 May 2013 19:04:50 +0400 Subject: [PATCH 03/14] Application::getAuthManager() comment fix --- framework/base/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/base/Application.php b/framework/base/Application.php index 6bd31f1..2e068ef 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -306,7 +306,7 @@ class Application extends Module /** * Returns the auth manager for this application. - * @return \yii\web\AuthManager the auth manager for this application. + * @return \yii\rbac\Manager the auth manager for this application. */ public function getAuthManager() { From 850ff06560268420a7c14cd1104420fc9bf74477 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 9 May 2013 22:17:12 +0400 Subject: [PATCH 04/14] DbManager added + other classes comments fixes --- framework/rbac/Assignment.php | 8 +- framework/rbac/DbManager.php | 574 ++++++++++++++++++++++++++++++++++++++++++ framework/rbac/IManager.php | 173 +++++++++++++ framework/rbac/Item.php | 2 +- framework/rbac/Manager.php | 6 +- framework/rbac/PhpManager.php | 24 +- 6 files changed, 772 insertions(+), 15 deletions(-) create mode 100644 framework/rbac/DbManager.php create mode 100644 framework/rbac/IManager.php diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php index c2a9f56..576017e 100644 --- a/framework/rbac/Assignment.php +++ b/framework/rbac/Assignment.php @@ -19,24 +19,24 @@ use yii\base\Object; class Assignment extends Object { private $_auth; - private $_itemName; private $_userId; + private $_itemName; private $_bizRule; private $_data; /** * Constructor. * @param IManager $auth the authorization manager - * @param string $itemName authorization item name * @param mixed $userId user ID (see [[User::id]]) + * @param string $itemName authorization item name * @param string $bizRule the business rule associated with this assignment * @param mixed $data additional data for this assignment */ - public function __construct($auth, $itemName, $userId, $bizRule = null, $data = null) + public function __construct($auth, $userId, $itemName, $bizRule = null, $data = null) { $this->_auth = $auth; - $this->_itemName = $itemName; $this->_userId = $userId; + $this->_itemName = $itemName; $this->_bizRule = $bizRule; $this->_data = $data; } diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php new file mode 100644 index 0000000..5f64694 --- /dev/null +++ b/framework/rbac/DbManager.php @@ -0,0 +1,574 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class DbManager extends Manager +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbManager object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string the name of the table storing authorization items. Defaults to 'tbl_auth_item'. + */ + public $itemTable = 'tbl_auth_item'; + /** + * @var string the name of the table storing authorization item hierarchy. Defaults to 'tbl_auth_item_child'. + */ + public $itemChildTable = 'tbl_auth_item_child'; + /** + * @var string the name of the table storing authorization item assignments. Defaults to 'tbl_auth_assignment'. + */ + public $assignmentTable = 'tbl_auth_assignment'; + + private $_usingSqlite; + + /** + * Initializes the application component. + * This method overrides the parent implementation by establishing the database connection. + */ + public function init() + { + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("DbManager::db must be either a DB connection instance or the application component ID of a DB connection."); + } + $this->_usingSqlite = !strncmp($this->db->getDriverName(), 'sqlite', 6); + parent::init(); + } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should can be either an integer and a string representing + * @param string $itemName the name of the operation that need access check + * the unique identifier of a user. See [[User::id]]. + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, + * which holds the value of `$userId`. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()) + { + $assignments = $this->getAssignments($userId); + return $this->checkAccessRecursive($userId, $itemName, $params, $assignments); + } + + /** + * Performs access check for the specified user. + * This method is internally called by [[checkAccess()]]. + * @param mixed $userId the user ID. This should can be either an integer and a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that need access check + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, + * which holds the value of `$userId`. + * @param Assignment[] $assignments the assignments to the specified user + * @return boolean whether the operations can be performed by the user. + */ + protected function checkAccessRecursive($userId, $itemName, $params, $assignments) + { + if (($item = $this->getItem($itemName)) === null) { + return false; + } + Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); + if (!isset($params['userId'])) { + $params['userId'] = $userId; + } + if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if (in_array($itemName, $this->defaultRoles)) { + return true; + } + if (isset($assignments[$itemName])) { + $assignment = $assignments[$itemName]; + if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + return true; + } + } + $query = new Query; + $parents = $query->select(array('parent')) + ->from($this->itemChildTable) + ->where('child=:name', array(':name' => $itemName)) + ->createCommand($this->db) + ->queryColumn(); + foreach ($parents as $parent) { + if ($this->checkAccessRecursive($userId, $parent, $params, $assignments)) { + return true; + } + } + } + return false; + } + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the item is added successfully + * @throws Exception if either parent or child doesn't exist or if a loop has been detected. + */ + public function addItemChild($itemName, $childName) + { + if ($itemName === $childName) { + throw new Exception("Cannot add '$itemName' as a child of itself."); + } + $query = new Query; + $rows = $query->from($this->itemTable) + ->where('name=:name1 OR name=:name2', array( + ':name1' => $itemName, + ':name2' => $childName + )) + ->createCommand($this->db) + ->queryAll(); + if (count($rows) == 2) { + if ($rows[0]['name'] === $itemName) { + $parentType = $rows[0]['type']; + $childType = $rows[1]['type']; + } else { + $childType = $rows[0]['type']; + $parentType = $rows[1]['type']; + } + $this->checkItemChildType($parentType, $childType); + if ($this->detectLoop($itemName, $childName)) { + throw new Exception("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + } + $this->db->createCommand() + ->insert($this->itemChildTable, array( + 'parent' => $itemName, + 'child' => $childName, + )); + return true; + } else { + throw new Exception("Either '$itemName' or '$childName' does not exist."); + } + } + + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName) + { + return $this->db->createCommand() + ->delete($this->itemChildTable, 'parent=:parent AND child=:child', array( + ':parent' => $itemName, + ':child' => $childName + )) > 0; + } + + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName) + { + $query = new Query; + return $query->select(array('parent')) + ->from($this->itemChildTable) + ->where('parent=:parent AND child=:child', array( + ':parent' => $itemName, + ':child' => $childName)) + ->createCommand($this->db) + ->queryScalar() !== false; + } + + /** + * Returns the children of the specified item. + * @param mixed $names the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + public function getItemChildren($names) + { + if (is_string($names)) { + $condition = 'parent=' . $this->db->quoteValue($names); + } elseif (is_array($names) && !empty($names)) { + foreach ($names as &$name) { + $name = $this->db->quoteValue($name); + } + $condition = 'parent IN (' . implode(', ', $names) . ')'; + } + $query = new Query; + $rows = $query->select(array('name', 'type', 'description', 'bizrule', 'data')) + ->from(array( + $this->itemTable, + $this->itemChildTable + )) + ->where($condition . ' AND name=child') + ->createCommand($this->db) + ->queryAll(); + $children = array(); + foreach ($rows as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $children[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } + return $children; + } + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws Exception if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null) + { + if ($this->usingSqlite() && $this->getItem($itemName) === null) { + throw new Exception("The item '$itemName' does not exist."); + } + $this->db->createCommand() + ->insert($this->assignmentTable, array( + 'userid' => $userId, + 'itemname' => $itemName, + 'bizrule' => $bizRule, + 'data' => serialize($data) + )); + return new Assignment($this, $userId, $itemName, $bizRule, $data); + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName) + { + return $this->db->createCommand() + ->delete($this->assignmentTable, 'itemname=:itemname AND userid=:userid', array( + ':userid' => $userId, + ':itemname' => $itemName + )) > 0; + } + + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($itemName, $userId) + { + $query = new Query; + return $query->select(array('itemname')) + ->from($this->assignmentTable) + ->where('itemname=:itemname AND userid=:userid', array( + ':userid' => $userId, + ':itemname' => $itemName + )) + ->createCommand($this->db) + ->queryScalar() !== false; + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAssignment($userId, $itemName) + { + $query = new Query; + $row = $query->from($this->assignmentTable) + ->where('itemname=:itemname AND userid=:userid', array( + ':userid' => $userId, + ':itemname' => $itemName + )) + ->createCommand($this->db) + ->queryRow(); + if ($row !== false) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + return new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); + } else { + return null; + } + } + + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Assignment[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAssignments($userId) + { + $query = new Query; + $rows = $query->from($this->assignmentTable) + ->where('userid=:userid', array(':userid' => $userId)) + ->createCommand($this->db) + ->queryAll(); + $assignments = array(); + foreach ($rows as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $assignments[$row['itemname']] = new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); + } + return $assignments; + } + + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + public function saveAssignment($assignment) + { + $this->db->createCommand() + ->update($this->assignmentTable, array( + 'bizrule' => $assignment->getBizRule(), + 'data' => serialize($assignment->getData()), + ), 'itemname=:itemname AND userid=:userid', array( + 'userid' => $assignment->getUserId(), + 'itemname' => $assignment->getItemName() + )); + } + + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + public function getItems($userId = null, $type = null) + { + $query = new Query; + if ($userId === null && $type === null) { + $command = $query->from($this->itemTable) + ->createCommand($this->db); + } elseif ($userId === null) { + $command = $query->from($this->itemTable) + ->where('type=:type', array(':type' => $type)) + ->createCommand($this->db); + } elseif ($type === null) { + $command = $query->select(array('name', 'type', 'description', 't1.bizrule', 't1.data')) + ->from(array( + $this->itemTable . ' t1', + $this->assignmentTable . ' t2' + )) + ->where('name=itemname AND userid=:userid', array(':userid' => $userId)) + ->createCommand($this->db); + } else { + $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') + ->from(array( + $this->itemTable . ' t1', + $this->assignmentTable . ' t2' + )) + ->where('name=itemname AND type=:type AND userid=:userid', array( + ':userid' => $userId, + ':type' => $type + )) + ->createCommand($this->db); + } + $items = array(); + foreach ($command->queryAll() as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $items[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } + return $items; + } + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @return Item the authorization item + * @throws Exception if an item with the same name already exists + */ + public function createItem($name, $type, $description = '', $bizRule = null, $data = null) + { + $this->db->createCommand() + ->insert($this->itemTable, array( + 'name' => $name, + 'type' => $type, + 'description' => $description, + 'bizrule' => $bizRule, + 'data' => serialize($data) + )); + return new Item($this, $name, $type, $description, $bizRule, $data); + } + + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeItem($name) + { + if ($this->usingSqlite()) { + $this->db->createCommand() + ->delete($this->itemChildTable, 'parent=:name1 OR child=:name2', array( + ':name1' => $name, + ':name2' => $name + )); + $this->db->createCommand() + ->delete($this->assignmentTable, 'itemname=:name', array( + ':name' => $name, + )); + } + + return $this->db->createCommand() + ->delete($this->itemTable, 'name=:name', array( + ':name' => $name + )) > 0; + } + + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + public function getItem($name) + { + $query = new Query; + $row = $query->from($this->itemTable) + ->where('name=:name', array(':name' => $name)) + ->createCommand($this->db) + ->queryRow(); + + if ($row !== false) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + return new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } else + return null; + } + + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + public function saveItem($item, $oldName = null) + { + if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { + $this->db->createCommand() + ->update($this->itemChildTable, array( + 'parent' => $item->getName(), + ), 'parent=:whereName', array( + ':whereName' => $oldName, + )); + $this->db->createCommand() + ->update($this->itemChildTable, array( + 'child' => $item->getName(), + ), 'child=:whereName', array( + ':whereName' => $oldName, + )); + $this->db->createCommand() + ->update($this->assignmentTable, array( + 'itemname' => $item->getName(), + ), 'itemname=:whereName', array( + ':whereName' => $oldName, + )); + } + + $this->db->createCommand() + ->update($this->itemTable, array( + 'name' => $item->getName(), + 'type' => $item->getType(), + 'description' => $item->getDescription(), + 'bizrule' => $item->getBizRule(), + 'data' => serialize($item->getData()), + ), 'name=:whereName', array( + ':whereName' => $oldName === null ? $item->getName() : $oldName, + )); + } + + /** + * Saves the authorization data to persistent storage. + */ + public function save() + { + } + + /** + * Removes all authorization data. + */ + public function clearAll() + { + $this->clearAssignments(); + $this->db->createCommand()->delete($this->itemChildTable); + $this->db->createCommand()->delete($this->itemTable); + } + + /** + * Removes all authorization assignments. + */ + public function clearAssignments() + { + $this->db->createCommand()->delete($this->assignmentTable); + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * @param string $itemName parent item name + * @param string $childName the name of the child item that is to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($itemName, $childName) + { + if ($childName === $itemName) { + return true; + } + foreach ($this->getItemChildren($childName) as $child) { + if ($this->detectLoop($itemName, $child->getName())) { + return true; + } + } + return false; + } + + /** + * @return boolean whether the database is a SQLite database + */ + protected function usingSqlite() + { + return $this->_usingSqlite; + } +} diff --git a/framework/rbac/IManager.php b/framework/rbac/IManager.php new file mode 100644 index 0000000..6eeb661 --- /dev/null +++ b/framework/rbac/IManager.php @@ -0,0 +1,173 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +interface IManager +{ + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that we are checking access to + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()); + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @throws \yii\base\Exception if an item with the same name already exists + * @return Item the authorization item + */ + public function createItem($name, $type, $description = '', $bizRule = null, $data = null); + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeItem($name); + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + public function getItems($userId = null, $type = null); + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + public function getItem($name); + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + public function saveItem($item, $oldName = null); + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + */ + public function addItemChild($itemName, $childName); + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName); + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName); + /** + * Returns the children of the specified item. + * @param mixed $itemName the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + public function getItemChildren($itemName); + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null); + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName); + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($userId, $itemName); + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAssignment($userId, $itemName); + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Item[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAssignments($userId); + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + public function saveAssignment($assignment); + /** + * Removes all authorization data. + */ + public function clearAll(); + /** + * Removes all authorization assignments. + */ + public function clearAssignments(); + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + public function save(); + /** + * Executes a business rule. + * A business rule is a piece of PHP code that will be executed when [[checkAccess()]] is called. + * @param string $bizRule the business rule to be executed. + * @param array $params additional parameters to be passed to the business rule when being executed. + * @param mixed $data additional data that is associated with the corresponding authorization item or assignment + * @return boolean whether the execution returns a true value. + * If the business rule is empty, it will also return true. + */ + public function executeBizRule($bizRule, $params, $data); +} diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php index ba78e60..d09196f 100644 --- a/framework/rbac/Item.php +++ b/framework/rbac/Item.php @@ -202,7 +202,7 @@ class Item extends Object /** * Returns the children of this item. - * @return array all child items of this item. + * @return Item[] all child items of this item. * @see IManager::getItemChildren */ public function getChildren() diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php index 2f77e10..4846546 100644 --- a/framework/rbac/Manager.php +++ b/framework/rbac/Manager.php @@ -83,7 +83,7 @@ abstract class Manager extends Component implements IManager * This is a shortcut method to [[IManager::getItems()]]. * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user * will be returned. Otherwise, all roles will be returned. - * @return array roles (name=>AuthItem) + * @return Item[] roles (name=>AuthItem) */ public function getRoles($userId = null) { @@ -95,7 +95,7 @@ abstract class Manager extends Component implements IManager * This is a shortcut method to [[IManager::getItems()]]. * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user * will be returned. Otherwise, all tasks will be returned. - * @return array tasks (name=>AuthItem) + * @return Item[] tasks (name=>AuthItem) */ public function getTasks($userId = null) { @@ -107,7 +107,7 @@ abstract class Manager extends Component implements IManager * This is a shortcut method to [[IManager::getItems()]]. * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user * will be returned. Otherwise, all operations will be returned. - * @return array operations (name=>AuthItem) + * @return Item[] operations (name=>AuthItem) */ public function getOperations($userId = null) { diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 08f5c83..3f471c6 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -52,8 +52,8 @@ class PhpManager extends Manager * @param string $itemName the name of the operation that need access check * the unique identifier of a user. See [[User::id]]. * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. - * Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of $userId. + * with the tasks and roles assigned to the user. A param with name 'userId' is added to + * this array, which holds the value of `$userId`. * @return boolean whether the operations can be performed by the user. */ public function checkAccess($userId, $itemName, $params = array()) @@ -61,6 +61,7 @@ class PhpManager extends Manager if (!isset($this->_items[$itemName])) { return false; } + /** @var $item Item */ $item = $this->_items[$itemName]; Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); if (!isset($params['userId'])) { @@ -71,6 +72,7 @@ class PhpManager extends Manager return true; } if (isset($this->_assignments[$userId][$itemName])) { + /** @var $assignment Assignment */ $assignment = $this->_assignments[$userId][$itemName]; if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { return true; @@ -97,7 +99,9 @@ class PhpManager extends Manager if (!isset($this->_items[$childName], $this->_items[$itemName])) { throw new Exception("Either '$itemName' or '$childName' does not exist."); } + /** @var $child Item */ $child = $this->_items[$childName]; + /** @var $item Item */ $item = $this->_items[$itemName]; $this->checkItemChildType($item->getType(), $child->getType()); if ($this->detectLoop($itemName, $childName)) { @@ -142,7 +146,7 @@ class PhpManager extends Manager * Returns the children of the specified item. * @param mixed $names the parent item name. This can be either a string or an array. * The latter represents a list of item names. - * @return array all child items of the parent + * @return Item[] all child items of the parent */ public function getItemChildren($names) { @@ -176,7 +180,7 @@ class PhpManager extends Manager } elseif (isset($this->_assignments[$userId][$itemName])) { throw new Exception("Authorization item '$itemName' has already been assigned to user '$userId'."); } else { - return $this->_assignments[$userId][$itemName] = new Assignment($this, $itemName, $userId, $bizRule, $data); + return $this->_assignments[$userId][$itemName] = new Assignment($this, $userId, $itemName, $bizRule, $data); } } @@ -222,7 +226,7 @@ class PhpManager extends Manager /** * Returns the item assignments for the specified user. * @param mixed $userId the user ID (see [[User::id]]) - * @return array the item assignment information for the user. An empty array will be + * @return Assignment[] the item assignment information for the user. An empty array will be * returned if there is no item assigned to the user. */ public function getAssignments($userId) @@ -236,22 +240,24 @@ class PhpManager extends Manager * they are not assigned to a user. * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, * meaning returning all items regardless of their type. - * @return array the authorization items of the specific type. + * @return Item[] the authorization items of the specific type. */ public function getItems($userId = null, $type = null) { - if ($type === null && $userId === null) { + if ($userId === null && $type === null) { return $this->_items; } $items = array(); if ($userId === null) { foreach ($this->_items as $name => $item) { + /** @var $item Item */ if ($item->getType() == $type) { $items[$name] = $item; } } } elseif (isset($this->_assignments[$userId])) { foreach ($this->_assignments[$userId] as $assignment) { + /** @var $assignment Assignment */ $name = $assignment->getItemName(); if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->getType() == $type)) { $items[$name] = $this->_items[$name]; @@ -368,6 +374,7 @@ class PhpManager extends Manager { $items = array(); foreach ($this->_items as $name => $item) { + /** @var $item Item */ $items[$name] = array( 'type' => $item->getType(), 'description' => $item->getDescription(), @@ -376,6 +383,7 @@ class PhpManager extends Manager ); if (isset($this->_children[$name])) { foreach ($this->_children[$name] as $child) { + /** @var $child Item */ $items[$name]['children'][] = $child->getName(); } } @@ -383,6 +391,7 @@ class PhpManager extends Manager foreach ($this->_assignments as $userId => $assignments) { foreach ($assignments as $name => $assignment) { + /** @var $assignment Assignment */ if (isset($items[$name])) { $items[$name]['assignments'][$userId] = array( 'bizRule' => $assignment->getBizRule(), @@ -457,6 +466,7 @@ class PhpManager extends Manager return false; } foreach ($this->_children[$childName] as $child) { + /** @var $child Item */ if ($this->detectLoop($itemName, $child->getName())) { return true; } From c9fe510ebbe14266519b8feb1e5b687075372dad Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 09:52:21 +0400 Subject: [PATCH 05/14] Comment fixes + class descriptions added --- framework/rbac/Assignment.php | 9 +++++++++ framework/rbac/DbManager.php | 12 ++++++++++-- framework/rbac/IManager.php | 2 ++ framework/rbac/Item.php | 14 ++++++++++++++ framework/rbac/Manager.php | 24 ++++++++++++++++++++++++ framework/rbac/PhpManager.php | 13 ++++++++++++- 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php index 576017e..e5d6157 100644 --- a/framework/rbac/Assignment.php +++ b/framework/rbac/Assignment.php @@ -11,6 +11,15 @@ use Yii; use yii\base\Object; /** + * Assignment represents an assignment of a role to a user. + * It includes additional assignment information such as [[bizRule]] and [[data]]. + * Do not create a Assignment instance using the 'new' operator. + * Instead, call [[IManager::assign]]. + * + * @property mixed $userId User ID (see [[User::id]]). + * @property string $itemName The authorization item name. + * @property string $bizRule The business rule associated with this assignment. + * @property mixed $data Additional data for this assignment. * * @author Qiang Xue * @author Alexander Kochetov diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 5f64694..d3c584c 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -14,6 +14,14 @@ use yii\base\Exception; use yii\base\InvalidConfigException; /** + * DbManager represents an authorization manager that stores authorization information in database. + * + * The database connection is specified by [[db]]. And the database schema + * should be as described in "framework/rbac/*.sql". You may change the names of + * the three tables used to store the authorization data by setting [[itemTable]], + * [[itemChildTable]] and [[assignmentTable]]. + * + * @property array $authItems The authorization items of the specific type. * * @author Qiang Xue * @author Alexander Kochetov @@ -60,9 +68,9 @@ class DbManager extends Manager /** * Performs access check for the specified user. - * @param mixed $userId the user ID. This should can be either an integer and a string representing - * @param string $itemName the name of the operation that need access check + * @param mixed $userId the user ID. This should can be either an integer or a string representing * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that need access check * @param array $params name-value pairs that would be passed to biz rules associated * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, * which holds the value of `$userId`. diff --git a/framework/rbac/IManager.php b/framework/rbac/IManager.php index 6eeb661..0e9eea0 100644 --- a/framework/rbac/IManager.php +++ b/framework/rbac/IManager.php @@ -10,6 +10,8 @@ namespace yii\rbac; /** * IManager interface is implemented by an auth manager application component. * + * An auth manager is mainly responsible for providing role-based access control (RBAC) service. + * * @author Qiang Xue * @author Alexander Kochetov * @since 2.0 diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php index d09196f..1297e41 100644 --- a/framework/rbac/Item.php +++ b/framework/rbac/Item.php @@ -11,6 +11,20 @@ use Yii; use yii\base\Object; /** + * Item represents an authorization item. + * An authorization item can be an operation, a task or a role. + * They form an authorization hierarchy. Items on higher levels of the hierarchy + * inherit the permissions represented by items on lower levels. + * A user may be assigned one or several authorization items (called [[Assignment]] assignments). + * He can perform an operation only when it is among his assigned items. + * + * @property IManager $authManager The authorization manager. + * @property integer $type The authorization item type. This could be 0 (operation), 1 (task) or 2 (role). + * @property string $name The item name. + * @property string $description The item description. + * @property string $bizRule The business rule associated with this item. + * @property mixed $data The additional data associated with this item. + * @property array $children All child items of this item. * * @author Qiang Xue * @author Alexander Kochetov diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php index 4846546..6d0b74d 100644 --- a/framework/rbac/Manager.php +++ b/framework/rbac/Manager.php @@ -12,6 +12,30 @@ use yii\base\Component; use yii\base\Exception; /** + * Manager is the base class for authorization manager classes. + * + * Manager extends [[Component]] and implements some methods + * that are common among authorization manager classes. + * + * Manager together with its concrete child classes implement the Role-Based + * Access Control (RBAC). + * + * The main idea is that permissions are organized as a hierarchy of + * [[Item]] authorization items. Items on higer level inherit the permissions + * represented by items on lower level. And roles are simply top-level authorization items + * that may be assigned to individual users. A user is said to have a permission + * to do something if the corresponding authorization item is inherited by one of his roles. + * + * Using authorization manager consists of two aspects. First, the authorization hierarchy + * and assignments have to be established. Manager and its child classes + * provides APIs to accomplish this task. Developers may need to develop some GUI + * so that it is more intuitive to end-users. Second, developers call [[IManager::checkAccess()]] + * at appropriate places in the application code to check if the current user + * has the needed permission for an operation. + * + * @property array $roles Roles (name=>Item). + * @property array $tasks Tasks (name=>Item). + * @property array $operations Operations (name=>Item). * * @author Qiang Xue * @author Alexander Kochetov diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 3f471c6..baacb36 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -11,6 +11,17 @@ use Yii; use yii\base\Exception; /** + * PhpManager represents an authorization manager that stores authorization + * information in terms of a PHP script file. + * + * The authorization data will be saved to and loaded from a file + * specified by [[authFile]], which defaults to 'protected/data/rbac.php'. + * + * PhpManager is mainly suitable for authorization data that is not too big + * (for example, the authorization data for a personal blog system). + * Use [[DbManager]] for more complex authorization data. + * + * @property array $authItems The authorization items of the specific type. * * @author Qiang Xue * @author Alexander Kochetov @@ -20,7 +31,7 @@ class PhpManager extends Manager { /** * @var string the path of the PHP script that contains the authorization data. - * If not set, it will be using 'protected/data/auth.php' as the data file. + * If not set, it will be using 'protected/data/rbac.php' as the data file. * Make sure this file is writable by the Web server process if the authorization * needs to be changed. * @see loadFromFile From 132391065ca3a148ffa777985ac72886bf9e6ab6 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 09:59:18 +0400 Subject: [PATCH 06/14] Comment fixes --- framework/rbac/DbManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index d3c584c..81a0edd 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -85,7 +85,7 @@ class DbManager extends Manager /** * Performs access check for the specified user. * This method is internally called by [[checkAccess()]]. - * @param mixed $userId the user ID. This should can be either an integer and a string representing + * @param mixed $userId the user ID. This should can be either an integer or a string representing * the unique identifier of a user. See [[User::id]]. * @param string $itemName the name of the operation that need access check * @param array $params name-value pairs that would be passed to biz rules associated From 56757cdd94dca5fe357f02373f7f9b8ace130120 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 10:02:36 +0400 Subject: [PATCH 07/14] PhpManager::saveToFile() LOCK_EX added --- framework/rbac/PhpManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index baacb36..60eb193 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -508,6 +508,6 @@ class PhpManager extends Manager */ protected function saveToFile($data, $file) { - file_put_contents($file, " Date: Fri, 10 May 2013 10:15:19 +0400 Subject: [PATCH 08/14] Exception types corrected --- framework/rbac/DbManager.php | 6 ++++-- framework/rbac/Manager.php | 6 +++--- framework/rbac/PhpManager.php | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 81a0edd..286c0b5 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -12,6 +12,7 @@ use yii\db\Connection; use yii\db\Query; use yii\base\Exception; use yii\base\InvalidConfigException; +use yii\base\InvalidCallException; /** * DbManager represents an authorization manager that stores authorization information in database. @@ -133,7 +134,8 @@ class DbManager extends Manager * @param string $itemName the parent item name * @param string $childName the child item name * @return boolean whether the item is added successfully - * @throws Exception if either parent or child doesn't exist or if a loop has been detected. + * @throws Exception if either parent or child doesn't exist. + * @throws InvalidCallException if a loop has been detected. */ public function addItemChild($itemName, $childName) { @@ -158,7 +160,7 @@ class DbManager extends Manager } $this->checkItemChildType($parentType, $childType); if ($this->detectLoop($itemName, $childName)) { - throw new Exception("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); } $this->db->createCommand() ->insert($this->itemChildTable, array( diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php index 6d0b74d..ce36e60 100644 --- a/framework/rbac/Manager.php +++ b/framework/rbac/Manager.php @@ -9,7 +9,7 @@ namespace yii\rbac; use Yii; use yii\base\Component; -use yii\base\Exception; +use yii\base\InvalidParamException; /** * Manager is the base class for authorization manager classes. @@ -155,13 +155,13 @@ abstract class Manager extends Component implements IManager * Checks the item types to make sure a child can be added to a parent. * @param integer $parentType parent item type * @param integer $childType child item type - * @throws Exception if the item cannot be added as a child due to its incompatible type. + * @throws InvalidParamException if the item cannot be added as a child due to its incompatible type. */ protected function checkItemChildType($parentType, $childType) { static $types = array('operation', 'task', 'role'); if ($parentType < $childType) { - throw new Exception("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); + throw new InvalidParamException("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); } } } diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 60eb193..8a4dbec 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -9,6 +9,8 @@ namespace yii\rbac; use Yii; use yii\base\Exception; +use yii\base\InvalidCallException; +use yii\base\InvalidParamException; /** * PhpManager represents an authorization manager that stores authorization @@ -103,7 +105,8 @@ class PhpManager extends Manager * @param string $itemName the parent item name * @param string $childName the child item name * @return boolean whether the item is added successfully - * @throws Exception if either parent or child doesn't exist or if a loop has been detected. + * @throws Exception if either parent or child doesn't exist. + * @throws InvalidCallException if item already has a child with $itemName or if a loop has been detected. */ public function addItemChild($itemName, $childName) { @@ -116,10 +119,10 @@ class PhpManager extends Manager $item = $this->_items[$itemName]; $this->checkItemChildType($item->getType(), $child->getType()); if ($this->detectLoop($itemName, $childName)) { - throw new Exception("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); } if (isset($this->_children[$itemName][$childName])) { - throw new Exception("The item '$itemName' already has a child '$childName'."); + throw new InvalidCallException("The item '$itemName' already has a child '$childName'."); } $this->_children[$itemName][$childName] = $this->_items[$childName]; return true; @@ -182,14 +185,14 @@ class PhpManager extends Manager * for this particular authorization item. * @param mixed $data additional data associated with this assignment * @return Assignment the authorization assignment information. - * @throws Exception if the item does not exist or if the item has already been assigned to the user + * @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user */ public function assign($userId, $itemName, $bizRule = null, $data = null) { if (!isset($this->_items[$itemName])) { - throw new Exception("Unknown authorization item '$itemName'."); + throw new InvalidParamException("Unknown authorization item '$itemName'."); } elseif (isset($this->_assignments[$userId][$itemName])) { - throw new Exception("Authorization item '$itemName' has already been assigned to user '$userId'."); + throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'."); } else { return $this->_assignments[$userId][$itemName] = new Assignment($this, $userId, $itemName, $bizRule, $data); } @@ -336,14 +339,14 @@ class PhpManager extends Manager * Saves an authorization item to persistent storage. * @param Item $item the item to be saved. * @param string $oldName the old item name. If null, it means the item name is not changed. - * @throws Exception if an item with the same name already taken + * @throws InvalidParamException if an item with the same name already taken */ public function saveItem($item, $oldName = null) { if ($oldName !== null && ($newName = $item->getName()) !== $oldName) // name changed { if (isset($this->_items[$newName])) { - throw new Exception("Unable to change the item name. The name '$newName' is already used by another item."); + throw new InvalidParamException("Unable to change the item name. The name '$newName' is already used by another item."); } if (isset($this->_items[$oldName]) && $this->_items[$oldName] === $item) { unset($this->_items[$oldName]); From a03d1164adbe2a0a5a81958918e0bc2fe731ab2f Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 10:47:08 +0400 Subject: [PATCH 09/14] New condition hash format used where its possible --- framework/rbac/DbManager.php | 93 +++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index 286c0b5..ea6e8ac 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -117,7 +117,7 @@ class DbManager extends Manager $query = new Query; $parents = $query->select(array('parent')) ->from($this->itemChildTable) - ->where('child=:name', array(':name' => $itemName)) + ->where(array('child' => $itemName)) ->createCommand($this->db) ->queryColumn(); foreach ($parents as $parent) { @@ -144,7 +144,7 @@ class DbManager extends Manager } $query = new Query; $rows = $query->from($this->itemTable) - ->where('name=:name1 OR name=:name2', array( + ->where(array('or', 'name=:name1', 'name=:name2'), array( ':name1' => $itemName, ':name2' => $childName )) @@ -183,9 +183,9 @@ class DbManager extends Manager public function removeItemChild($itemName, $childName) { return $this->db->createCommand() - ->delete($this->itemChildTable, 'parent=:parent AND child=:child', array( - ':parent' => $itemName, - ':child' => $childName + ->delete($this->itemChildTable, array( + 'parent' => $itemName, + 'child' => $childName )) > 0; } @@ -200,9 +200,10 @@ class DbManager extends Manager $query = new Query; return $query->select(array('parent')) ->from($this->itemChildTable) - ->where('parent=:parent AND child=:child', array( - ':parent' => $itemName, - ':child' => $childName)) + ->where(array( + 'parent' => $itemName, + 'child' => $childName + )) ->createCommand($this->db) ->queryScalar() !== false; } @@ -215,21 +216,14 @@ class DbManager extends Manager */ public function getItemChildren($names) { - if (is_string($names)) { - $condition = 'parent=' . $this->db->quoteValue($names); - } elseif (is_array($names) && !empty($names)) { - foreach ($names as &$name) { - $name = $this->db->quoteValue($name); - } - $condition = 'parent IN (' . implode(', ', $names) . ')'; - } $query = new Query; $rows = $query->select(array('name', 'type', 'description', 'bizrule', 'data')) ->from(array( $this->itemTable, $this->itemChildTable )) - ->where($condition . ' AND name=child') + ->where(array('parent'=>$names)) + ->andWhere('name=child') ->createCommand($this->db) ->queryAll(); $children = array(); @@ -276,9 +270,9 @@ class DbManager extends Manager public function revoke($userId, $itemName) { return $this->db->createCommand() - ->delete($this->assignmentTable, 'itemname=:itemname AND userid=:userid', array( - ':userid' => $userId, - ':itemname' => $itemName + ->delete($this->assignmentTable, array( + 'userid' => $userId, + 'itemname' => $itemName )) > 0; } @@ -293,9 +287,9 @@ class DbManager extends Manager $query = new Query; return $query->select(array('itemname')) ->from($this->assignmentTable) - ->where('itemname=:itemname AND userid=:userid', array( - ':userid' => $userId, - ':itemname' => $itemName + ->where(array( + 'userid' => $userId, + 'itemname' => $itemName )) ->createCommand($this->db) ->queryScalar() !== false; @@ -312,9 +306,9 @@ class DbManager extends Manager { $query = new Query; $row = $query->from($this->assignmentTable) - ->where('itemname=:itemname AND userid=:userid', array( - ':userid' => $userId, - ':itemname' => $itemName + ->where(array( + 'userid' => $userId, + 'itemname' => $itemName )) ->createCommand($this->db) ->queryRow(); @@ -338,7 +332,7 @@ class DbManager extends Manager { $query = new Query; $rows = $query->from($this->assignmentTable) - ->where('userid=:userid', array(':userid' => $userId)) + ->where(array('userid' => $userId)) ->createCommand($this->db) ->queryAll(); $assignments = array(); @@ -361,7 +355,7 @@ class DbManager extends Manager ->update($this->assignmentTable, array( 'bizrule' => $assignment->getBizRule(), 'data' => serialize($assignment->getData()), - ), 'itemname=:itemname AND userid=:userid', array( + ), array( 'userid' => $assignment->getUserId(), 'itemname' => $assignment->getItemName() )); @@ -383,7 +377,7 @@ class DbManager extends Manager ->createCommand($this->db); } elseif ($userId === null) { $command = $query->from($this->itemTable) - ->where('type=:type', array(':type' => $type)) + ->where(array('type' => $type)) ->createCommand($this->db); } elseif ($type === null) { $command = $query->select(array('name', 'type', 'description', 't1.bizrule', 't1.data')) @@ -391,7 +385,8 @@ class DbManager extends Manager $this->itemTable . ' t1', $this->assignmentTable . ' t2' )) - ->where('name=itemname AND userid=:userid', array(':userid' => $userId)) + ->where(array('userid' => $userId)) + ->andWhere('name=itemname') ->createCommand($this->db); } else { $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') @@ -399,10 +394,11 @@ class DbManager extends Manager $this->itemTable . ' t1', $this->assignmentTable . ' t2' )) - ->where('name=itemname AND type=:type AND userid=:userid', array( - ':userid' => $userId, - ':type' => $type + ->where(array( + 'userid' => $userId, + 'type' => $type )) + ->andWhere('name=itemname') ->createCommand($this->db); } $items = array(); @@ -452,20 +448,13 @@ class DbManager extends Manager { if ($this->usingSqlite()) { $this->db->createCommand() - ->delete($this->itemChildTable, 'parent=:name1 OR child=:name2', array( + ->delete($this->itemChildTable, array('or', 'parent=:name1', 'child=:name2'), array( ':name1' => $name, ':name2' => $name )); - $this->db->createCommand() - ->delete($this->assignmentTable, 'itemname=:name', array( - ':name' => $name, - )); + $this->db->createCommand()->delete($this->assignmentTable, array('itemname' => $name)); } - - return $this->db->createCommand() - ->delete($this->itemTable, 'name=:name', array( - ':name' => $name - )) > 0; + return $this->db->createCommand()->delete($this->itemTable, array('name' => $name)) > 0; } /** @@ -477,7 +466,7 @@ class DbManager extends Manager { $query = new Query; $row = $query->from($this->itemTable) - ->where('name=:name', array(':name' => $name)) + ->where(array('name' => $name)) ->createCommand($this->db) ->queryRow(); @@ -501,20 +490,20 @@ class DbManager extends Manager $this->db->createCommand() ->update($this->itemChildTable, array( 'parent' => $item->getName(), - ), 'parent=:whereName', array( - ':whereName' => $oldName, + ), array( + 'parent' => $oldName, )); $this->db->createCommand() ->update($this->itemChildTable, array( 'child' => $item->getName(), - ), 'child=:whereName', array( - ':whereName' => $oldName, + ), array( + 'child' => $oldName, )); $this->db->createCommand() ->update($this->assignmentTable, array( 'itemname' => $item->getName(), - ), 'itemname=:whereName', array( - ':whereName' => $oldName, + ), array( + 'itemname' => $oldName, )); } @@ -525,8 +514,8 @@ class DbManager extends Manager 'description' => $item->getDescription(), 'bizrule' => $item->getBizRule(), 'data' => serialize($item->getData()), - ), 'name=:whereName', array( - ':whereName' => $oldName === null ? $item->getName() : $oldName, + ), array( + 'name' => $oldName === null ? $item->getName() : $oldName, )); } From 3fe23f83c75bc106e17885297de4be6cae3d61d2 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 13:50:17 +0400 Subject: [PATCH 10/14] Remove IManager interface --- framework/rbac/Assignment.php | 4 +- framework/rbac/IManager.php | 175 ------------------------------------------ framework/rbac/Item.php | 24 +++--- framework/rbac/Manager.php | 163 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 168 insertions(+), 198 deletions(-) delete mode 100644 framework/rbac/IManager.php diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php index e5d6157..5b6a607 100644 --- a/framework/rbac/Assignment.php +++ b/framework/rbac/Assignment.php @@ -14,7 +14,7 @@ use yii\base\Object; * Assignment represents an assignment of a role to a user. * It includes additional assignment information such as [[bizRule]] and [[data]]. * Do not create a Assignment instance using the 'new' operator. - * Instead, call [[IManager::assign]]. + * Instead, call [[Manager::assign()]]. * * @property mixed $userId User ID (see [[User::id]]). * @property string $itemName The authorization item name. @@ -35,7 +35,7 @@ class Assignment extends Object /** * Constructor. - * @param IManager $auth the authorization manager + * @param Manager $auth the authorization manager * @param mixed $userId user ID (see [[User::id]]) * @param string $itemName authorization item name * @param string $bizRule the business rule associated with this assignment diff --git a/framework/rbac/IManager.php b/framework/rbac/IManager.php deleted file mode 100644 index 0e9eea0..0000000 --- a/framework/rbac/IManager.php +++ /dev/null @@ -1,175 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -interface IManager -{ - /** - * Performs access check for the specified user. - * @param mixed $userId the user ID. This should be either an integer or a string representing - * the unique identifier of a user. See [[User::id]]. - * @param string $itemName the name of the operation that we are checking access to - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. - * @return boolean whether the operations can be performed by the user. - */ - public function checkAccess($userId, $itemName, $params = array()); - - /** - * Creates an authorization item. - * An authorization item represents an action permission (e.g. creating a post). - * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing - * by lower level items. - * @param string $name the item name. This must be a unique identifier. - * @param integer $type the item type (0: operation, 1: task, 2: role). - * @param string $description description of the item - * @param string $bizRule business rule associated with the item. This is a piece of - * PHP code that will be executed when [[checkAccess()]] is called for the item. - * @param mixed $data additional data associated with the item. - * @throws \yii\base\Exception if an item with the same name already exists - * @return Item the authorization item - */ - public function createItem($name, $type, $description = '', $bizRule = null, $data = null); - /** - * Removes the specified authorization item. - * @param string $name the name of the item to be removed - * @return boolean whether the item exists in the storage and has been removed - */ - public function removeItem($name); - /** - * Returns the authorization items of the specific type and user. - * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if - * they are not assigned to a user. - * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, - * meaning returning all items regardless of their type. - * @return Item[] the authorization items of the specific type. - */ - public function getItems($userId = null, $type = null); - /** - * Returns the authorization item with the specified name. - * @param string $name the name of the item - * @return Item the authorization item. Null if the item cannot be found. - */ - public function getItem($name); - /** - * Saves an authorization item to persistent storage. - * @param Item $item the item to be saved. - * @param string $oldName the old item name. If null, it means the item name is not changed. - */ - public function saveItem($item, $oldName = null); - - /** - * Adds an item as a child of another item. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - */ - public function addItemChild($itemName, $childName); - /** - * Removes a child from its parent. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the removal is successful - */ - public function removeItemChild($itemName, $childName); - /** - * Returns a value indicating whether a child exists within a parent. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the child exists - */ - public function hasItemChild($itemName, $childName); - /** - * Returns the children of the specified item. - * @param mixed $itemName the parent item name. This can be either a string or an array. - * The latter represents a list of item names. - * @return Item[] all child items of the parent - */ - public function getItemChildren($itemName); - - /** - * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return Assignment the authorization assignment information. - * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user - */ - public function assign($userId, $itemName, $bizRule = null, $data = null); - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether removal is successful - */ - public function revoke($userId, $itemName); - /** - * Returns a value indicating whether the item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether the item has been assigned to the user. - */ - public function isAssigned($userId, $itemName); - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return Assignment the item assignment information. Null is returned if - * the item is not assigned to the user. - */ - public function getAssignment($userId, $itemName); - /** - * Returns the item assignments for the specified user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return Item[] the item assignment information for the user. An empty array will be - * returned if there is no item assigned to the user. - */ - public function getAssignments($userId); - /** - * Saves the changes to an authorization assignment. - * @param Assignment $assignment the assignment that has been changed. - */ - public function saveAssignment($assignment); - /** - * Removes all authorization data. - */ - public function clearAll(); - /** - * Removes all authorization assignments. - */ - public function clearAssignments(); - /** - * Saves authorization data into persistent storage. - * If any change is made to the authorization data, please make - * sure you call this method to save the changed data into persistent storage. - */ - public function save(); - /** - * Executes a business rule. - * A business rule is a piece of PHP code that will be executed when [[checkAccess()]] is called. - * @param string $bizRule the business rule to be executed. - * @param array $params additional parameters to be passed to the business rule when being executed. - * @param mixed $data additional data that is associated with the corresponding authorization item or assignment - * @return boolean whether the execution returns a true value. - * If the business rule is empty, it will also return true. - */ - public function executeBizRule($bizRule, $params, $data); -} diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php index 1297e41..ef56a53 100644 --- a/framework/rbac/Item.php +++ b/framework/rbac/Item.php @@ -18,7 +18,7 @@ use yii\base\Object; * A user may be assigned one or several authorization items (called [[Assignment]] assignments). * He can perform an operation only when it is among his assigned items. * - * @property IManager $authManager The authorization manager. + * @property Manager $authManager The authorization manager. * @property integer $type The authorization item type. This could be 0 (operation), 1 (task) or 2 (role). * @property string $name The item name. * @property string $description The item description. @@ -45,7 +45,7 @@ class Item extends Object /** * Constructor. - * @param IManager $auth authorization manager + * @param Manager $auth authorization manager * @param string $name authorization item name * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). * @param string $description the description @@ -65,7 +65,7 @@ class Item extends Object /** * Checks to see if the specified item is within the hierarchy starting from this item. * This method is expected to be internally used by the actual implementations - * of the [[IManager::checkAccess()]]. + * of the [[Manager::checkAccess()]]. * @param string $itemName the name of the item to be checked * @param array $params the parameters to be passed to business rule evaluation * @return boolean whether the specified item is within the hierarchy starting from this item. @@ -87,7 +87,7 @@ class Item extends Object } /** - * @return IManager the authorization manager + * @return Manager the authorization manager */ public function getManager() { @@ -184,7 +184,7 @@ class Item extends Object * @param string $name the name of the child item * @return boolean whether the item is added successfully * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - * @see IManager::addItemChild + * @see Manager::addItemChild */ public function addChild($name) { @@ -196,7 +196,7 @@ class Item extends Object * Note, the child item is not deleted. Only the parent-child relationship is removed. * @param string $name the child item name * @return boolean whether the removal is successful - * @see IManager::removeItemChild + * @see Manager::removeItemChild */ public function removeChild($name) { @@ -207,7 +207,7 @@ class Item extends Object * Returns a value indicating whether a child exists * @param string $name the child item name * @return boolean whether the child exists - * @see IManager::hasItemChild + * @see Manager::hasItemChild */ public function hasChild($name) { @@ -217,7 +217,7 @@ class Item extends Object /** * Returns the children of this item. * @return Item[] all child items of this item. - * @see IManager::getItemChildren + * @see Manager::getItemChildren */ public function getChildren() { @@ -232,7 +232,7 @@ class Item extends Object * @param mixed $data additional data associated with this assignment * @return Assignment the authorization assignment information. * @throws \yii\base\Exception if the item has already been assigned to the user - * @see IManager::assign + * @see Manager::assign */ public function assign($userId, $bizRule = null, $data = null) { @@ -243,7 +243,7 @@ class Item extends Object * Revokes an authorization assignment from a user. * @param mixed $userId the user ID (see [[User::id]]) * @return boolean whether removal is successful - * @see IManager::revoke + * @see Manager::revoke */ public function revoke($userId) { @@ -254,7 +254,7 @@ class Item extends Object * Returns a value indicating whether this item has been assigned to the user. * @param mixed $userId the user ID (see [[User::id]]) * @return boolean whether the item has been assigned to the user. - * @see IManager::isAssigned + * @see Manager::isAssigned */ public function isAssigned($userId) { @@ -266,7 +266,7 @@ class Item extends Object * @param mixed $userId the user ID (see [[User::id]]) * @return Assignment the item assignment information. Null is returned if * this item is not assigned to the user. - * @see IManager::getAssignment + * @see Manager::getAssignment */ public function getAssignment($userId) { diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php index ce36e60..5c3c4f6 100644 --- a/framework/rbac/Manager.php +++ b/framework/rbac/Manager.php @@ -29,7 +29,7 @@ use yii\base\InvalidParamException; * Using authorization manager consists of two aspects. First, the authorization hierarchy * and assignments have to be established. Manager and its child classes * provides APIs to accomplish this task. Developers may need to develop some GUI - * so that it is more intuitive to end-users. Second, developers call [[IManager::checkAccess()]] + * so that it is more intuitive to end-users. Second, developers call [[Manager::checkAccess()]] * at appropriate places in the application code to check if the current user * has the needed permission for an operation. * @@ -41,7 +41,7 @@ use yii\base\InvalidParamException; * @author Alexander Kochetov * @since 2.0 */ -abstract class Manager extends Component implements IManager +abstract class Manager extends Component { /** * @var boolean Enable error reporting for bizRules. @@ -62,7 +62,7 @@ abstract class Manager extends Component implements IManager /** * Creates a role. - * This is a shortcut method to [[IManager::createItem()]]. + * This is a shortcut method to [[Manager::createItem()]]. * @param string $name the item name * @param string $description the item description. * @param string $bizRule the business rule associated with this item @@ -76,7 +76,7 @@ abstract class Manager extends Component implements IManager /** * Creates a task. - * This is a shortcut method to [[IManager::createItem()]]. + * This is a shortcut method to [[Manager::createItem()]]. * @param string $name the item name * @param string $description the item description. * @param string $bizRule the business rule associated with this item @@ -90,7 +90,7 @@ abstract class Manager extends Component implements IManager /** * Creates an operation. - * This is a shortcut method to [[IManager::createItem()]]. + * This is a shortcut method to [[Manager::createItem()]]. * @param string $name the item name * @param string $description the item description. * @param string $bizRule the business rule associated with this item @@ -104,7 +104,7 @@ abstract class Manager extends Component implements IManager /** * Returns roles. - * This is a shortcut method to [[IManager::getItems()]]. + * This is a shortcut method to [[Manager::getItems()]]. * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user * will be returned. Otherwise, all roles will be returned. * @return Item[] roles (name=>AuthItem) @@ -116,7 +116,7 @@ abstract class Manager extends Component implements IManager /** * Returns tasks. - * This is a shortcut method to [[IManager::getItems()]]. + * This is a shortcut method to [[Manager::getItems()]]. * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user * will be returned. Otherwise, all tasks will be returned. * @return Item[] tasks (name=>AuthItem) @@ -128,7 +128,7 @@ abstract class Manager extends Component implements IManager /** * Returns operations. - * This is a shortcut method to [[IManager::getItems()]]. + * This is a shortcut method to [[Manager::getItems()]]. * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user * will be returned. Otherwise, all operations will be returned. * @return Item[] operations (name=>AuthItem) @@ -141,7 +141,7 @@ abstract class Manager extends Component implements IManager /** * Executes the specified business rule. * @param string $bizRule the business rule to be executed. - * @param array $params parameters passed to [[IManager::checkAccess()]]. + * @param array $params parameters passed to [[Manager::checkAccess()]]. * @param mixed $data additional data associated with the authorization item or assignment. * @return boolean whether the business rule returns true. * If the business rule is empty, it will still return true. @@ -164,4 +164,149 @@ abstract class Manager extends Component implements IManager throw new InvalidParamException("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); } } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that we are checking access to + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. + * @return boolean whether the operations can be performed by the user. + */ + abstract public function checkAccess($userId, $itemName, $params = array()); + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @throws \yii\base\Exception if an item with the same name already exists + * @return Item the authorization item + */ + abstract public function createItem($name, $type, $description = '', $bizRule = null, $data = null); + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + abstract public function removeItem($name); + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + abstract public function getItems($userId = null, $type = null); + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + abstract public function getItem($name); + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + abstract public function saveItem($item, $oldName = null); + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + */ + abstract public function addItemChild($itemName, $childName); + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + abstract public function removeItemChild($itemName, $childName); + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + abstract public function hasItemChild($itemName, $childName); + /** + * Returns the children of the specified item. + * @param mixed $itemName the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + abstract public function getItemChildren($itemName); + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user + */ + abstract public function assign($userId, $itemName, $bizRule = null, $data = null); + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + abstract public function revoke($userId, $itemName); + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + abstract public function isAssigned($userId, $itemName); + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + abstract public function getAssignment($userId, $itemName); + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Item[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + abstract public function getAssignments($userId); + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + abstract public function saveAssignment($assignment); + /** + * Removes all authorization data. + */ + abstract public function clearAll(); + /** + * Removes all authorization assignments. + */ + abstract public function clearAssignments(); + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + abstract public function save(); } From 0d90541084a0e9e0135c98a05946326126d0a053 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 14:59:11 +0400 Subject: [PATCH 11/14] Moved from framework to yii --- framework/rbac/Assignment.php | 106 -------- framework/rbac/DbManager.php | 573 ------------------------------------------ framework/rbac/Item.php | 275 -------------------- framework/rbac/Manager.php | 312 ----------------------- framework/rbac/PhpManager.php | 516 ------------------------------------- yii/rbac/Assignment.php | 106 ++++++++ yii/rbac/DbManager.php | 573 ++++++++++++++++++++++++++++++++++++++++++ yii/rbac/Item.php | 275 ++++++++++++++++++++ yii/rbac/Manager.php | 312 +++++++++++++++++++++++ yii/rbac/PhpManager.php | 516 +++++++++++++++++++++++++++++++++++++ 10 files changed, 1782 insertions(+), 1782 deletions(-) delete mode 100644 framework/rbac/Assignment.php delete mode 100644 framework/rbac/DbManager.php delete mode 100644 framework/rbac/Item.php delete mode 100644 framework/rbac/Manager.php delete mode 100644 framework/rbac/PhpManager.php create mode 100644 yii/rbac/Assignment.php create mode 100644 yii/rbac/DbManager.php create mode 100644 yii/rbac/Item.php create mode 100644 yii/rbac/Manager.php create mode 100644 yii/rbac/PhpManager.php diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php deleted file mode 100644 index 5b6a607..0000000 --- a/framework/rbac/Assignment.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -class Assignment extends Object -{ - private $_auth; - private $_userId; - private $_itemName; - private $_bizRule; - private $_data; - - /** - * Constructor. - * @param Manager $auth the authorization manager - * @param mixed $userId user ID (see [[User::id]]) - * @param string $itemName authorization item name - * @param string $bizRule the business rule associated with this assignment - * @param mixed $data additional data for this assignment - */ - public function __construct($auth, $userId, $itemName, $bizRule = null, $data = null) - { - $this->_auth = $auth; - $this->_userId = $userId; - $this->_itemName = $itemName; - $this->_bizRule = $bizRule; - $this->_data = $data; - } - - /** - * @return mixed user ID (see [[User::id]]) - */ - public function getUserId() - { - return $this->_userId; - } - - /** - * @return string the authorization item name - */ - public function getItemName() - { - return $this->_itemName; - } - - /** - * @return string the business rule associated with this assignment - */ - public function getBizRule() - { - return $this->_bizRule; - } - - /** - * @param string $value the business rule associated with this assignment - */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - $this->_auth->saveAssignment($this); - } - } - - /** - * @return mixed additional data for this assignment - */ - public function getData() - { - return $this->_data; - } - - /** - * @param mixed $value additional data for this assignment - */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - $this->_auth->saveAssignment($this); - } - } -} diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php deleted file mode 100644 index ea6e8ac..0000000 --- a/framework/rbac/DbManager.php +++ /dev/null @@ -1,573 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -class DbManager extends Manager -{ - /** - * @var Connection|string the DB connection object or the application component ID of the DB connection. - * After the DbManager object is created, if you want to change this property, you should only assign it - * with a DB connection object. - */ - public $db = 'db'; - /** - * @var string the name of the table storing authorization items. Defaults to 'tbl_auth_item'. - */ - public $itemTable = 'tbl_auth_item'; - /** - * @var string the name of the table storing authorization item hierarchy. Defaults to 'tbl_auth_item_child'. - */ - public $itemChildTable = 'tbl_auth_item_child'; - /** - * @var string the name of the table storing authorization item assignments. Defaults to 'tbl_auth_assignment'. - */ - public $assignmentTable = 'tbl_auth_assignment'; - - private $_usingSqlite; - - /** - * Initializes the application component. - * This method overrides the parent implementation by establishing the database connection. - */ - public function init() - { - if (is_string($this->db)) { - $this->db = Yii::$app->getComponent($this->db); - } - if (!$this->db instanceof Connection) { - throw new InvalidConfigException("DbManager::db must be either a DB connection instance or the application component ID of a DB connection."); - } - $this->_usingSqlite = !strncmp($this->db->getDriverName(), 'sqlite', 6); - parent::init(); - } - - /** - * Performs access check for the specified user. - * @param mixed $userId the user ID. This should can be either an integer or a string representing - * the unique identifier of a user. See [[User::id]]. - * @param string $itemName the name of the operation that need access check - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, - * which holds the value of `$userId`. - * @return boolean whether the operations can be performed by the user. - */ - public function checkAccess($userId, $itemName, $params = array()) - { - $assignments = $this->getAssignments($userId); - return $this->checkAccessRecursive($userId, $itemName, $params, $assignments); - } - - /** - * Performs access check for the specified user. - * This method is internally called by [[checkAccess()]]. - * @param mixed $userId the user ID. This should can be either an integer or a string representing - * the unique identifier of a user. See [[User::id]]. - * @param string $itemName the name of the operation that need access check - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, - * which holds the value of `$userId`. - * @param Assignment[] $assignments the assignments to the specified user - * @return boolean whether the operations can be performed by the user. - */ - protected function checkAccessRecursive($userId, $itemName, $params, $assignments) - { - if (($item = $this->getItem($itemName)) === null) { - return false; - } - Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); - if (!isset($params['userId'])) { - $params['userId'] = $userId; - } - if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { - if (in_array($itemName, $this->defaultRoles)) { - return true; - } - if (isset($assignments[$itemName])) { - $assignment = $assignments[$itemName]; - if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { - return true; - } - } - $query = new Query; - $parents = $query->select(array('parent')) - ->from($this->itemChildTable) - ->where(array('child' => $itemName)) - ->createCommand($this->db) - ->queryColumn(); - foreach ($parents as $parent) { - if ($this->checkAccessRecursive($userId, $parent, $params, $assignments)) { - return true; - } - } - } - return false; - } - - /** - * Adds an item as a child of another item. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the item is added successfully - * @throws Exception if either parent or child doesn't exist. - * @throws InvalidCallException if a loop has been detected. - */ - public function addItemChild($itemName, $childName) - { - if ($itemName === $childName) { - throw new Exception("Cannot add '$itemName' as a child of itself."); - } - $query = new Query; - $rows = $query->from($this->itemTable) - ->where(array('or', 'name=:name1', 'name=:name2'), array( - ':name1' => $itemName, - ':name2' => $childName - )) - ->createCommand($this->db) - ->queryAll(); - if (count($rows) == 2) { - if ($rows[0]['name'] === $itemName) { - $parentType = $rows[0]['type']; - $childType = $rows[1]['type']; - } else { - $childType = $rows[0]['type']; - $parentType = $rows[1]['type']; - } - $this->checkItemChildType($parentType, $childType); - if ($this->detectLoop($itemName, $childName)) { - throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); - } - $this->db->createCommand() - ->insert($this->itemChildTable, array( - 'parent' => $itemName, - 'child' => $childName, - )); - return true; - } else { - throw new Exception("Either '$itemName' or '$childName' does not exist."); - } - } - - /** - * Removes a child from its parent. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the removal is successful - */ - public function removeItemChild($itemName, $childName) - { - return $this->db->createCommand() - ->delete($this->itemChildTable, array( - 'parent' => $itemName, - 'child' => $childName - )) > 0; - } - - /** - * Returns a value indicating whether a child exists within a parent. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the child exists - */ - public function hasItemChild($itemName, $childName) - { - $query = new Query; - return $query->select(array('parent')) - ->from($this->itemChildTable) - ->where(array( - 'parent' => $itemName, - 'child' => $childName - )) - ->createCommand($this->db) - ->queryScalar() !== false; - } - - /** - * Returns the children of the specified item. - * @param mixed $names the parent item name. This can be either a string or an array. - * The latter represents a list of item names. - * @return Item[] all child items of the parent - */ - public function getItemChildren($names) - { - $query = new Query; - $rows = $query->select(array('name', 'type', 'description', 'bizrule', 'data')) - ->from(array( - $this->itemTable, - $this->itemChildTable - )) - ->where(array('parent'=>$names)) - ->andWhere('name=child') - ->createCommand($this->db) - ->queryAll(); - $children = array(); - foreach ($rows as $row) { - if (($data = @unserialize($row['data'])) === false) { - $data = null; - } - $children[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); - } - return $children; - } - - /** - * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return Assignment the authorization assignment information. - * @throws Exception if the item does not exist or if the item has already been assigned to the user - */ - public function assign($userId, $itemName, $bizRule = null, $data = null) - { - if ($this->usingSqlite() && $this->getItem($itemName) === null) { - throw new Exception("The item '$itemName' does not exist."); - } - $this->db->createCommand() - ->insert($this->assignmentTable, array( - 'userid' => $userId, - 'itemname' => $itemName, - 'bizrule' => $bizRule, - 'data' => serialize($data) - )); - return new Assignment($this, $userId, $itemName, $bizRule, $data); - } - - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether removal is successful - */ - public function revoke($userId, $itemName) - { - return $this->db->createCommand() - ->delete($this->assignmentTable, array( - 'userid' => $userId, - 'itemname' => $itemName - )) > 0; - } - - /** - * Returns a value indicating whether the item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether the item has been assigned to the user. - */ - public function isAssigned($itemName, $userId) - { - $query = new Query; - return $query->select(array('itemname')) - ->from($this->assignmentTable) - ->where(array( - 'userid' => $userId, - 'itemname' => $itemName - )) - ->createCommand($this->db) - ->queryScalar() !== false; - } - - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return Assignment the item assignment information. Null is returned if - * the item is not assigned to the user. - */ - public function getAssignment($userId, $itemName) - { - $query = new Query; - $row = $query->from($this->assignmentTable) - ->where(array( - 'userid' => $userId, - 'itemname' => $itemName - )) - ->createCommand($this->db) - ->queryRow(); - if ($row !== false) { - if (($data = @unserialize($row['data'])) === false) { - $data = null; - } - return new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); - } else { - return null; - } - } - - /** - * Returns the item assignments for the specified user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return Assignment[] the item assignment information for the user. An empty array will be - * returned if there is no item assigned to the user. - */ - public function getAssignments($userId) - { - $query = new Query; - $rows = $query->from($this->assignmentTable) - ->where(array('userid' => $userId)) - ->createCommand($this->db) - ->queryAll(); - $assignments = array(); - foreach ($rows as $row) { - if (($data = @unserialize($row['data'])) === false) { - $data = null; - } - $assignments[$row['itemname']] = new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); - } - return $assignments; - } - - /** - * Saves the changes to an authorization assignment. - * @param Assignment $assignment the assignment that has been changed. - */ - public function saveAssignment($assignment) - { - $this->db->createCommand() - ->update($this->assignmentTable, array( - 'bizrule' => $assignment->getBizRule(), - 'data' => serialize($assignment->getData()), - ), array( - 'userid' => $assignment->getUserId(), - 'itemname' => $assignment->getItemName() - )); - } - - /** - * Returns the authorization items of the specific type and user. - * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if - * they are not assigned to a user. - * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, - * meaning returning all items regardless of their type. - * @return Item[] the authorization items of the specific type. - */ - public function getItems($userId = null, $type = null) - { - $query = new Query; - if ($userId === null && $type === null) { - $command = $query->from($this->itemTable) - ->createCommand($this->db); - } elseif ($userId === null) { - $command = $query->from($this->itemTable) - ->where(array('type' => $type)) - ->createCommand($this->db); - } elseif ($type === null) { - $command = $query->select(array('name', 'type', 'description', 't1.bizrule', 't1.data')) - ->from(array( - $this->itemTable . ' t1', - $this->assignmentTable . ' t2' - )) - ->where(array('userid' => $userId)) - ->andWhere('name=itemname') - ->createCommand($this->db); - } else { - $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') - ->from(array( - $this->itemTable . ' t1', - $this->assignmentTable . ' t2' - )) - ->where(array( - 'userid' => $userId, - 'type' => $type - )) - ->andWhere('name=itemname') - ->createCommand($this->db); - } - $items = array(); - foreach ($command->queryAll() as $row) { - if (($data = @unserialize($row['data'])) === false) { - $data = null; - } - $items[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); - } - return $items; - } - - /** - * Creates an authorization item. - * An authorization item represents an action permission (e.g. creating a post). - * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing - * by lower level items. - * @param string $name the item name. This must be a unique identifier. - * @param integer $type the item type (0: operation, 1: task, 2: role). - * @param string $description description of the item - * @param string $bizRule business rule associated with the item. This is a piece of - * PHP code that will be executed when [[checkAccess()]] is called for the item. - * @param mixed $data additional data associated with the item. - * @return Item the authorization item - * @throws Exception if an item with the same name already exists - */ - public function createItem($name, $type, $description = '', $bizRule = null, $data = null) - { - $this->db->createCommand() - ->insert($this->itemTable, array( - 'name' => $name, - 'type' => $type, - 'description' => $description, - 'bizrule' => $bizRule, - 'data' => serialize($data) - )); - return new Item($this, $name, $type, $description, $bizRule, $data); - } - - /** - * Removes the specified authorization item. - * @param string $name the name of the item to be removed - * @return boolean whether the item exists in the storage and has been removed - */ - public function removeItem($name) - { - if ($this->usingSqlite()) { - $this->db->createCommand() - ->delete($this->itemChildTable, array('or', 'parent=:name1', 'child=:name2'), array( - ':name1' => $name, - ':name2' => $name - )); - $this->db->createCommand()->delete($this->assignmentTable, array('itemname' => $name)); - } - return $this->db->createCommand()->delete($this->itemTable, array('name' => $name)) > 0; - } - - /** - * Returns the authorization item with the specified name. - * @param string $name the name of the item - * @return Item the authorization item. Null if the item cannot be found. - */ - public function getItem($name) - { - $query = new Query; - $row = $query->from($this->itemTable) - ->where(array('name' => $name)) - ->createCommand($this->db) - ->queryRow(); - - if ($row !== false) { - if (($data = @unserialize($row['data'])) === false) { - $data = null; - } - return new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); - } else - return null; - } - - /** - * Saves an authorization item to persistent storage. - * @param Item $item the item to be saved. - * @param string $oldName the old item name. If null, it means the item name is not changed. - */ - public function saveItem($item, $oldName = null) - { - if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { - $this->db->createCommand() - ->update($this->itemChildTable, array( - 'parent' => $item->getName(), - ), array( - 'parent' => $oldName, - )); - $this->db->createCommand() - ->update($this->itemChildTable, array( - 'child' => $item->getName(), - ), array( - 'child' => $oldName, - )); - $this->db->createCommand() - ->update($this->assignmentTable, array( - 'itemname' => $item->getName(), - ), array( - 'itemname' => $oldName, - )); - } - - $this->db->createCommand() - ->update($this->itemTable, array( - 'name' => $item->getName(), - 'type' => $item->getType(), - 'description' => $item->getDescription(), - 'bizrule' => $item->getBizRule(), - 'data' => serialize($item->getData()), - ), array( - 'name' => $oldName === null ? $item->getName() : $oldName, - )); - } - - /** - * Saves the authorization data to persistent storage. - */ - public function save() - { - } - - /** - * Removes all authorization data. - */ - public function clearAll() - { - $this->clearAssignments(); - $this->db->createCommand()->delete($this->itemChildTable); - $this->db->createCommand()->delete($this->itemTable); - } - - /** - * Removes all authorization assignments. - */ - public function clearAssignments() - { - $this->db->createCommand()->delete($this->assignmentTable); - } - - /** - * Checks whether there is a loop in the authorization item hierarchy. - * @param string $itemName parent item name - * @param string $childName the name of the child item that is to be added to the hierarchy - * @return boolean whether a loop exists - */ - protected function detectLoop($itemName, $childName) - { - if ($childName === $itemName) { - return true; - } - foreach ($this->getItemChildren($childName) as $child) { - if ($this->detectLoop($itemName, $child->getName())) { - return true; - } - } - return false; - } - - /** - * @return boolean whether the database is a SQLite database - */ - protected function usingSqlite() - { - return $this->_usingSqlite; - } -} diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php deleted file mode 100644 index ef56a53..0000000 --- a/framework/rbac/Item.php +++ /dev/null @@ -1,275 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -class Item extends Object -{ - const TYPE_OPERATION = 0; - const TYPE_TASK = 1; - const TYPE_ROLE = 2; - - private $_auth; - private $_type; - private $_name; - private $_description; - private $_bizRule; - private $_data; - - /** - * Constructor. - * @param Manager $auth authorization manager - * @param string $name authorization item name - * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). - * @param string $description the description - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data for this item - */ - public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) - { - $this->_type = (int)$type; - $this->_auth = $auth; - $this->_name = $name; - $this->_description = $description; - $this->_bizRule = $bizRule; - $this->_data = $data; - } - - /** - * Checks to see if the specified item is within the hierarchy starting from this item. - * This method is expected to be internally used by the actual implementations - * of the [[Manager::checkAccess()]]. - * @param string $itemName the name of the item to be checked - * @param array $params the parameters to be passed to business rule evaluation - * @return boolean whether the specified item is within the hierarchy starting from this item. - */ - public function checkAccess($itemName, $params = array()) - { - Yii::trace('Checking permission: ' . $this->_name, __METHOD__); - if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { - if ($this->_name == $itemName) { - return true; - } - foreach ($this->_auth->getItemChildren($this->_name) as $item) { - if ($item->checkAccess($itemName, $params)) { - return true; - } - } - } - return false; - } - - /** - * @return Manager the authorization manager - */ - public function getManager() - { - return $this->_auth; - } - - /** - * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). - */ - public function getType() - { - return $this->_type; - } - - /** - * @return string the item name - */ - public function getName() - { - return $this->_name; - } - - /** - * @param string $value the item name - */ - public function setName($value) - { - if ($this->_name !== $value) { - $oldName = $this->_name; - $this->_name = $value; - $this->_auth->saveItem($this, $oldName); - } - } - - /** - * @return string the item description - */ - public function getDescription() - { - return $this->_description; - } - - /** - * @param string $value the item description - */ - public function setDescription($value) - { - if ($this->_description !== $value) { - $this->_description = $value; - $this->_auth->saveItem($this); - } - } - - /** - * @return string the business rule associated with this item - */ - public function getBizRule() - { - return $this->_bizRule; - } - - /** - * @param string $value the business rule associated with this item - */ - public function setBizRule($value) - { - if ($this->_bizRule !== $value) { - $this->_bizRule = $value; - $this->_auth->saveItem($this); - } - } - - /** - * @return mixed the additional data associated with this item - */ - public function getData() - { - return $this->_data; - } - - /** - * @param mixed $value the additional data associated with this item - */ - public function setData($value) - { - if ($this->_data !== $value) { - $this->_data = $value; - $this->_auth->saveItem($this); - } - } - - /** - * Adds a child item. - * @param string $name the name of the child item - * @return boolean whether the item is added successfully - * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - * @see Manager::addItemChild - */ - public function addChild($name) - { - return $this->_auth->addItemChild($this->_name, $name); - } - - /** - * Removes a child item. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $name the child item name - * @return boolean whether the removal is successful - * @see Manager::removeItemChild - */ - public function removeChild($name) - { - return $this->_auth->removeItemChild($this->_name, $name); - } - - /** - * Returns a value indicating whether a child exists - * @param string $name the child item name - * @return boolean whether the child exists - * @see Manager::hasItemChild - */ - public function hasChild($name) - { - return $this->_auth->hasItemChild($this->_name, $name); - } - - /** - * Returns the children of this item. - * @return Item[] all child items of this item. - * @see Manager::getItemChildren - */ - public function getChildren() - { - return $this->_auth->getItemChildren($this->_name); - } - - /** - * Assigns this item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return Assignment the authorization assignment information. - * @throws \yii\base\Exception if the item has already been assigned to the user - * @see Manager::assign - */ - public function assign($userId, $bizRule = null, $data = null) - { - return $this->_auth->assign($userId, $this->_name, $bizRule, $data); - } - - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return boolean whether removal is successful - * @see Manager::revoke - */ - public function revoke($userId) - { - return $this->_auth->revoke($userId, $this->_name); - } - - /** - * Returns a value indicating whether this item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return boolean whether the item has been assigned to the user. - * @see Manager::isAssigned - */ - public function isAssigned($userId) - { - return $this->_auth->isAssigned($userId, $this->_name); - } - - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @return Assignment the item assignment information. Null is returned if - * this item is not assigned to the user. - * @see Manager::getAssignment - */ - public function getAssignment($userId) - { - return $this->_auth->getAssignment($userId, $this->_name); - } -} diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php deleted file mode 100644 index 5c3c4f6..0000000 --- a/framework/rbac/Manager.php +++ /dev/null @@ -1,312 +0,0 @@ -Item). - * @property array $tasks Tasks (name=>Item). - * @property array $operations Operations (name=>Item). - * - * @author Qiang Xue - * @author Alexander Kochetov - * @since 2.0 - */ -abstract class Manager extends Component -{ - /** - * @var boolean Enable error reporting for bizRules. - */ - public $showErrors = false; - - /** - * @var array list of role names that are assigned to all users implicitly. - * These roles do not need to be explicitly assigned to any user. - * When calling [[checkAccess()]], these roles will be checked first. - * For performance reason, you should minimize the number of such roles. - * A typical usage of such roles is to define an 'authenticated' role and associate - * it with a biz rule which checks if the current user is authenticated. - * And then declare 'authenticated' in this property so that it can be applied to - * every authenticated user. - */ - public $defaultRoles = array(); - - /** - * Creates a role. - * This is a shortcut method to [[Manager::createItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return Item the authorization item - */ - public function createRole($name, $description = '', $bizRule = null, $data = null) - { - return $this->createItem($name, Item::TYPE_ROLE, $description, $bizRule, $data); - } - - /** - * Creates a task. - * This is a shortcut method to [[Manager::createItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return Item the authorization item - */ - public function createTask($name, $description = '', $bizRule = null, $data = null) - { - return $this->createItem($name, Item::TYPE_TASK, $description, $bizRule, $data); - } - - /** - * Creates an operation. - * This is a shortcut method to [[Manager::createItem()]]. - * @param string $name the item name - * @param string $description the item description. - * @param string $bizRule the business rule associated with this item - * @param mixed $data additional data to be passed when evaluating the business rule - * @return Item the authorization item - */ - public function createOperation($name, $description = '', $bizRule = null, $data = null) - { - return $this->createItem($name, Item::TYPE_OPERATION, $description, $bizRule, $data); - } - - /** - * Returns roles. - * This is a shortcut method to [[Manager::getItems()]]. - * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user - * will be returned. Otherwise, all roles will be returned. - * @return Item[] roles (name=>AuthItem) - */ - public function getRoles($userId = null) - { - return $this->getItems($userId, Item::TYPE_ROLE); - } - - /** - * Returns tasks. - * This is a shortcut method to [[Manager::getItems()]]. - * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user - * will be returned. Otherwise, all tasks will be returned. - * @return Item[] tasks (name=>AuthItem) - */ - public function getTasks($userId = null) - { - return $this->getItems($userId, Item::TYPE_TASK); - } - - /** - * Returns operations. - * This is a shortcut method to [[Manager::getItems()]]. - * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user - * will be returned. Otherwise, all operations will be returned. - * @return Item[] operations (name=>AuthItem) - */ - public function getOperations($userId = null) - { - return $this->getItems($userId, Item::TYPE_OPERATION); - } - - /** - * Executes the specified business rule. - * @param string $bizRule the business rule to be executed. - * @param array $params parameters passed to [[Manager::checkAccess()]]. - * @param mixed $data additional data associated with the authorization item or assignment. - * @return boolean whether the business rule returns true. - * If the business rule is empty, it will still return true. - */ - public function executeBizRule($bizRule, $params, $data) - { - return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); - } - - /** - * Checks the item types to make sure a child can be added to a parent. - * @param integer $parentType parent item type - * @param integer $childType child item type - * @throws InvalidParamException if the item cannot be added as a child due to its incompatible type. - */ - protected function checkItemChildType($parentType, $childType) - { - static $types = array('operation', 'task', 'role'); - if ($parentType < $childType) { - throw new InvalidParamException("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); - } - } - - /** - * Performs access check for the specified user. - * @param mixed $userId the user ID. This should be either an integer or a string representing - * the unique identifier of a user. See [[User::id]]. - * @param string $itemName the name of the operation that we are checking access to - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. - * @return boolean whether the operations can be performed by the user. - */ - abstract public function checkAccess($userId, $itemName, $params = array()); - - /** - * Creates an authorization item. - * An authorization item represents an action permission (e.g. creating a post). - * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing - * by lower level items. - * @param string $name the item name. This must be a unique identifier. - * @param integer $type the item type (0: operation, 1: task, 2: role). - * @param string $description description of the item - * @param string $bizRule business rule associated with the item. This is a piece of - * PHP code that will be executed when [[checkAccess()]] is called for the item. - * @param mixed $data additional data associated with the item. - * @throws \yii\base\Exception if an item with the same name already exists - * @return Item the authorization item - */ - abstract public function createItem($name, $type, $description = '', $bizRule = null, $data = null); - /** - * Removes the specified authorization item. - * @param string $name the name of the item to be removed - * @return boolean whether the item exists in the storage and has been removed - */ - abstract public function removeItem($name); - /** - * Returns the authorization items of the specific type and user. - * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if - * they are not assigned to a user. - * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, - * meaning returning all items regardless of their type. - * @return Item[] the authorization items of the specific type. - */ - abstract public function getItems($userId = null, $type = null); - /** - * Returns the authorization item with the specified name. - * @param string $name the name of the item - * @return Item the authorization item. Null if the item cannot be found. - */ - abstract public function getItem($name); - /** - * Saves an authorization item to persistent storage. - * @param Item $item the item to be saved. - * @param string $oldName the old item name. If null, it means the item name is not changed. - */ - abstract public function saveItem($item, $oldName = null); - - /** - * Adds an item as a child of another item. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. - */ - abstract public function addItemChild($itemName, $childName); - /** - * Removes a child from its parent. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the removal is successful - */ - abstract public function removeItemChild($itemName, $childName); - /** - * Returns a value indicating whether a child exists within a parent. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the child exists - */ - abstract public function hasItemChild($itemName, $childName); - /** - * Returns the children of the specified item. - * @param mixed $itemName the parent item name. This can be either a string or an array. - * The latter represents a list of item names. - * @return Item[] all child items of the parent - */ - abstract public function getItemChildren($itemName); - - /** - * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return Assignment the authorization assignment information. - * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user - */ - abstract public function assign($userId, $itemName, $bizRule = null, $data = null); - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether removal is successful - */ - abstract public function revoke($userId, $itemName); - /** - * Returns a value indicating whether the item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether the item has been assigned to the user. - */ - abstract public function isAssigned($userId, $itemName); - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return Assignment the item assignment information. Null is returned if - * the item is not assigned to the user. - */ - abstract public function getAssignment($userId, $itemName); - /** - * Returns the item assignments for the specified user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return Item[] the item assignment information for the user. An empty array will be - * returned if there is no item assigned to the user. - */ - abstract public function getAssignments($userId); - /** - * Saves the changes to an authorization assignment. - * @param Assignment $assignment the assignment that has been changed. - */ - abstract public function saveAssignment($assignment); - /** - * Removes all authorization data. - */ - abstract public function clearAll(); - /** - * Removes all authorization assignments. - */ - abstract public function clearAssignments(); - /** - * Saves authorization data into persistent storage. - * If any change is made to the authorization data, please make - * sure you call this method to save the changed data into persistent storage. - */ - abstract public function save(); -} diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php deleted file mode 100644 index 8a4dbec..0000000 --- a/framework/rbac/PhpManager.php +++ /dev/null @@ -1,516 +0,0 @@ - - * @author Alexander Kochetov - * @since 2.0 - */ -class PhpManager extends Manager -{ - /** - * @var string the path of the PHP script that contains the authorization data. - * If not set, it will be using 'protected/data/rbac.php' as the data file. - * Make sure this file is writable by the Web server process if the authorization - * needs to be changed. - * @see loadFromFile - * @see saveToFile - */ - public $authFile; - - private $_items = array(); // itemName => item - private $_children = array(); // itemName, childName => child - private $_assignments = array(); // userId, itemName => assignment - - /** - * Initializes the application component. - * This method overrides parent implementation by loading the authorization data - * from PHP script. - */ - public function init() - { - parent::init(); - if ($this->authFile === null) { - $this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; - } - $this->load(); - } - - /** - * Performs access check for the specified user. - * @param mixed $userId the user ID. This can be either an integer or a string representing - * @param string $itemName the name of the operation that need access check - * the unique identifier of a user. See [[User::id]]. - * @param array $params name-value pairs that would be passed to biz rules associated - * with the tasks and roles assigned to the user. A param with name 'userId' is added to - * this array, which holds the value of `$userId`. - * @return boolean whether the operations can be performed by the user. - */ - public function checkAccess($userId, $itemName, $params = array()) - { - if (!isset($this->_items[$itemName])) { - return false; - } - /** @var $item Item */ - $item = $this->_items[$itemName]; - Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); - if (!isset($params['userId'])) { - $params['userId'] = $userId; - } - if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { - if (in_array($itemName, $this->defaultRoles)) { - return true; - } - if (isset($this->_assignments[$userId][$itemName])) { - /** @var $assignment Assignment */ - $assignment = $this->_assignments[$userId][$itemName]; - if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { - return true; - } - } - foreach ($this->_children as $parentName => $children) { - if (isset($children[$itemName]) && $this->checkAccess($userId, $parentName, $params)) { - return true; - } - } - } - return false; - } - - /** - * Adds an item as a child of another item. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the item is added successfully - * @throws Exception if either parent or child doesn't exist. - * @throws InvalidCallException if item already has a child with $itemName or if a loop has been detected. - */ - public function addItemChild($itemName, $childName) - { - if (!isset($this->_items[$childName], $this->_items[$itemName])) { - throw new Exception("Either '$itemName' or '$childName' does not exist."); - } - /** @var $child Item */ - $child = $this->_items[$childName]; - /** @var $item Item */ - $item = $this->_items[$itemName]; - $this->checkItemChildType($item->getType(), $child->getType()); - if ($this->detectLoop($itemName, $childName)) { - throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); - } - if (isset($this->_children[$itemName][$childName])) { - throw new InvalidCallException("The item '$itemName' already has a child '$childName'."); - } - $this->_children[$itemName][$childName] = $this->_items[$childName]; - return true; - } - - /** - * Removes a child from its parent. - * Note, the child item is not deleted. Only the parent-child relationship is removed. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the removal is successful - */ - public function removeItemChild($itemName, $childName) - { - if (isset($this->_children[$itemName][$childName])) { - unset($this->_children[$itemName][$childName]); - return true; - } else { - return false; - } - } - - /** - * Returns a value indicating whether a child exists within a parent. - * @param string $itemName the parent item name - * @param string $childName the child item name - * @return boolean whether the child exists - */ - public function hasItemChild($itemName, $childName) - { - return isset($this->_children[$itemName][$childName]); - } - - /** - * Returns the children of the specified item. - * @param mixed $names the parent item name. This can be either a string or an array. - * The latter represents a list of item names. - * @return Item[] all child items of the parent - */ - public function getItemChildren($names) - { - if (is_string($names)) { - return isset($this->_children[$names]) ? $this->_children[$names] : array(); - } - - $children = array(); - foreach ($names as $name) { - if (isset($this->_children[$name])) { - $children = array_merge($children, $this->_children[$name]); - } - } - return $children; - } - - /** - * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called - * for this particular authorization item. - * @param mixed $data additional data associated with this assignment - * @return Assignment the authorization assignment information. - * @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user - */ - public function assign($userId, $itemName, $bizRule = null, $data = null) - { - if (!isset($this->_items[$itemName])) { - throw new InvalidParamException("Unknown authorization item '$itemName'."); - } elseif (isset($this->_assignments[$userId][$itemName])) { - throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'."); - } else { - return $this->_assignments[$userId][$itemName] = new Assignment($this, $userId, $itemName, $bizRule, $data); - } - } - - /** - * Revokes an authorization assignment from a user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether removal is successful - */ - public function revoke($userId, $itemName) - { - if (isset($this->_assignments[$userId][$itemName])) { - unset($this->_assignments[$userId][$itemName]); - return true; - } else { - return false; - } - } - - /** - * Returns a value indicating whether the item has been assigned to the user. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return boolean whether the item has been assigned to the user. - */ - public function isAssigned($userId, $itemName) - { - return isset($this->_assignments[$userId][$itemName]); - } - - /** - * Returns the item assignment information. - * @param mixed $userId the user ID (see [[User::id]]) - * @param string $itemName the item name - * @return Assignment the item assignment information. Null is returned if - * the item is not assigned to the user. - */ - public function getAssignment($userId, $itemName) - { - return isset($this->_assignments[$userId][$itemName]) ? $this->_assignments[$userId][$itemName] : null; - } - - /** - * Returns the item assignments for the specified user. - * @param mixed $userId the user ID (see [[User::id]]) - * @return Assignment[] the item assignment information for the user. An empty array will be - * returned if there is no item assigned to the user. - */ - public function getAssignments($userId) - { - return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : array(); - } - - /** - * Returns the authorization items of the specific type and user. - * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if - * they are not assigned to a user. - * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, - * meaning returning all items regardless of their type. - * @return Item[] the authorization items of the specific type. - */ - public function getItems($userId = null, $type = null) - { - if ($userId === null && $type === null) { - return $this->_items; - } - $items = array(); - if ($userId === null) { - foreach ($this->_items as $name => $item) { - /** @var $item Item */ - if ($item->getType() == $type) { - $items[$name] = $item; - } - } - } elseif (isset($this->_assignments[$userId])) { - foreach ($this->_assignments[$userId] as $assignment) { - /** @var $assignment Assignment */ - $name = $assignment->getItemName(); - if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->getType() == $type)) { - $items[$name] = $this->_items[$name]; - } - } - } - return $items; - } - - /** - * Creates an authorization item. - * An authorization item represents an action permission (e.g. creating a post). - * It has three types: operation, task and role. - * Authorization items form a hierarchy. Higher level items inheirt permissions representing - * by lower level items. - * @param string $name the item name. This must be a unique identifier. - * @param integer $type the item type (0: operation, 1: task, 2: role). - * @param string $description description of the item - * @param string $bizRule business rule associated with the item. This is a piece of - * PHP code that will be executed when [[checkAccess()]] is called for the item. - * @param mixed $data additional data associated with the item. - * @return Item the authorization item - * @throws Exception if an item with the same name already exists - */ - public function createItem($name, $type, $description = '', $bizRule = null, $data = null) - { - if (isset($this->_items[$name])) { - throw new Exception('Unable to add an item whose name is the same as an existing item.'); - } - return $this->_items[$name] = new Item($this, $name, $type, $description, $bizRule, $data); - } - - /** - * Removes the specified authorization item. - * @param string $name the name of the item to be removed - * @return boolean whether the item exists in the storage and has been removed - */ - public function removeItem($name) - { - if (isset($this->_items[$name])) { - foreach ($this->_children as &$children) { - unset($children[$name]); - } - foreach ($this->_assignments as &$assignments) { - unset($assignments[$name]); - } - unset($this->_items[$name]); - return true; - } else { - return false; - } - } - - /** - * Returns the authorization item with the specified name. - * @param string $name the name of the item - * @return Item the authorization item. Null if the item cannot be found. - */ - public function getItem($name) - { - return isset($this->_items[$name]) ? $this->_items[$name] : null; - } - - /** - * Saves an authorization item to persistent storage. - * @param Item $item the item to be saved. - * @param string $oldName the old item name. If null, it means the item name is not changed. - * @throws InvalidParamException if an item with the same name already taken - */ - public function saveItem($item, $oldName = null) - { - if ($oldName !== null && ($newName = $item->getName()) !== $oldName) // name changed - { - if (isset($this->_items[$newName])) { - throw new InvalidParamException("Unable to change the item name. The name '$newName' is already used by another item."); - } - if (isset($this->_items[$oldName]) && $this->_items[$oldName] === $item) { - unset($this->_items[$oldName]); - $this->_items[$newName] = $item; - if (isset($this->_children[$oldName])) { - $this->_children[$newName] = $this->_children[$oldName]; - unset($this->_children[$oldName]); - } - foreach ($this->_children as &$children) { - if (isset($children[$oldName])) { - $children[$newName] = $children[$oldName]; - unset($children[$oldName]); - } - } - foreach ($this->_assignments as &$assignments) { - if (isset($assignments[$oldName])) { - $assignments[$newName] = $assignments[$oldName]; - unset($assignments[$oldName]); - } - } - } - } - } - - /** - * Saves the changes to an authorization assignment. - * @param Assignment $assignment the assignment that has been changed. - */ - public function saveAssignment($assignment) - { - } - - /** - * Saves authorization data into persistent storage. - * If any change is made to the authorization data, please make - * sure you call this method to save the changed data into persistent storage. - */ - public function save() - { - $items = array(); - foreach ($this->_items as $name => $item) { - /** @var $item Item */ - $items[$name] = array( - 'type' => $item->getType(), - 'description' => $item->getDescription(), - 'bizRule' => $item->getBizRule(), - 'data' => $item->getData(), - ); - if (isset($this->_children[$name])) { - foreach ($this->_children[$name] as $child) { - /** @var $child Item */ - $items[$name]['children'][] = $child->getName(); - } - } - } - - foreach ($this->_assignments as $userId => $assignments) { - foreach ($assignments as $name => $assignment) { - /** @var $assignment Assignment */ - if (isset($items[$name])) { - $items[$name]['assignments'][$userId] = array( - 'bizRule' => $assignment->getBizRule(), - 'data' => $assignment->getData(), - ); - } - } - } - - $this->saveToFile($items, $this->authFile); - } - - /** - * Loads authorization data. - */ - public function load() - { - $this->clearAll(); - - $items = $this->loadFromFile($this->authFile); - - foreach ($items as $name => $item) { - $this->_items[$name] = new Item($this, $name, $item['type'], $item['description'], $item['bizRule'], $item['data']); - } - - foreach ($items as $name => $item) { - if (isset($item['children'])) { - foreach ($item['children'] as $childName) { - if (isset($this->_items[$childName])) { - $this->_children[$name][$childName] = $this->_items[$childName]; - } - } - } - if (isset($item['assignments'])) { - foreach ($item['assignments'] as $userId => $assignment) { - $this->_assignments[$userId][$name] = new Assignment($this, $name, $userId, $assignment['bizRule'], $assignment['data']); - } - } - } - } - - /** - * Removes all authorization data. - */ - public function clearAll() - { - $this->clearAssignments(); - $this->_children = array(); - $this->_items = array(); - } - - /** - * Removes all authorization assignments. - */ - public function clearAssignments() - { - $this->_assignments = array(); - } - - /** - * Checks whether there is a loop in the authorization item hierarchy. - * @param string $itemName parent item name - * @param string $childName the name of the child item that is to be added to the hierarchy - * @return boolean whether a loop exists - */ - protected function detectLoop($itemName, $childName) - { - if ($childName === $itemName) { - return true; - } - if (!isset($this->_children[$childName], $this->_items[$itemName])) { - return false; - } - foreach ($this->_children[$childName] as $child) { - /** @var $child Item */ - if ($this->detectLoop($itemName, $child->getName())) { - return true; - } - } - return false; - } - - /** - * Loads the authorization data from a PHP script file. - * @param string $file the file path. - * @return array the authorization data - * @see saveToFile - */ - protected function loadFromFile($file) - { - if (is_file($file)) { - return require($file); - } else { - return array(); - } - } - - /** - * Saves the authorization data to a PHP script file. - * @param array $data the authorization data - * @param string $file the file path. - * @see loadFromFile - */ - protected function saveToFile($data, $file) - { - file_put_contents($file, " + * @author Alexander Kochetov + * @since 2.0 + */ +class Assignment extends Object +{ + private $_auth; + private $_userId; + private $_itemName; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param Manager $auth the authorization manager + * @param mixed $userId user ID (see [[User::id]]) + * @param string $itemName authorization item name + * @param string $bizRule the business rule associated with this assignment + * @param mixed $data additional data for this assignment + */ + public function __construct($auth, $userId, $itemName, $bizRule = null, $data = null) + { + $this->_auth = $auth; + $this->_userId = $userId; + $this->_itemName = $itemName; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * @return mixed user ID (see [[User::id]]) + */ + public function getUserId() + { + return $this->_userId; + } + + /** + * @return string the authorization item name + */ + public function getItemName() + { + return $this->_itemName; + } + + /** + * @return string the business rule associated with this assignment + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this assignment + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveAssignment($this); + } + } + + /** + * @return mixed additional data for this assignment + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value additional data for this assignment + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveAssignment($this); + } + } +} diff --git a/yii/rbac/DbManager.php b/yii/rbac/DbManager.php new file mode 100644 index 0000000..ea6e8ac --- /dev/null +++ b/yii/rbac/DbManager.php @@ -0,0 +1,573 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class DbManager extends Manager +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbManager object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string the name of the table storing authorization items. Defaults to 'tbl_auth_item'. + */ + public $itemTable = 'tbl_auth_item'; + /** + * @var string the name of the table storing authorization item hierarchy. Defaults to 'tbl_auth_item_child'. + */ + public $itemChildTable = 'tbl_auth_item_child'; + /** + * @var string the name of the table storing authorization item assignments. Defaults to 'tbl_auth_assignment'. + */ + public $assignmentTable = 'tbl_auth_assignment'; + + private $_usingSqlite; + + /** + * Initializes the application component. + * This method overrides the parent implementation by establishing the database connection. + */ + public function init() + { + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("DbManager::db must be either a DB connection instance or the application component ID of a DB connection."); + } + $this->_usingSqlite = !strncmp($this->db->getDriverName(), 'sqlite', 6); + parent::init(); + } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should can be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that need access check + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, + * which holds the value of `$userId`. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()) + { + $assignments = $this->getAssignments($userId); + return $this->checkAccessRecursive($userId, $itemName, $params, $assignments); + } + + /** + * Performs access check for the specified user. + * This method is internally called by [[checkAccess()]]. + * @param mixed $userId the user ID. This should can be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that need access check + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to this array, + * which holds the value of `$userId`. + * @param Assignment[] $assignments the assignments to the specified user + * @return boolean whether the operations can be performed by the user. + */ + protected function checkAccessRecursive($userId, $itemName, $params, $assignments) + { + if (($item = $this->getItem($itemName)) === null) { + return false; + } + Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); + if (!isset($params['userId'])) { + $params['userId'] = $userId; + } + if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if (in_array($itemName, $this->defaultRoles)) { + return true; + } + if (isset($assignments[$itemName])) { + $assignment = $assignments[$itemName]; + if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + return true; + } + } + $query = new Query; + $parents = $query->select(array('parent')) + ->from($this->itemChildTable) + ->where(array('child' => $itemName)) + ->createCommand($this->db) + ->queryColumn(); + foreach ($parents as $parent) { + if ($this->checkAccessRecursive($userId, $parent, $params, $assignments)) { + return true; + } + } + } + return false; + } + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the item is added successfully + * @throws Exception if either parent or child doesn't exist. + * @throws InvalidCallException if a loop has been detected. + */ + public function addItemChild($itemName, $childName) + { + if ($itemName === $childName) { + throw new Exception("Cannot add '$itemName' as a child of itself."); + } + $query = new Query; + $rows = $query->from($this->itemTable) + ->where(array('or', 'name=:name1', 'name=:name2'), array( + ':name1' => $itemName, + ':name2' => $childName + )) + ->createCommand($this->db) + ->queryAll(); + if (count($rows) == 2) { + if ($rows[0]['name'] === $itemName) { + $parentType = $rows[0]['type']; + $childType = $rows[1]['type']; + } else { + $childType = $rows[0]['type']; + $parentType = $rows[1]['type']; + } + $this->checkItemChildType($parentType, $childType); + if ($this->detectLoop($itemName, $childName)) { + throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + } + $this->db->createCommand() + ->insert($this->itemChildTable, array( + 'parent' => $itemName, + 'child' => $childName, + )); + return true; + } else { + throw new Exception("Either '$itemName' or '$childName' does not exist."); + } + } + + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName) + { + return $this->db->createCommand() + ->delete($this->itemChildTable, array( + 'parent' => $itemName, + 'child' => $childName + )) > 0; + } + + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName) + { + $query = new Query; + return $query->select(array('parent')) + ->from($this->itemChildTable) + ->where(array( + 'parent' => $itemName, + 'child' => $childName + )) + ->createCommand($this->db) + ->queryScalar() !== false; + } + + /** + * Returns the children of the specified item. + * @param mixed $names the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + public function getItemChildren($names) + { + $query = new Query; + $rows = $query->select(array('name', 'type', 'description', 'bizrule', 'data')) + ->from(array( + $this->itemTable, + $this->itemChildTable + )) + ->where(array('parent'=>$names)) + ->andWhere('name=child') + ->createCommand($this->db) + ->queryAll(); + $children = array(); + foreach ($rows as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $children[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } + return $children; + } + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws Exception if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null) + { + if ($this->usingSqlite() && $this->getItem($itemName) === null) { + throw new Exception("The item '$itemName' does not exist."); + } + $this->db->createCommand() + ->insert($this->assignmentTable, array( + 'userid' => $userId, + 'itemname' => $itemName, + 'bizrule' => $bizRule, + 'data' => serialize($data) + )); + return new Assignment($this, $userId, $itemName, $bizRule, $data); + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName) + { + return $this->db->createCommand() + ->delete($this->assignmentTable, array( + 'userid' => $userId, + 'itemname' => $itemName + )) > 0; + } + + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($itemName, $userId) + { + $query = new Query; + return $query->select(array('itemname')) + ->from($this->assignmentTable) + ->where(array( + 'userid' => $userId, + 'itemname' => $itemName + )) + ->createCommand($this->db) + ->queryScalar() !== false; + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAssignment($userId, $itemName) + { + $query = new Query; + $row = $query->from($this->assignmentTable) + ->where(array( + 'userid' => $userId, + 'itemname' => $itemName + )) + ->createCommand($this->db) + ->queryRow(); + if ($row !== false) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + return new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); + } else { + return null; + } + } + + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Assignment[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAssignments($userId) + { + $query = new Query; + $rows = $query->from($this->assignmentTable) + ->where(array('userid' => $userId)) + ->createCommand($this->db) + ->queryAll(); + $assignments = array(); + foreach ($rows as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $assignments[$row['itemname']] = new Assignment($this, $row['userid'], $row['itemname'], $row['bizrule'], $data); + } + return $assignments; + } + + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + public function saveAssignment($assignment) + { + $this->db->createCommand() + ->update($this->assignmentTable, array( + 'bizrule' => $assignment->getBizRule(), + 'data' => serialize($assignment->getData()), + ), array( + 'userid' => $assignment->getUserId(), + 'itemname' => $assignment->getItemName() + )); + } + + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + public function getItems($userId = null, $type = null) + { + $query = new Query; + if ($userId === null && $type === null) { + $command = $query->from($this->itemTable) + ->createCommand($this->db); + } elseif ($userId === null) { + $command = $query->from($this->itemTable) + ->where(array('type' => $type)) + ->createCommand($this->db); + } elseif ($type === null) { + $command = $query->select(array('name', 'type', 'description', 't1.bizrule', 't1.data')) + ->from(array( + $this->itemTable . ' t1', + $this->assignmentTable . ' t2' + )) + ->where(array('userid' => $userId)) + ->andWhere('name=itemname') + ->createCommand($this->db); + } else { + $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') + ->from(array( + $this->itemTable . ' t1', + $this->assignmentTable . ' t2' + )) + ->where(array( + 'userid' => $userId, + 'type' => $type + )) + ->andWhere('name=itemname') + ->createCommand($this->db); + } + $items = array(); + foreach ($command->queryAll() as $row) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + $items[$row['name']] = new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } + return $items; + } + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @return Item the authorization item + * @throws Exception if an item with the same name already exists + */ + public function createItem($name, $type, $description = '', $bizRule = null, $data = null) + { + $this->db->createCommand() + ->insert($this->itemTable, array( + 'name' => $name, + 'type' => $type, + 'description' => $description, + 'bizrule' => $bizRule, + 'data' => serialize($data) + )); + return new Item($this, $name, $type, $description, $bizRule, $data); + } + + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeItem($name) + { + if ($this->usingSqlite()) { + $this->db->createCommand() + ->delete($this->itemChildTable, array('or', 'parent=:name1', 'child=:name2'), array( + ':name1' => $name, + ':name2' => $name + )); + $this->db->createCommand()->delete($this->assignmentTable, array('itemname' => $name)); + } + return $this->db->createCommand()->delete($this->itemTable, array('name' => $name)) > 0; + } + + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + public function getItem($name) + { + $query = new Query; + $row = $query->from($this->itemTable) + ->where(array('name' => $name)) + ->createCommand($this->db) + ->queryRow(); + + if ($row !== false) { + if (($data = @unserialize($row['data'])) === false) { + $data = null; + } + return new Item($this, $row['name'], $row['type'], $row['description'], $row['bizrule'], $data); + } else + return null; + } + + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + public function saveItem($item, $oldName = null) + { + if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { + $this->db->createCommand() + ->update($this->itemChildTable, array( + 'parent' => $item->getName(), + ), array( + 'parent' => $oldName, + )); + $this->db->createCommand() + ->update($this->itemChildTable, array( + 'child' => $item->getName(), + ), array( + 'child' => $oldName, + )); + $this->db->createCommand() + ->update($this->assignmentTable, array( + 'itemname' => $item->getName(), + ), array( + 'itemname' => $oldName, + )); + } + + $this->db->createCommand() + ->update($this->itemTable, array( + 'name' => $item->getName(), + 'type' => $item->getType(), + 'description' => $item->getDescription(), + 'bizrule' => $item->getBizRule(), + 'data' => serialize($item->getData()), + ), array( + 'name' => $oldName === null ? $item->getName() : $oldName, + )); + } + + /** + * Saves the authorization data to persistent storage. + */ + public function save() + { + } + + /** + * Removes all authorization data. + */ + public function clearAll() + { + $this->clearAssignments(); + $this->db->createCommand()->delete($this->itemChildTable); + $this->db->createCommand()->delete($this->itemTable); + } + + /** + * Removes all authorization assignments. + */ + public function clearAssignments() + { + $this->db->createCommand()->delete($this->assignmentTable); + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * @param string $itemName parent item name + * @param string $childName the name of the child item that is to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($itemName, $childName) + { + if ($childName === $itemName) { + return true; + } + foreach ($this->getItemChildren($childName) as $child) { + if ($this->detectLoop($itemName, $child->getName())) { + return true; + } + } + return false; + } + + /** + * @return boolean whether the database is a SQLite database + */ + protected function usingSqlite() + { + return $this->_usingSqlite; + } +} diff --git a/yii/rbac/Item.php b/yii/rbac/Item.php new file mode 100644 index 0000000..ef56a53 --- /dev/null +++ b/yii/rbac/Item.php @@ -0,0 +1,275 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class Item extends Object +{ + const TYPE_OPERATION = 0; + const TYPE_TASK = 1; + const TYPE_ROLE = 2; + + private $_auth; + private $_type; + private $_name; + private $_description; + private $_bizRule; + private $_data; + + /** + * Constructor. + * @param Manager $auth authorization manager + * @param string $name authorization item name + * @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role). + * @param string $description the description + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data for this item + */ + public function __construct($auth, $name, $type, $description = '', $bizRule = null, $data = null) + { + $this->_type = (int)$type; + $this->_auth = $auth; + $this->_name = $name; + $this->_description = $description; + $this->_bizRule = $bizRule; + $this->_data = $data; + } + + /** + * Checks to see if the specified item is within the hierarchy starting from this item. + * This method is expected to be internally used by the actual implementations + * of the [[Manager::checkAccess()]]. + * @param string $itemName the name of the item to be checked + * @param array $params the parameters to be passed to business rule evaluation + * @return boolean whether the specified item is within the hierarchy starting from this item. + */ + public function checkAccess($itemName, $params = array()) + { + Yii::trace('Checking permission: ' . $this->_name, __METHOD__); + if ($this->_auth->executeBizRule($this->_bizRule, $params, $this->_data)) { + if ($this->_name == $itemName) { + return true; + } + foreach ($this->_auth->getItemChildren($this->_name) as $item) { + if ($item->checkAccess($itemName, $params)) { + return true; + } + } + } + return false; + } + + /** + * @return Manager the authorization manager + */ + public function getManager() + { + return $this->_auth; + } + + /** + * @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role). + */ + public function getType() + { + return $this->_type; + } + + /** + * @return string the item name + */ + public function getName() + { + return $this->_name; + } + + /** + * @param string $value the item name + */ + public function setName($value) + { + if ($this->_name !== $value) { + $oldName = $this->_name; + $this->_name = $value; + $this->_auth->saveItem($this, $oldName); + } + } + + /** + * @return string the item description + */ + public function getDescription() + { + return $this->_description; + } + + /** + * @param string $value the item description + */ + public function setDescription($value) + { + if ($this->_description !== $value) { + $this->_description = $value; + $this->_auth->saveItem($this); + } + } + + /** + * @return string the business rule associated with this item + */ + public function getBizRule() + { + return $this->_bizRule; + } + + /** + * @param string $value the business rule associated with this item + */ + public function setBizRule($value) + { + if ($this->_bizRule !== $value) { + $this->_bizRule = $value; + $this->_auth->saveItem($this); + } + } + + /** + * @return mixed the additional data associated with this item + */ + public function getData() + { + return $this->_data; + } + + /** + * @param mixed $value the additional data associated with this item + */ + public function setData($value) + { + if ($this->_data !== $value) { + $this->_data = $value; + $this->_auth->saveItem($this); + } + } + + /** + * Adds a child item. + * @param string $name the name of the child item + * @return boolean whether the item is added successfully + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + * @see Manager::addItemChild + */ + public function addChild($name) + { + return $this->_auth->addItemChild($this->_name, $name); + } + + /** + * Removes a child item. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $name the child item name + * @return boolean whether the removal is successful + * @see Manager::removeItemChild + */ + public function removeChild($name) + { + return $this->_auth->removeItemChild($this->_name, $name); + } + + /** + * Returns a value indicating whether a child exists + * @param string $name the child item name + * @return boolean whether the child exists + * @see Manager::hasItemChild + */ + public function hasChild($name) + { + return $this->_auth->hasItemChild($this->_name, $name); + } + + /** + * Returns the children of this item. + * @return Item[] all child items of this item. + * @see Manager::getItemChildren + */ + public function getChildren() + { + return $this->_auth->getItemChildren($this->_name); + } + + /** + * Assigns this item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws \yii\base\Exception if the item has already been assigned to the user + * @see Manager::assign + */ + public function assign($userId, $bizRule = null, $data = null) + { + return $this->_auth->assign($userId, $this->_name, $bizRule, $data); + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether removal is successful + * @see Manager::revoke + */ + public function revoke($userId) + { + return $this->_auth->revoke($userId, $this->_name); + } + + /** + * Returns a value indicating whether this item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return boolean whether the item has been assigned to the user. + * @see Manager::isAssigned + */ + public function isAssigned($userId) + { + return $this->_auth->isAssigned($userId, $this->_name); + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Assignment the item assignment information. Null is returned if + * this item is not assigned to the user. + * @see Manager::getAssignment + */ + public function getAssignment($userId) + { + return $this->_auth->getAssignment($userId, $this->_name); + } +} diff --git a/yii/rbac/Manager.php b/yii/rbac/Manager.php new file mode 100644 index 0000000..5c3c4f6 --- /dev/null +++ b/yii/rbac/Manager.php @@ -0,0 +1,312 @@ +Item). + * @property array $tasks Tasks (name=>Item). + * @property array $operations Operations (name=>Item). + * + * @author Qiang Xue + * @author Alexander Kochetov + * @since 2.0 + */ +abstract class Manager extends Component +{ + /** + * @var boolean Enable error reporting for bizRules. + */ + public $showErrors = false; + + /** + * @var array list of role names that are assigned to all users implicitly. + * These roles do not need to be explicitly assigned to any user. + * When calling [[checkAccess()]], these roles will be checked first. + * For performance reason, you should minimize the number of such roles. + * A typical usage of such roles is to define an 'authenticated' role and associate + * it with a biz rule which checks if the current user is authenticated. + * And then declare 'authenticated' in this property so that it can be applied to + * every authenticated user. + */ + public $defaultRoles = array(); + + /** + * Creates a role. + * This is a shortcut method to [[Manager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createRole($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_ROLE, $description, $bizRule, $data); + } + + /** + * Creates a task. + * This is a shortcut method to [[Manager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createTask($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_TASK, $description, $bizRule, $data); + } + + /** + * Creates an operation. + * This is a shortcut method to [[Manager::createItem()]]. + * @param string $name the item name + * @param string $description the item description. + * @param string $bizRule the business rule associated with this item + * @param mixed $data additional data to be passed when evaluating the business rule + * @return Item the authorization item + */ + public function createOperation($name, $description = '', $bizRule = null, $data = null) + { + return $this->createItem($name, Item::TYPE_OPERATION, $description, $bizRule, $data); + } + + /** + * Returns roles. + * This is a shortcut method to [[Manager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the roles directly assigned to the user + * will be returned. Otherwise, all roles will be returned. + * @return Item[] roles (name=>AuthItem) + */ + public function getRoles($userId = null) + { + return $this->getItems($userId, Item::TYPE_ROLE); + } + + /** + * Returns tasks. + * This is a shortcut method to [[Manager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user + * will be returned. Otherwise, all tasks will be returned. + * @return Item[] tasks (name=>AuthItem) + */ + public function getTasks($userId = null) + { + return $this->getItems($userId, Item::TYPE_TASK); + } + + /** + * Returns operations. + * This is a shortcut method to [[Manager::getItems()]]. + * @param mixed $userId the user ID. If not null, only the operations directly assigned to the user + * will be returned. Otherwise, all operations will be returned. + * @return Item[] operations (name=>AuthItem) + */ + public function getOperations($userId = null) + { + return $this->getItems($userId, Item::TYPE_OPERATION); + } + + /** + * Executes the specified business rule. + * @param string $bizRule the business rule to be executed. + * @param array $params parameters passed to [[Manager::checkAccess()]]. + * @param mixed $data additional data associated with the authorization item or assignment. + * @return boolean whether the business rule returns true. + * If the business rule is empty, it will still return true. + */ + public function executeBizRule($bizRule, $params, $data) + { + return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); + } + + /** + * Checks the item types to make sure a child can be added to a parent. + * @param integer $parentType parent item type + * @param integer $childType child item type + * @throws InvalidParamException if the item cannot be added as a child due to its incompatible type. + */ + protected function checkItemChildType($parentType, $childType) + { + static $types = array('operation', 'task', 'role'); + if ($parentType < $childType) { + throw new InvalidParamException("Cannot add an item of type '$types[$childType]' to an item of type '$types[$parentType]'."); + } + } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[User::id]]. + * @param string $itemName the name of the operation that we are checking access to + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. + * @return boolean whether the operations can be performed by the user. + */ + abstract public function checkAccess($userId, $itemName, $params = array()); + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @throws \yii\base\Exception if an item with the same name already exists + * @return Item the authorization item + */ + abstract public function createItem($name, $type, $description = '', $bizRule = null, $data = null); + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + abstract public function removeItem($name); + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + abstract public function getItems($userId = null, $type = null); + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + abstract public function getItem($name); + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + */ + abstract public function saveItem($item, $oldName = null); + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected. + */ + abstract public function addItemChild($itemName, $childName); + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + abstract public function removeItemChild($itemName, $childName); + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + abstract public function hasItemChild($itemName, $childName); + /** + * Returns the children of the specified item. + * @param mixed $itemName the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + abstract public function getItemChildren($itemName); + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user + */ + abstract public function assign($userId, $itemName, $bizRule = null, $data = null); + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + abstract public function revoke($userId, $itemName); + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + abstract public function isAssigned($userId, $itemName); + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + abstract public function getAssignment($userId, $itemName); + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Item[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + abstract public function getAssignments($userId); + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + abstract public function saveAssignment($assignment); + /** + * Removes all authorization data. + */ + abstract public function clearAll(); + /** + * Removes all authorization assignments. + */ + abstract public function clearAssignments(); + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + abstract public function save(); +} diff --git a/yii/rbac/PhpManager.php b/yii/rbac/PhpManager.php new file mode 100644 index 0000000..8a4dbec --- /dev/null +++ b/yii/rbac/PhpManager.php @@ -0,0 +1,516 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class PhpManager extends Manager +{ + /** + * @var string the path of the PHP script that contains the authorization data. + * If not set, it will be using 'protected/data/rbac.php' as the data file. + * Make sure this file is writable by the Web server process if the authorization + * needs to be changed. + * @see loadFromFile + * @see saveToFile + */ + public $authFile; + + private $_items = array(); // itemName => item + private $_children = array(); // itemName, childName => child + private $_assignments = array(); // userId, itemName => assignment + + /** + * Initializes the application component. + * This method overrides parent implementation by loading the authorization data + * from PHP script. + */ + public function init() + { + parent::init(); + if ($this->authFile === null) { + $this->authFile = Yii::getAlias('@app/data/rbac') . '.php'; + } + $this->load(); + } + + /** + * Performs access check for the specified user. + * @param mixed $userId the user ID. This can be either an integer or a string representing + * @param string $itemName the name of the operation that need access check + * the unique identifier of a user. See [[User::id]]. + * @param array $params name-value pairs that would be passed to biz rules associated + * with the tasks and roles assigned to the user. A param with name 'userId' is added to + * this array, which holds the value of `$userId`. + * @return boolean whether the operations can be performed by the user. + */ + public function checkAccess($userId, $itemName, $params = array()) + { + if (!isset($this->_items[$itemName])) { + return false; + } + /** @var $item Item */ + $item = $this->_items[$itemName]; + Yii::trace('Checking permission: ' . $item->getName(), __METHOD__); + if (!isset($params['userId'])) { + $params['userId'] = $userId; + } + if ($this->executeBizRule($item->getBizRule(), $params, $item->getData())) { + if (in_array($itemName, $this->defaultRoles)) { + return true; + } + if (isset($this->_assignments[$userId][$itemName])) { + /** @var $assignment Assignment */ + $assignment = $this->_assignments[$userId][$itemName]; + if ($this->executeBizRule($assignment->getBizRule(), $params, $assignment->getData())) { + return true; + } + } + foreach ($this->_children as $parentName => $children) { + if (isset($children[$itemName]) && $this->checkAccess($userId, $parentName, $params)) { + return true; + } + } + } + return false; + } + + /** + * Adds an item as a child of another item. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the item is added successfully + * @throws Exception if either parent or child doesn't exist. + * @throws InvalidCallException if item already has a child with $itemName or if a loop has been detected. + */ + public function addItemChild($itemName, $childName) + { + if (!isset($this->_items[$childName], $this->_items[$itemName])) { + throw new Exception("Either '$itemName' or '$childName' does not exist."); + } + /** @var $child Item */ + $child = $this->_items[$childName]; + /** @var $item Item */ + $item = $this->_items[$itemName]; + $this->checkItemChildType($item->getType(), $child->getType()); + if ($this->detectLoop($itemName, $childName)) { + throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected."); + } + if (isset($this->_children[$itemName][$childName])) { + throw new InvalidCallException("The item '$itemName' already has a child '$childName'."); + } + $this->_children[$itemName][$childName] = $this->_items[$childName]; + return true; + } + + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the removal is successful + */ + public function removeItemChild($itemName, $childName) + { + if (isset($this->_children[$itemName][$childName])) { + unset($this->_children[$itemName][$childName]); + return true; + } else { + return false; + } + } + + /** + * Returns a value indicating whether a child exists within a parent. + * @param string $itemName the parent item name + * @param string $childName the child item name + * @return boolean whether the child exists + */ + public function hasItemChild($itemName, $childName) + { + return isset($this->_children[$itemName][$childName]); + } + + /** + * Returns the children of the specified item. + * @param mixed $names the parent item name. This can be either a string or an array. + * The latter represents a list of item names. + * @return Item[] all child items of the parent + */ + public function getItemChildren($names) + { + if (is_string($names)) { + return isset($this->_children[$names]) ? $this->_children[$names] : array(); + } + + $children = array(); + foreach ($names as $name) { + if (isset($this->_children[$name])) { + $children = array_merge($children, $this->_children[$name]); + } + } + return $children; + } + + /** + * Assigns an authorization item to a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * for this particular authorization item. + * @param mixed $data additional data associated with this assignment + * @return Assignment the authorization assignment information. + * @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user + */ + public function assign($userId, $itemName, $bizRule = null, $data = null) + { + if (!isset($this->_items[$itemName])) { + throw new InvalidParamException("Unknown authorization item '$itemName'."); + } elseif (isset($this->_assignments[$userId][$itemName])) { + throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'."); + } else { + return $this->_assignments[$userId][$itemName] = new Assignment($this, $userId, $itemName, $bizRule, $data); + } + } + + /** + * Revokes an authorization assignment from a user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether removal is successful + */ + public function revoke($userId, $itemName) + { + if (isset($this->_assignments[$userId][$itemName])) { + unset($this->_assignments[$userId][$itemName]); + return true; + } else { + return false; + } + } + + /** + * Returns a value indicating whether the item has been assigned to the user. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return boolean whether the item has been assigned to the user. + */ + public function isAssigned($userId, $itemName) + { + return isset($this->_assignments[$userId][$itemName]); + } + + /** + * Returns the item assignment information. + * @param mixed $userId the user ID (see [[User::id]]) + * @param string $itemName the item name + * @return Assignment the item assignment information. Null is returned if + * the item is not assigned to the user. + */ + public function getAssignment($userId, $itemName) + { + return isset($this->_assignments[$userId][$itemName]) ? $this->_assignments[$userId][$itemName] : null; + } + + /** + * Returns the item assignments for the specified user. + * @param mixed $userId the user ID (see [[User::id]]) + * @return Assignment[] the item assignment information for the user. An empty array will be + * returned if there is no item assigned to the user. + */ + public function getAssignments($userId) + { + return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : array(); + } + + /** + * Returns the authorization items of the specific type and user. + * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if + * they are not assigned to a user. + * @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null, + * meaning returning all items regardless of their type. + * @return Item[] the authorization items of the specific type. + */ + public function getItems($userId = null, $type = null) + { + if ($userId === null && $type === null) { + return $this->_items; + } + $items = array(); + if ($userId === null) { + foreach ($this->_items as $name => $item) { + /** @var $item Item */ + if ($item->getType() == $type) { + $items[$name] = $item; + } + } + } elseif (isset($this->_assignments[$userId])) { + foreach ($this->_assignments[$userId] as $assignment) { + /** @var $assignment Assignment */ + $name = $assignment->getItemName(); + if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->getType() == $type)) { + $items[$name] = $this->_items[$name]; + } + } + } + return $items; + } + + /** + * Creates an authorization item. + * An authorization item represents an action permission (e.g. creating a post). + * It has three types: operation, task and role. + * Authorization items form a hierarchy. Higher level items inheirt permissions representing + * by lower level items. + * @param string $name the item name. This must be a unique identifier. + * @param integer $type the item type (0: operation, 1: task, 2: role). + * @param string $description description of the item + * @param string $bizRule business rule associated with the item. This is a piece of + * PHP code that will be executed when [[checkAccess()]] is called for the item. + * @param mixed $data additional data associated with the item. + * @return Item the authorization item + * @throws Exception if an item with the same name already exists + */ + public function createItem($name, $type, $description = '', $bizRule = null, $data = null) + { + if (isset($this->_items[$name])) { + throw new Exception('Unable to add an item whose name is the same as an existing item.'); + } + return $this->_items[$name] = new Item($this, $name, $type, $description, $bizRule, $data); + } + + /** + * Removes the specified authorization item. + * @param string $name the name of the item to be removed + * @return boolean whether the item exists in the storage and has been removed + */ + public function removeItem($name) + { + if (isset($this->_items[$name])) { + foreach ($this->_children as &$children) { + unset($children[$name]); + } + foreach ($this->_assignments as &$assignments) { + unset($assignments[$name]); + } + unset($this->_items[$name]); + return true; + } else { + return false; + } + } + + /** + * Returns the authorization item with the specified name. + * @param string $name the name of the item + * @return Item the authorization item. Null if the item cannot be found. + */ + public function getItem($name) + { + return isset($this->_items[$name]) ? $this->_items[$name] : null; + } + + /** + * Saves an authorization item to persistent storage. + * @param Item $item the item to be saved. + * @param string $oldName the old item name. If null, it means the item name is not changed. + * @throws InvalidParamException if an item with the same name already taken + */ + public function saveItem($item, $oldName = null) + { + if ($oldName !== null && ($newName = $item->getName()) !== $oldName) // name changed + { + if (isset($this->_items[$newName])) { + throw new InvalidParamException("Unable to change the item name. The name '$newName' is already used by another item."); + } + if (isset($this->_items[$oldName]) && $this->_items[$oldName] === $item) { + unset($this->_items[$oldName]); + $this->_items[$newName] = $item; + if (isset($this->_children[$oldName])) { + $this->_children[$newName] = $this->_children[$oldName]; + unset($this->_children[$oldName]); + } + foreach ($this->_children as &$children) { + if (isset($children[$oldName])) { + $children[$newName] = $children[$oldName]; + unset($children[$oldName]); + } + } + foreach ($this->_assignments as &$assignments) { + if (isset($assignments[$oldName])) { + $assignments[$newName] = $assignments[$oldName]; + unset($assignments[$oldName]); + } + } + } + } + } + + /** + * Saves the changes to an authorization assignment. + * @param Assignment $assignment the assignment that has been changed. + */ + public function saveAssignment($assignment) + { + } + + /** + * Saves authorization data into persistent storage. + * If any change is made to the authorization data, please make + * sure you call this method to save the changed data into persistent storage. + */ + public function save() + { + $items = array(); + foreach ($this->_items as $name => $item) { + /** @var $item Item */ + $items[$name] = array( + 'type' => $item->getType(), + 'description' => $item->getDescription(), + 'bizRule' => $item->getBizRule(), + 'data' => $item->getData(), + ); + if (isset($this->_children[$name])) { + foreach ($this->_children[$name] as $child) { + /** @var $child Item */ + $items[$name]['children'][] = $child->getName(); + } + } + } + + foreach ($this->_assignments as $userId => $assignments) { + foreach ($assignments as $name => $assignment) { + /** @var $assignment Assignment */ + if (isset($items[$name])) { + $items[$name]['assignments'][$userId] = array( + 'bizRule' => $assignment->getBizRule(), + 'data' => $assignment->getData(), + ); + } + } + } + + $this->saveToFile($items, $this->authFile); + } + + /** + * Loads authorization data. + */ + public function load() + { + $this->clearAll(); + + $items = $this->loadFromFile($this->authFile); + + foreach ($items as $name => $item) { + $this->_items[$name] = new Item($this, $name, $item['type'], $item['description'], $item['bizRule'], $item['data']); + } + + foreach ($items as $name => $item) { + if (isset($item['children'])) { + foreach ($item['children'] as $childName) { + if (isset($this->_items[$childName])) { + $this->_children[$name][$childName] = $this->_items[$childName]; + } + } + } + if (isset($item['assignments'])) { + foreach ($item['assignments'] as $userId => $assignment) { + $this->_assignments[$userId][$name] = new Assignment($this, $name, $userId, $assignment['bizRule'], $assignment['data']); + } + } + } + } + + /** + * Removes all authorization data. + */ + public function clearAll() + { + $this->clearAssignments(); + $this->_children = array(); + $this->_items = array(); + } + + /** + * Removes all authorization assignments. + */ + public function clearAssignments() + { + $this->_assignments = array(); + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * @param string $itemName parent item name + * @param string $childName the name of the child item that is to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($itemName, $childName) + { + if ($childName === $itemName) { + return true; + } + if (!isset($this->_children[$childName], $this->_items[$itemName])) { + return false; + } + foreach ($this->_children[$childName] as $child) { + /** @var $child Item */ + if ($this->detectLoop($itemName, $child->getName())) { + return true; + } + } + return false; + } + + /** + * Loads the authorization data from a PHP script file. + * @param string $file the file path. + * @return array the authorization data + * @see saveToFile + */ + protected function loadFromFile($file) + { + if (is_file($file)) { + return require($file); + } else { + return array(); + } + } + + /** + * Saves the authorization data to a PHP script file. + * @param array $data the authorization data + * @param string $file the file path. + * @see loadFromFile + */ + protected function saveToFile($data, $file) + { + file_put_contents($file, " Date: Fri, 10 May 2013 16:11:04 +0400 Subject: [PATCH 12/14] Unit tests --- tests/unit/framework/rbac/ManagerTestBase.php | 248 ++++++++++++++++++++++++++ tests/unit/framework/rbac/PhpManagerTest.php | 33 ++++ 2 files changed, 281 insertions(+) create mode 100644 tests/unit/framework/rbac/ManagerTestBase.php create mode 100644 tests/unit/framework/rbac/PhpManagerTest.php diff --git a/tests/unit/framework/rbac/ManagerTestBase.php b/tests/unit/framework/rbac/ManagerTestBase.php new file mode 100644 index 0000000..03d5354 --- /dev/null +++ b/tests/unit/framework/rbac/ManagerTestBase.php @@ -0,0 +1,248 @@ +auth->createItem($name, $type, $description, $bizRule, $data); + $this->assertTrue($item instanceof Item); + $this->assertEquals($item->type, $type); + $this->assertEquals($item->name, $name); + $this->assertEquals($item->description, $description); + $this->assertEquals($item->bizRule, $bizRule); + $this->assertEquals($item->data, $data); + + // test shortcut + $name2 = 'createUser'; + $item2 = $this->auth->createRole($name2, $description, $bizRule, $data); + $this->assertEquals($item2->type, Item::TYPE_ROLE); + + // test adding an item with the same name + $this->setExpectedException('Exception'); + $this->auth->createItem($name, $type, $description, $bizRule, $data); + } + + public function testGetItem() + { + $this->assertTrue($this->auth->getItem('readPost') instanceof Item); + $this->assertTrue($this->auth->getItem('reader') instanceof Item); + $this->assertNull($this->auth->getItem('unknown')); + } + + public function testRemoveAuthItem() + { + $this->assertTrue($this->auth->getItem('updatePost') instanceof Item); + $this->assertTrue($this->auth->removeItem('updatePost')); + $this->assertNull($this->auth->getItem('updatePost')); + $this->assertFalse($this->auth->removeItem('updatePost')); + } + + public function testChangeItemName() + { + $item = $this->auth->getItem('readPost'); + $this->assertTrue($item instanceof Item); + $this->assertTrue($this->auth->hasItemChild('reader', 'readPost')); + $item->name = 'readPost2'; + $this->assertNull($this->auth->getItem('readPost')); + $this->assertEquals($this->auth->getItem('readPost2'), $item); + $this->assertFalse($this->auth->hasItemChild('reader', 'readPost')); + $this->assertTrue($this->auth->hasItemChild('reader', 'readPost2')); + } + + public function testAddItemChild() + { + $this->auth->addItemChild('createPost', 'updatePost'); + + // test adding upper level item to lower one + $this->setExpectedException('Exception'); + $this->auth->addItemChild('readPost', 'reader'); + } + + public function testAddItemChild2() + { + // test adding inexistent items + $this->setExpectedException('Exception'); + $this->assertFalse($this->auth->addItemChild('createPost2', 'updatePost')); + } + + public function testRemoveItemChild() + { + $this->assertTrue($this->auth->hasItemChild('reader', 'readPost')); + $this->assertTrue($this->auth->removeItemChild('reader', 'readPost')); + $this->assertFalse($this->auth->hasItemChild('reader', 'readPost')); + $this->assertFalse($this->auth->removeItemChild('reader', 'readPost')); + } + + public function testGetItemChildren() + { + $this->assertEquals(array(), $this->auth->getItemChildren('readPost')); + $children = $this->auth->getItemChildren('author'); + $this->assertEquals(3, count($children)); + $this->assertTrue(reset($children) instanceof Item); + } + + public function testAssign() + { + $auth = $this->auth->assign('new user', 'createPost', 'rule', 'data'); + $this->assertTrue($auth instanceof Assignment); + $this->assertEquals($auth->userId, 'new user'); + $this->assertEquals($auth->itemName, 'createPost'); + $this->assertEquals($auth->bizRule, 'rule'); + $this->assertEquals($auth->data, 'data'); + + $this->setExpectedException('Exception'); + $this->auth->assign('new user', 'createPost2', 'rule', 'data'); + } + + public function testRevoke() + { + $this->assertTrue($this->auth->isAssigned('author B', 'author')); + $auth = $this->auth->getAssignment('author B', 'author'); + $this->assertTrue($auth instanceof Assignment); + $this->assertTrue($this->auth->revoke('author B', 'author')); + $this->assertFalse($this->auth->isAssigned('author B', 'author')); + $this->assertFalse($this->auth->revoke('author B', 'author')); + } + + public function testGetAssignments() + { + $this->auth->assign('author B', 'deletePost'); + $auths = $this->auth->getAssignments('author B'); + $this->assertEquals(2, count($auths)); + $this->assertTrue(reset($auths) instanceof Assignment); + } + + public function testGetItems() + { + $this->assertEquals(count($this->auth->getRoles()), 4); + $this->assertEquals(count($this->auth->getOperations()), 4); + $this->assertEquals(count($this->auth->getTasks()), 1); + $this->assertEquals(count($this->auth->getItems()), 9); + + $this->assertEquals(count($this->auth->getItems('author B', null)), 1); + $this->assertEquals(count($this->auth->getItems('author C', null)), 0); + $this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_ROLE)), 1); + $this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_OPERATION)), 0); + } + + public function testClearAll() + { + $this->auth->clearAll(); + $this->assertEquals(count($this->auth->getRoles()), 0); + $this->assertEquals(count($this->auth->getOperations()), 0); + $this->assertEquals(count($this->auth->getTasks()), 0); + $this->assertEquals(count($this->auth->getItems()), 0); + $this->assertEquals(count($this->auth->getAssignments('author B')), 0); + } + + public function testClearAssignments() + { + $this->auth->clearAssignments(); + $this->assertEquals(count($this->auth->getAssignments('author B')), 0); + } + + public function testDetectLoop() + { + $this->setExpectedException('Exception'); + $this->auth->addItemChild('readPost', 'readPost'); + } + + public function testExecuteBizRule() + { + $this->assertTrue($this->auth->executeBizRule(null, array(), null)); + $this->assertTrue($this->auth->executeBizRule('return 1==true;', array(), null)); + $this->assertTrue($this->auth->executeBizRule('return $params[0]==$params[1];', array(1, '1'), null)); + $this->assertFalse($this->auth->executeBizRule('invalid', array(), null)); + } + + public function testCheckAccess() + { + $results = array( + 'reader A' => array( + 'createPost' => false, + 'readPost' => true, + 'updatePost' => false, + 'updateOwnPost' => false, + 'deletePost' => false, + ), + 'author B' => array( + 'createPost' => true, + 'readPost' => true, + 'updatePost' => true, + 'updateOwnPost' => true, + 'deletePost' => false, + ), + 'editor C' => array( + 'createPost' => false, + 'readPost' => true, + 'updatePost' => true, + 'updateOwnPost' => false, + 'deletePost' => false, + ), + 'admin D' => array( + 'createPost' => true, + 'readPost' => true, + 'updatePost' => true, + 'updateOwnPost' => false, + 'deletePost' => true, + ), + ); + + $params = array('authorID' => 'author B'); + + foreach (array('reader A', 'author B', 'editor C', 'admin D') as $user) { + $params['userID'] = $user; + foreach (array('createPost', 'readPost', 'updatePost', 'updateOwnPost', 'deletePost') as $operation) { + $result = $this->auth->checkAccess($user, $operation, $params); + $this->assertEquals($results[$user][$operation], $result); + } + } + } + + protected function prepareData() + { + $this->auth->createOperation('createPost', 'create a post'); + $this->auth->createOperation('readPost', 'read a post'); + $this->auth->createOperation('updatePost', 'update a post'); + $this->auth->createOperation('deletePost', 'delete a post'); + + $task = $this->auth->createTask('updateOwnPost', 'update a post by author himself', 'return $params["authorID"]==$params["userID"];'); + $task->addChild('updatePost'); + + $role = $this->auth->createRole('reader'); + $role->addChild('readPost'); + + $role = $this->auth->createRole('author'); + $role->addChild('reader'); + $role->addChild('createPost'); + $role->addChild('updateOwnPost'); + + $role = $this->auth->createRole('editor'); + $role->addChild('reader'); + $role->addChild('updatePost'); + + $role = $this->auth->createRole('admin'); + $role->addChild('editor'); + $role->addChild('author'); + $role->addChild('deletePost'); + + $this->auth->assign('reader A', 'reader'); + $this->auth->assign('author B', 'author'); + $this->auth->assign('editor C', 'editor'); + $this->auth->assign('admin D', 'admin'); + } +} diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php new file mode 100644 index 0000000..2a9fc3f --- /dev/null +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -0,0 +1,33 @@ +getRuntimePath() . '/rbac.php'; + @unlink($authFile); + $this->auth = new PhpManager; + $this->auth->authFile = $authFile; + $this->auth->init(); + $this->prepareData(); + } + + public function tearDown() + { + @unlink($this->auth->authFile); + } + + public function testSaveLoad() + { + $this->auth->save(); + $this->auth->clearAll(); + $this->auth->load(); + $this->testCheckAccess(); + } +} From 4b8bcd43e06e66cc1ee805843960bedb8a516460 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 16:18:01 +0400 Subject: [PATCH 13/14] Unit tests fix --- tests/unit/framework/rbac/PhpManagerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php index 2a9fc3f..69fdd41 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -2,6 +2,7 @@ namespace yiiunit\framework\rbac; +use Yii; use yii\rbac\PhpManager; require_once(__DIR__ . '/ManagerTestBase.php'); @@ -10,7 +11,7 @@ class PhpManagerTest extends ManagerTestBase { public function setUp() { - $authFile = \Yii::$app->getRuntimePath() . '/rbac.php'; + $authFile = Yii::$app->getRuntimePath() . '/rbac.php'; @unlink($authFile); $this->auth = new PhpManager; $this->auth->authFile = $authFile; From b9ea7e6fd8b1f6d88dbb786580e91999f351bc6d Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 16:32:28 +0400 Subject: [PATCH 14/14] DbManager code style fix + right Exception type fix --- yii/rbac/DbManager.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yii/rbac/DbManager.php b/yii/rbac/DbManager.php index ea6e8ac..386ce5a 100644 --- a/yii/rbac/DbManager.php +++ b/yii/rbac/DbManager.php @@ -10,9 +10,11 @@ namespace yii\rbac; use Yii; use yii\db\Connection; use yii\db\Query; +use yii\db\Expression; use yii\base\Exception; use yii\base\InvalidConfigException; use yii\base\InvalidCallException; +use yii\base\InvalidParamException; /** * DbManager represents an authorization manager that stores authorization information in database. @@ -222,8 +224,7 @@ class DbManager extends Manager $this->itemTable, $this->itemChildTable )) - ->where(array('parent'=>$names)) - ->andWhere('name=child') + ->where(array('parent' => $names, 'name' => new Expression('child'))) ->createCommand($this->db) ->queryAll(); $children = array(); @@ -244,12 +245,12 @@ class DbManager extends Manager * for this particular authorization item. * @param mixed $data additional data associated with this assignment * @return Assignment the authorization assignment information. - * @throws Exception if the item does not exist or if the item has already been assigned to the user + * @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user */ public function assign($userId, $itemName, $bizRule = null, $data = null) { if ($this->usingSqlite() && $this->getItem($itemName) === null) { - throw new Exception("The item '$itemName' does not exist."); + throw new InvalidParamException("The item '$itemName' does not exist."); } $this->db->createCommand() ->insert($this->assignmentTable, array( @@ -385,8 +386,7 @@ class DbManager extends Manager $this->itemTable . ' t1', $this->assignmentTable . ' t2' )) - ->where(array('userid' => $userId)) - ->andWhere('name=itemname') + ->where(array('userid' => $userId, 'name' => new Expression('itemname'))) ->createCommand($this->db); } else { $command = $query->select('name', 'type', 'description', 't1.bizrule', 't1.data') @@ -396,9 +396,9 @@ class DbManager extends Manager )) ->where(array( 'userid' => $userId, - 'type' => $type + 'type' => $type, + 'name' => new Expression('itemname'), )) - ->andWhere('name=itemname') ->createCommand($this->db); } $items = array();