diff --git a/framework/rbac/Assignment.php b/framework/rbac/Assignment.php index 1e21d37a55..1caab01f11 100644 --- a/framework/rbac/Assignment.php +++ b/framework/rbac/Assignment.php @@ -27,9 +27,9 @@ class Assignment extends Object */ public $manager; /** - * @var string the business rule associated with this assignment + * @var string name of the rule associated with this assignment */ - public $bizRule; + public $ruleName; /** * @var mixed additional data for this assignment */ diff --git a/framework/rbac/DbManager.php b/framework/rbac/DbManager.php index e6d0da4bf6..c7e1d64c84 100644 --- a/framework/rbac/DbManager.php +++ b/framework/rbac/DbManager.php @@ -38,19 +38,27 @@ class DbManager extends Manager * with a DB connection object. */ public $db = 'db'; + /** - * @var string the name of the table storing authorization items. Defaults to 'auth_item'. + * @var string the name of the table storing authorization items. Defaults to "auth_item". */ public $itemTable = '{{%auth_item}}'; + /** - * @var string the name of the table storing authorization item hierarchy. Defaults to 'auth_item_child'. + * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child". */ public $itemChildTable = '{{%auth_item_child}}'; + /** - * @var string the name of the table storing authorization item assignments. Defaults to 'auth_assignment'. + * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment". */ public $assignmentTable = '{{%auth_assignment}}'; + /** + * @var string the name of the table storing rules. Defaults to "auth_rule". + */ + public $ruleTable = '{{%auth_rule}}'; + private $_usingSqlite; /** @@ -102,13 +110,13 @@ class DbManager extends Manager if (!isset($params['userId'])) { $params['userId'] = $userId; } - if ($this->executeBizRule($item->bizRule, $params, $item->data)) { + if ($this->executeRule($item->ruleName, $params, $item->data)) { if (in_array($itemName, $this->defaultRoles)) { return true; } if (isset($assignments[$itemName])) { $assignment = $assignments[$itemName]; - if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { + if ($this->executeRule($assignment->bizRule, $params, $assignment->data)) { return true; } } @@ -233,15 +241,16 @@ class DbManager extends Manager /** * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[\yii\web\User::id]]) + * +*@param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * @param string $ruleName 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) + public function assign($userId, $itemName, $ruleName = null, $data = null) { if ($this->usingSqlite() && $this->getItem($itemName) === null) { throw new InvalidParamException("The item '$itemName' does not exist."); @@ -250,7 +259,7 @@ class DbManager extends Manager ->insert($this->assignmentTable, [ 'user_id' => $userId, 'item_name' => $itemName, - 'biz_rule' => $bizRule, + 'biz_rule' => $ruleName, 'data' => $data === null ? null : serialize($data), ]) ->execute(); @@ -259,7 +268,7 @@ class DbManager extends Manager 'manager' => $this, 'userId' => $userId, 'itemName' => $itemName, - 'bizRule' => $bizRule, + 'bizRule' => $ruleName, 'data' => $data, ]); } @@ -371,7 +380,7 @@ class DbManager extends Manager * Saves the changes to an authorization assignment. * @param Assignment $assignment the assignment that has been changed. */ - public function saveAssignment($assignment) + public function saveAssignment(Assignment $assignment) { $this->db->createCommand() ->update($this->assignmentTable, [ @@ -437,23 +446,24 @@ class DbManager extends Manager * 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 + * @param string $rule 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) + public function createItem($name, $type, $description = '', $rule = null, $data = null) { $this->db->createCommand() ->insert($this->itemTable, [ 'name' => $name, 'type' => $type, 'description' => $description, - 'biz_rule' => $bizRule, + 'biz_rule' => $rule, 'data' => $data === null ? null : serialize($data), ]) ->execute(); @@ -463,7 +473,7 @@ class DbManager extends Manager 'name' => $name, 'type' => $type, 'description' => $description, - 'bizRule' => $bizRule, + 'bizRule' => $rule, 'data' => $data, ]); } @@ -525,7 +535,7 @@ class DbManager extends Manager * @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) + public function saveItem(Item $item, $oldName = null) { if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) { $this->db->createCommand() @@ -544,7 +554,7 @@ class DbManager extends Manager 'name' => $item->getName(), 'type' => $item->type, 'description' => $item->description, - 'biz_rule' => $item->bizRule, + 'rule_name' => $item->ruleName, 'data' => $item->data === null ? null : serialize($item->data), ], [ 'name' => $oldName === null ? $item->getName() : $oldName, @@ -604,4 +614,45 @@ class DbManager extends Manager { return $this->_usingSqlite; } -} + + /** + * Removes the specified rule. + * + * @param string $name the name of the rule to be removed + * @return boolean whether the rule exists in the storage and has been removed + */ + public function removeRule($name) + { + // TODO: Implement removeRule() method. + } + + /** + * Saves the changes to the rule. + * + * @param Rule $rule the rule that has been changed. + */ + public function saveRule(Rule $rule) + { + // TODO: Implement saveRule() method. + } + + /** + * Returns rule given its name. + * + * @param string $name name of the rule. + * @return Rule + */ + public function getRule($name) + { + // TODO: Implement getRule() method. + } + + /** + * Returns all rules. + * + * @return Rule[] + */ + public function getRules() + { + // TODO: Implement getRules() method. + }} diff --git a/framework/rbac/Item.php b/framework/rbac/Item.php index 67e4951faa..bacef3d33f 100644 --- a/framework/rbac/Item.php +++ b/framework/rbac/Item.php @@ -40,9 +40,9 @@ class Item extends Object */ public $description; /** - * @var string the business rule associated with this item + * @var string name of the rule associated with this item */ - public $bizRule; + public $ruleName; /** * @var mixed the additional data associated with this item */ @@ -66,7 +66,7 @@ class Item extends Object public function checkAccess($itemName, $params = []) { Yii::trace('Checking permission: ' . $this->_name, __METHOD__); - if ($this->manager->executeBizRule($this->bizRule, $params, $this->data)) { + if ($this->manager->executeRule($this->ruleName, $params, $this->data)) { if ($this->_name == $itemName) { return true; } @@ -146,17 +146,18 @@ class Item extends Object /** * Assigns this item to a user. - * @param mixed $userId the user ID (see [[\yii\web\User::id]]) - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * +*@param mixed $userId the user ID (see [[\yii\web\User::id]]) + * @param Rule $rule the 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) + public function assign($userId, Rule $rule = null, $data = null) { - return $this->manager->assign($userId, $this->_name, $bizRule, $data); + return $this->manager->assign($userId, $this->_name, $rule, $data); } /** diff --git a/framework/rbac/Manager.php b/framework/rbac/Manager.php index d1798e3f23..1ab494f04a 100644 --- a/framework/rbac/Manager.php +++ b/framework/rbac/Manager.php @@ -43,18 +43,13 @@ use yii\base\InvalidParamException; */ 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. + * it with a 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. */ @@ -63,48 +58,52 @@ abstract class Manager extends Component /** * 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 string $ruleName name of the 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) + public function createRole($name, $description = '', $ruleName = null, $data = null) { - return $this->createItem($name, Item::TYPE_ROLE, $description, $bizRule, $data); + return $this->createItem($name, Item::TYPE_ROLE, $description, $ruleName, $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 string $ruleName name of the 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) + public function createTask($name, $description = '', $ruleName = null, $data = null) { - return $this->createItem($name, Item::TYPE_TASK, $description, $bizRule, $data); + return $this->createItem($name, Item::TYPE_TASK, $description, $ruleName, $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 string $ruleName name of the 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) + public function createOperation($name, $description = '', $ruleName = null, $data = null) { - return $this->createItem($name, Item::TYPE_OPERATION, $description, $bizRule, $data); + return $this->createItem($name, Item::TYPE_OPERATION, $description, $ruleName, $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) @@ -117,6 +116,7 @@ abstract class Manager extends Component /** * 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) @@ -129,6 +129,7 @@ abstract class Manager extends Component /** * 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) @@ -139,16 +140,21 @@ abstract class Manager extends Component } /** - * Executes the specified business rule. - * @param string $bizRule the business rule to be executed. + * Executes the specified rule. + * + * @param string $ruleName name of the 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. + * @return boolean whether the rule execution returns true. + * If the rule is empty, it will still return true. */ - public function executeBizRule($bizRule, $params, $data) + public function executeRule($ruleName, $params, $data) { - return $bizRule === '' || $bizRule === null || ($this->showErrors ? eval($bizRule) != 0 : @eval($bizRule) != 0); + $rule = $this->getRule($ruleName); + if ($rule) { + return $rule->execute($params, $data); + } + return true; } /** @@ -170,7 +176,7 @@ abstract class Manager extends Component * @param mixed $userId the user ID. This should be either an integer or a string representing * the unique identifier of a user. See [[\yii\web\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 + * @param array $params name-value pairs that would be passed to rules associated * with the tasks and roles assigned to the user. * @return boolean whether the operations can be performed by the user. */ @@ -182,22 +188,24 @@ abstract class Manager extends Component * 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 string $ruleName name of the rule associated with 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); + abstract public function createItem($name, $type, $description = '', $ruleName = 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 @@ -207,18 +215,20 @@ abstract class Manager extends Component * @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); + abstract public function saveItem(Item $item, $oldName = null); /** * Adds an item as a child of another item. @@ -227,6 +237,7 @@ abstract class Manager extends Component * @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. @@ -235,6 +246,7 @@ abstract class Manager extends Component * @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 @@ -242,6 +254,7 @@ abstract class Manager extends Component * @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. @@ -252,15 +265,17 @@ abstract class Manager extends Component /** * Assigns an authorization item to a user. + * * @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * @param string $ruleName name of the 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); + abstract public function assign($userId, $itemName, $ruleName = null, $data = null); + /** * Revokes an authorization assignment from a user. * @param mixed $userId the user ID (see [[\yii\web\User::id]]) @@ -268,12 +283,14 @@ abstract class Manager extends Component * @return boolean whether removal is successful */ abstract public function revoke($userId, $itemName); + /** * Revokes all authorization assignments from a user. * @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @return boolean whether removal is successful */ abstract public function revokeAll($userId); + /** * Returns a value indicating whether the item has been assigned to the user. * @param mixed $userId the user ID (see [[\yii\web\User::id]]) @@ -281,6 +298,7 @@ abstract class Manager extends Component * @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 [[\yii\web\User::id]]) @@ -296,19 +314,52 @@ abstract class Manager extends Component * returned if there is no item assigned to the user. */ abstract public function getAssignments($userId); + + /** + * Removes the specified rule. + * @param string $name the name of the rule to be removed + * @return boolean whether the rule exists in the storage and has been removed + */ + abstract public function removeRule($name); + + /** + * Saves the changes to the rule. + * + * @param Rule $rule the rule that has been changed. + */ + abstract public function saveRule(Rule $rule); + + /** + * Returns rule given its name. + * + * @param string $name name of the rule. + * @return Rule + */ + abstract public function getRule($name); + + /** + * Returns all rules. + * + * @return Rule[] + */ + abstract public function getRules(); + /** * Saves the changes to an authorization assignment. * @param Assignment $assignment the assignment that has been changed. */ - abstract public function saveAssignment($assignment); + abstract public function saveAssignment(Assignment $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 diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index b3a1b7a35c..9a9fb02d33 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -43,6 +43,7 @@ class PhpManager extends Manager private $_items = []; // itemName => item private $_children = []; // itemName, childName => child private $_assignments = []; // userId, itemName => assignment + private $_rules = []; // ruleName => rule /** @@ -62,7 +63,7 @@ class PhpManager extends Manager * @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 [[\yii\web\User::id]]. - * @param array $params name-value pairs that would be passed to biz rules associated + * @param array $params name-value pairs that would be passed to 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. @@ -78,14 +79,14 @@ class PhpManager extends Manager if (!isset($params['userId'])) { $params['userId'] = $userId; } - if ($this->executeBizRule($item->bizRule, $params, $item->data)) { + if ($this->executeRule($item->ruleName, $params, $item->data)) { 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->bizRule, $params, $assignment->data)) { + if ($this->executeRule($assignment->ruleName, $params, $assignment->data)) { return true; } } @@ -181,15 +182,16 @@ class PhpManager extends Manager /** * Assigns an authorization item to a user. - * @param mixed $userId the user ID (see [[\yii\web\User::id]]) + * +*@param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param string $itemName the item name - * @param string $bizRule the business rule to be executed when [[checkAccess()]] is called + * @param string $ruleName 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) + public function assign($userId, $itemName, $ruleName = null, $data = null) { if (!isset($this->_items[$itemName])) { throw new InvalidParamException("Unknown authorization item '$itemName'."); @@ -200,7 +202,7 @@ class PhpManager extends Manager 'manager' => $this, 'userId' => $userId, 'itemName' => $itemName, - 'bizRule' => $bizRule, + 'ruleName' => $ruleName, 'data' => $data, ]); } @@ -314,16 +316,17 @@ class PhpManager extends Manager * 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 + * @param string $rule 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) + public function createItem($name, $type, $description = '', $rule = 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.'); @@ -334,7 +337,7 @@ class PhpManager extends Manager 'name' => $name, 'type' => $type, 'description' => $description, - 'bizRule' => $bizRule, + 'ruleName' => $rule, 'data' => $data, ]); } @@ -377,7 +380,7 @@ class PhpManager extends Manager * @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) + public function saveItem(Item $item, $oldName = null) { if ($oldName !== null && ($newName = $item->getName()) !== $oldName) { // name changed if (isset($this->_items[$newName])) { @@ -410,7 +413,7 @@ class PhpManager extends Manager * Saves the changes to an authorization assignment. * @param Assignment $assignment the assignment that has been changed. */ - public function saveAssignment($assignment) + public function saveAssignment(Assignment $assignment) { } @@ -427,7 +430,7 @@ class PhpManager extends Manager $items[$name] = [ 'type' => $item->type, 'description' => $item->description, - 'bizRule' => $item->bizRule, + 'ruleName' => $item->ruleName, 'data' => $item->data, ]; if (isset($this->_children[$name])) { @@ -443,14 +446,19 @@ class PhpManager extends Manager /** @var Assignment $assignment */ if (isset($items[$name])) { $items[$name]['assignments'][$userId] = [ - 'bizRule' => $assignment->bizRule, + 'ruleName' => $assignment->ruleName, 'data' => $assignment->data, ]; } } } - $this->saveToFile($items, $this->authFile); + $rules = []; + foreach ($this->_rules as $name => $rule) { + $rules[$name] = serialize($rule); + } + + $this->saveToFile(['items' => $items, 'rules' => $rules], $this->authFile); } /** @@ -460,37 +468,45 @@ class PhpManager extends Manager { $this->clearAll(); - $items = $this->loadFromFile($this->authFile); + $data = $this->loadFromFile($this->authFile); - foreach ($items as $name => $item) { - $this->_items[$name] = new Item([ - 'manager' => $this, - 'name' => $name, - 'type' => $item['type'], - 'description' => $item['description'], - 'bizRule' => $item['bizRule'], - 'data' => $item['data'], - ]); - } + if (isset($data['items'])) { + foreach ($data['items'] as $name => $item) { + $this->_items[$name] = new Item([ + 'manager' => $this, + 'name' => $name, + 'type' => $item['type'], + 'description' => $item['description'], + 'ruleName' => $item['ruleName'], + 'data' => $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]; + foreach ($data['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([ + 'manager' => $this, + 'userId' => $userId, + 'itemName' => $name, + 'ruleName' => $assignment['ruleName'], + 'data' => $assignment['data'], + ]); } } } - if (isset($item['assignments'])) { - foreach ($item['assignments'] as $userId => $assignment) { - $this->_assignments[$userId][$name] = new Assignment([ - 'manager' => $this, - 'userId' => $userId, - 'itemName' => $name, - 'bizRule' => $assignment['bizRule'], - 'data' => $assignment['data'], - ]); - } + } + + if (isset($data['rules'])) { + foreach ($data['rules'] as $name => $ruleData) { + $this->_rules[$name] = unserialize($ruleData); } } } @@ -562,4 +578,56 @@ class PhpManager extends Manager { file_put_contents($file, "_rules[$name])) { + unset($this->_rules[$name]); + return true; + } else { + return false; + } + } + + + /** + * Saves the changes to the rule. + * + * @param Rule $rule the rule that has been changed. + */ + public function saveRule(Rule $rule) + { + $this->_rules[$rule->name] = $rule; + } + + /** + * Returns rule given its name. + * + * @param string $name name of the rule. + * @return Rule + */ + public function getRule($name) + { + if (!isset($this->_rules[$name])) { + return null; + } + + return $this->_rules[$name]; + } + + /** + * Returns all rules. + * + * @return Rule[] + */ + public function getRules() + { + return $this->_rules; + } } diff --git a/framework/rbac/Rule.php b/framework/rbac/Rule.php new file mode 100644 index 0000000000..f1d3de37a2 --- /dev/null +++ b/framework/rbac/Rule.php @@ -0,0 +1,42 @@ +name = $name; + } + parent::__construct($config); + } + + /** + * Executes the rule. + * + * @param array $params parameters passed to [[Manager::checkAccess()]]. + * @param mixed $data additional data associated with the authorization item or assignment. + * @return boolean whether the rule execution returns true. + */ + abstract public function execute($params, $data); +} diff --git a/framework/rbac/schema-mssql.sql b/framework/rbac/schema-mssql.sql index 14d97c5b8c..6a96975e13 100644 --- a/framework/rbac/schema-mssql.sql +++ b/framework/rbac/schema-mssql.sql @@ -12,15 +12,24 @@ drop table if exists [auth_assignment]; drop table if exists [auth_item_child]; drop table if exists [auth_item]; +drop table if exists [auth_rule]; + +create table [auth_rule] +( + [name] varchar(64) not null, + [data] text, + primary key ([name]) +); create table [auth_item] ( [name] varchar(64) not null, [type] integer not null, [description] text, - [biz_rule] text, + [rule_name] varchar(64), [data] text, primary key ([name]), + foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade, key [type] ([type]) ); @@ -37,8 +46,9 @@ create table [auth_assignment] ( [item_name] varchar(64) not null, [user_id] varchar(64) not null, - [biz_rule] text, + [rule_name] varchar(64), [data] text, - primary key ([item_name],[user_id]), - foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade + primary key ([item_name], [user_id]), + foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade, + foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade ); diff --git a/framework/rbac/schema-mysql.sql b/framework/rbac/schema-mysql.sql index 5c91fddeaf..21c21ba719 100644 --- a/framework/rbac/schema-mysql.sql +++ b/framework/rbac/schema-mysql.sql @@ -12,15 +12,24 @@ drop table if exists `auth_assignment`; drop table if exists `auth_item_child`; drop table if exists `auth_item`; +drop table if exists `auth_rule`; + +create table `auth_rule` +( + `name` varchar(64) not null, + `data` text, + primary key (`name`) +) engine InnoDB; create table `auth_item` ( `name` varchar(64) not null, `type` integer not null, `description` text, - `biz_rule` text, + `rule_name` varchar(64), `data` text, primary key (`name`), + foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade, key `type` (`type`) ) engine InnoDB; @@ -28,7 +37,7 @@ create table `auth_item_child` ( `parent` varchar(64) not null, `child` varchar(64) not null, - primary key (`parent`,`child`), + primary key (`parent`, `child`), foreign key (`parent`) references `auth_item` (`name`) on delete cascade on update cascade, foreign key (`child`) references `auth_item` (`name`) on delete cascade on update cascade ) engine InnoDB; @@ -37,8 +46,9 @@ create table `auth_assignment` ( `item_name` varchar(64) not null, `user_id` varchar(64) not null, - `biz_rule` text, + `rule_name` varchar(64), `data` text, - primary key (`item_name`,`user_id`), - foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade -) engine InnoDB; + primary key (`item_name`, `user_id`), + foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade, + foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade +) engine InnoDB; \ No newline at end of file diff --git a/framework/rbac/schema-oci.sql b/framework/rbac/schema-oci.sql index 195d359be5..e90936475f 100644 --- a/framework/rbac/schema-oci.sql +++ b/framework/rbac/schema-oci.sql @@ -12,15 +12,24 @@ drop table if exists "auth_assignment"; drop table if exists "auth_item_child"; drop table if exists "auth_item"; +drop table if exists "auth_rule"; + +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + primary key ("name") +); create table "auth_item" ( "name" varchar(64) not null, "type" integer not null, "description" text, - "biz_rule" text, + "rule_name" varchar(64), "data" text, primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade, key "type" ("type") ); @@ -37,8 +46,9 @@ create table "auth_assignment" ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "biz_rule" text, + "rule_name" varchar(64), "data" text, primary key ("item_name","user_id"), - foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade ); diff --git a/framework/rbac/schema-pgsql.sql b/framework/rbac/schema-pgsql.sql index 9b3fec984e..134e5e3cf7 100644 --- a/framework/rbac/schema-pgsql.sql +++ b/framework/rbac/schema-pgsql.sql @@ -12,15 +12,24 @@ drop table if exists "auth_assignment"; drop table if exists "auth_item_child"; drop table if exists "auth_item"; +drop table if exists "auth_rule"; + +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + primary key ("name") +); create table "auth_item" ( "name" varchar(64) not null, "type" integer not null, "description" text, - "biz_rule" text, + "rule_name" varchar(64), "data" text, - primary key ("name") + primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade ); create index auth_item_type_idx on "auth_item" ("type"); @@ -38,8 +47,9 @@ create table "auth_assignment" ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "biz_rule" text, + "rule_name" varchar(64), "data" text, primary key ("item_name","user_id"), - foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade ); diff --git a/framework/rbac/schema-sqlite.sql b/framework/rbac/schema-sqlite.sql index 7aecc85b53..e90936475f 100644 --- a/framework/rbac/schema-sqlite.sql +++ b/framework/rbac/schema-sqlite.sql @@ -9,36 +9,46 @@ * @since 2.0 */ -drop table if exists 'auth_assignment'; -drop table if exists 'auth_item_child'; -drop table if exists 'auth_item'; +drop table if exists "auth_assignment"; +drop table if exists "auth_item_child"; +drop table if exists "auth_item"; +drop table if exists "auth_rule"; -create table 'auth_item' +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + primary key ("name") +); + +create table "auth_item" ( "name" varchar(64) not null, "type" integer not null, "description" text, - "biz_rule" text, + "rule_name" varchar(64), "data" text, primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade, key "type" ("type") ); -create table 'auth_item_child' +create table "auth_item_child" ( "parent" varchar(64) not null, "child" varchar(64) not null, primary key ("parent","child"), - foreign key ("parent") references 'auth_item' ("name") on delete cascade on update cascade, - foreign key ("child") references 'auth_item' ("name") on delete cascade on update cascade + foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade ); -create table 'auth_assignment' +create table "auth_assignment" ( "item_name" varchar(64) not null, "user_id" varchar(64) not null, - "biz_rule" text, + "rule_name" varchar(64), "data" text, primary key ("item_name","user_id"), - foreign key ("item_name") references 'auth_item' ("name") on delete cascade on update cascade + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade ); diff --git a/tests/unit/framework/rbac/AuthorRule.php b/tests/unit/framework/rbac/AuthorRule.php new file mode 100644 index 0000000000..8f9feb7d1e --- /dev/null +++ b/tests/unit/framework/rbac/AuthorRule.php @@ -0,0 +1,22 @@ +auth->createItem($name, $type, $description, $bizRule, $data); + $item = $this->auth->createItem($name, $type, $description, $ruleName, $data); $this->assertTrue($item instanceof Item); $this->assertEquals($item->type, $type); $this->assertEquals($item->name, $name); $this->assertEquals($item->description, $description); - $this->assertEquals($item->bizRule, $bizRule); + $this->assertEquals($item->ruleName, $ruleName); $this->assertEquals($item->data, $data); // test shortcut $name2 = 'createUser'; - $item2 = $this->auth->createRole($name2, $description, $bizRule, $data); + $item2 = $this->auth->createRole($name2, $description, $ruleName, $data); $this->assertEquals($item2->type, Item::TYPE_ROLE); // test adding an item with the same name $this->setExpectedException('\yii\base\Exception'); - $this->auth->createItem($name, $type, $description, $bizRule, $data); + $this->auth->createItem($name, $type, $description, $ruleName, $data); } public function testGetItem() @@ -102,7 +102,7 @@ abstract class ManagerTestCase extends TestCase $this->assertTrue($auth instanceof Assignment); $this->assertEquals($auth->userId, 'new user'); $this->assertEquals($auth->itemName, 'createPost'); - $this->assertEquals($auth->bizRule, 'rule'); + $this->assertEquals($auth->ruleName, 'rule'); $this->assertEquals($auth->data, 'data'); $this->setExpectedException('\yii\base\Exception'); @@ -168,14 +168,64 @@ abstract class ManagerTestCase extends TestCase $this->auth->addItemChild('readPost', 'readPost'); } - public function testExecuteBizRule() + public function testGetRule() { - $this->assertTrue($this->auth->executeBizRule(null, [], null)); - $this->assertTrue($this->auth->executeBizRule('return 1 == true;', [], null)); - $this->assertTrue($this->auth->executeBizRule('return $params[0] == $params[1];', [1, '1'], null)); - if (!defined('HHVM_VERSION')) { // invalid code crashes on HHVM - $this->assertFalse($this->auth->executeBizRule('invalid;', [], null)); + $rule = $this->auth->getRule('isAuthor'); + $this->assertInstanceOf('yii\rbac\Rule', $rule); + $this->assertEquals('isAuthor', $rule->name); + + $rule = $this->auth->getRule('nonExisting'); + $this->assertNull($rule); + } + + public function testSaveRule() + { + $ruleName = 'isReallyReallyAuthor'; + $rule = new AuthorRule($ruleName, ['reallyReally' => true]); + $this->auth->saveRule($rule); + + /** @var AuthorRule $rule */ + $rule = $this->auth->getRule($ruleName); + $this->assertEquals($ruleName, $rule->name); + $this->assertEquals(true, $rule->reallyReally); + + $rule->reallyReally = false; + $this->auth->saveRule($rule); + + /** @var AuthorRule $rule */ + $rule = $this->auth->getRule($ruleName); + $this->assertEquals(false, $rule->reallyReally); + } + + public function testGetRules() + { + $rule = new AuthorRule('isReallyReallyAuthor', ['reallyReally' => true]); + $this->auth->saveRule($rule); + + $rules = $this->auth->getRules(); + + $ruleNames = []; + foreach ($rules as $rule) { + $ruleNames[] = $rule->name; } + + $this->assertContains('isReallyReallyAuthor', $ruleNames); + $this->assertContains('isAuthor', $ruleNames); + } + + public function testRemoveRule() + { + $this->auth->removeRule('isAuthor'); + $rules = $this->auth->getRules(); + + $this->assertEmpty($rules); + } + + public function testExecuteRule() + { + $this->assertTrue($this->auth->executeRule(null, [], null)); + $this->assertTrue($this->auth->executeRule('isAuthor', ['userID' => 1, 'authorID' => 1], null)); + $this->assertFalse($this->auth->executeRule('isAuthor', ['userID' => 1, 'authorID' => 2], null)); } public function testCheckAccess() @@ -231,12 +281,14 @@ abstract class ManagerTestCase extends TestCase protected function prepareData() { + $this->auth->saveRule(new AuthorRule()); + $this->auth->createOperation('createPost', 'create a post'); $this->auth->createOperation('readPost', 'read a post'); $this->auth->createOperation('updatePost', 'update a post'); $this->auth->createOperation('deletePost', 'delete a post'); - $task = $this->auth->createTask('updateOwnPost', 'update a post by author himself', 'return $params["authorID"] == $params["userID"];'); + $task = $this->auth->createTask('updateOwnPost', 'update a post by author himself', 'isAuthor'); $task->addChild('updatePost'); $role = $this->auth->createRole('reader'); diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php index 7e5927a497..1dee48ef61 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -7,6 +7,7 @@ use yii\rbac\PhpManager; /** * @group rbac + * @property \yii\rbac\PhpManager $auth */ class PhpManagerTest extends ManagerTestCase {