From 0d90541084a0e9e0135c98a05946326126d0a053 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 10 May 2013 14:59:11 +0400 Subject: [PATCH] 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, "