Commit 69786b01 by Alexander Makarov

Merge pull request #2974 from yiisoft/decouple-bizrules-from-auth-items

RBAC: decoupled rules from assignments and items, implemented php manager
parents c5ad45c7 57498582
...@@ -121,24 +121,47 @@ class PhpManager extends \yii\rbac\PhpManager ...@@ -121,24 +121,47 @@ class PhpManager extends \yii\rbac\PhpManager
} }
``` ```
Now create custom rule class:
```php
namespace app\rbac;
use yii\rbac\Rule;
class NotGuestRule extends Rule
{
public function execute($params, $data)
{
return !Yii::$app->user->isGuest;
}
}
```
Then create permissions hierarchy in `@app/data/rbac.php`: Then create permissions hierarchy in `@app/data/rbac.php`:
```php ```php
<?php <?php
use yii\rbac\Item; use yii\rbac\Item;
use app\rbac\NotGuestRule;
$notGuest = new NotGuestRule();
return [ return [
'rules' => [
$rule->name => serialize($notGuest),
],
'items' => [
// HERE ARE YOUR MANAGEMENT TASKS // HERE ARE YOUR MANAGEMENT TASKS
'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL], 'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => NULL, 'data' => NULL],
'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL], 'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => NULL, 'data' => NULL],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL], 'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => NULL, 'data' => NULL],
'manageThing3' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'bizRule' => NULL, 'data' => NULL], 'manageThing3' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => NULL, 'data' => NULL],
// AND THE ROLES // AND THE ROLES
'guest' => [ 'guest' => [
'type' => Item::TYPE_ROLE, 'type' => Item::TYPE_ROLE,
'description' => 'Guest', 'description' => 'Guest',
'bizRule' => NULL, 'ruleName' => NULL,
'data' => NULL 'data' => NULL
], ],
...@@ -149,7 +172,7 @@ return [ ...@@ -149,7 +172,7 @@ return [
'guest', 'guest',
'manageThing0', // User can edit thing0 'manageThing0', // User can edit thing0
], ],
'bizRule' => 'return !Yii::$app->user->isGuest;', 'ruleName' => $notGuest->name,
'data' => NULL 'data' => NULL
], ],
...@@ -160,7 +183,7 @@ return [ ...@@ -160,7 +183,7 @@ return [
'user', // Can manage all that user can 'user', // Can manage all that user can
'manageThing1', // and also thing1 'manageThing1', // and also thing1
], ],
'bizRule' => NULL, 'ruleName' => NULL,
'data' => NULL 'data' => NULL
], ],
...@@ -171,7 +194,7 @@ return [ ...@@ -171,7 +194,7 @@ return [
'moderator', // can do all the stuff that moderator can 'moderator', // can do all the stuff that moderator can
'manageThing2', // and also manage thing2 'manageThing2', // and also manage thing2
], ],
'bizRule' => NULL, 'ruleName' => NULL,
'data' => NULL 'data' => NULL
], ],
...@@ -182,10 +205,10 @@ return [ ...@@ -182,10 +205,10 @@ return [
'admin', // can do all that admin can 'admin', // can do all that admin can
'manageThing3', // and also thing3 'manageThing3', // and also thing3
], ],
'bizRule' => NULL, 'ruleName' => NULL,
'data' => NULL 'data' => NULL
], ],
],
]; ];
``` ```
......
...@@ -79,6 +79,7 @@ Yii Framework 2 Change Log ...@@ -79,6 +79,7 @@ Yii Framework 2 Change Log
- Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard) - Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard)
- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard) - Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
- Enh #497: Removed `\yii\log\Target::logUser` and added `\yii\log\Target::prefix` to support customizing message prefix (qiangxue) - Enh #497: Removed `\yii\log\Target::logUser` and added `\yii\log\Target::prefix` to support customizing message prefix (qiangxue)
- Enh #499: Decoupled `Rule` from RBAC `Item` (samdark, qiangxue)
- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue) - Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
- Enh #802: Added support for retrieving sub-array element or child object property through `ArrayHelper::getValue()` (qiangxue, cebe) - Enh #802: Added support for retrieving sub-array element or child object property through `ArrayHelper::getValue()` (qiangxue, cebe)
- Enh #938: Added `yii\web\View::renderAjax()` and `yii\web\Controller::renderAjax()` (qiangxue) - Enh #938: Added `yii\web\View::renderAjax()` and `yii\web\Controller::renderAjax()` (qiangxue)
......
...@@ -27,9 +27,9 @@ class Assignment extends Object ...@@ -27,9 +27,9 @@ class Assignment extends Object
*/ */
public $manager; 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 * @var mixed additional data for this assignment
*/ */
......
...@@ -38,19 +38,27 @@ class DbManager extends Manager ...@@ -38,19 +38,27 @@ class DbManager extends Manager
* with a DB connection object. * with a DB connection object.
*/ */
public $db = 'db'; 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}}'; 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}}'; 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}}'; public $assignmentTable = '{{%auth_assignment}}';
/**
* @var string the name of the table storing rules. Defaults to "auth_rule".
*/
public $ruleTable = '{{%auth_rule}}';
private $_usingSqlite; private $_usingSqlite;
/** /**
...@@ -69,7 +77,7 @@ class DbManager extends Manager ...@@ -69,7 +77,7 @@ class DbManager extends Manager
* @param mixed $userId the user ID. This should can be either an integer or a string representing * @param mixed $userId the user ID. This should can be either an integer or a string representing
* the unique identifier of a user. See [[\yii\web\User::id]]. * the unique identifier of a user. See [[\yii\web\User::id]].
* @param string $itemName the name of the operation that need access check * @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 * @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, * 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`. * which holds the value of `$userId`.
* @return boolean whether the operations can be performed by the user. * @return boolean whether the operations can be performed by the user.
...@@ -87,7 +95,7 @@ class DbManager extends Manager ...@@ -87,7 +95,7 @@ class DbManager extends Manager
* @param mixed $userId the user ID. This should can be either an integer or a string representing * @param mixed $userId the user ID. This should can be either an integer or a string representing
* the unique identifier of a user. See [[\yii\web\User::id]]. * the unique identifier of a user. See [[\yii\web\User::id]].
* @param string $itemName the name of the operation that need access check * @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 * @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, * 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`. * which holds the value of `$userId`.
* @param Assignment[] $assignments the assignments to the specified user * @param Assignment[] $assignments the assignments to the specified user
...@@ -102,13 +110,13 @@ class DbManager extends Manager ...@@ -102,13 +110,13 @@ class DbManager extends Manager
if (!isset($params['userId'])) { if (!isset($params['userId'])) {
$params['userId'] = $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)) { if (in_array($itemName, $this->defaultRoles)) {
return true; return true;
} }
if (isset($assignments[$itemName])) { if (isset($assignments[$itemName])) {
$assignment = $assignments[$itemName]; $assignment = $assignments[$itemName];
if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { if ($this->executeRule($assignment->ruleName, $params, $assignment->data)) {
return true; return true;
} }
} }
...@@ -208,7 +216,7 @@ class DbManager extends Manager ...@@ -208,7 +216,7 @@ class DbManager extends Manager
public function getItemChildren($names) public function getItemChildren($names)
{ {
$query = new Query; $query = new Query;
$rows = $query->select(['name', 'type', 'description', 'biz_rule', 'data']) $rows = $query->select(['name', 'type', 'description', 'rule_name', 'data'])
->from([$this->itemTable, $this->itemChildTable]) ->from([$this->itemTable, $this->itemChildTable])
->where(['parent' => $names, 'name' => new Expression('child')]) ->where(['parent' => $names, 'name' => new Expression('child')])
->createCommand($this->db) ->createCommand($this->db)
...@@ -223,7 +231,7 @@ class DbManager extends Manager ...@@ -223,7 +231,7 @@ class DbManager extends Manager
'name' => $row['name'], 'name' => $row['name'],
'type' => $row['type'], 'type' => $row['type'],
'description' => $row['description'], 'description' => $row['description'],
'bizRule' => $row['biz_rule'], 'ruleName' => $row['rule_name'],
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -233,15 +241,16 @@ class DbManager extends Manager ...@@ -233,15 +241,16 @@ class DbManager extends Manager
/** /**
* Assigns an authorization item to a user. * 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 $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. * for this particular authorization item.
* @param mixed $data additional data associated with this assignment * @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information. * @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 * @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) { if ($this->usingSqlite() && $this->getItem($itemName) === null) {
throw new InvalidParamException("The item '$itemName' does not exist."); throw new InvalidParamException("The item '$itemName' does not exist.");
...@@ -250,7 +259,7 @@ class DbManager extends Manager ...@@ -250,7 +259,7 @@ class DbManager extends Manager
->insert($this->assignmentTable, [ ->insert($this->assignmentTable, [
'user_id' => $userId, 'user_id' => $userId,
'item_name' => $itemName, 'item_name' => $itemName,
'biz_rule' => $bizRule, 'rule_name' => $ruleName,
'data' => $data === null ? null : serialize($data), 'data' => $data === null ? null : serialize($data),
]) ])
->execute(); ->execute();
...@@ -259,7 +268,7 @@ class DbManager extends Manager ...@@ -259,7 +268,7 @@ class DbManager extends Manager
'manager' => $this, 'manager' => $this,
'userId' => $userId, 'userId' => $userId,
'itemName' => $itemName, 'itemName' => $itemName,
'bizRule' => $bizRule, 'ruleName' => $ruleName,
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -329,7 +338,7 @@ class DbManager extends Manager ...@@ -329,7 +338,7 @@ class DbManager extends Manager
'manager' => $this, 'manager' => $this,
'userId' => $row['user_id'], 'userId' => $row['user_id'],
'itemName' => $row['item_name'], 'itemName' => $row['item_name'],
'bizRule' => $row['biz_rule'], 'ruleName' => $row['rule_name'],
'data' => $data, 'data' => $data,
]); ]);
} else { } else {
...@@ -359,7 +368,7 @@ class DbManager extends Manager ...@@ -359,7 +368,7 @@ class DbManager extends Manager
'manager' => $this, 'manager' => $this,
'userId' => $row['user_id'], 'userId' => $row['user_id'],
'itemName' => $row['item_name'], 'itemName' => $row['item_name'],
'bizRule' => $row['biz_rule'], 'ruleName' => $row['rule_name'],
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -371,11 +380,11 @@ class DbManager extends Manager ...@@ -371,11 +380,11 @@ class DbManager extends Manager
* Saves the changes to an authorization assignment. * Saves the changes to an authorization assignment.
* @param Assignment $assignment the assignment that has been changed. * @param Assignment $assignment the assignment that has been changed.
*/ */
public function saveAssignment($assignment) public function saveAssignment(Assignment $assignment)
{ {
$this->db->createCommand() $this->db->createCommand()
->update($this->assignmentTable, [ ->update($this->assignmentTable, [
'biz_rule' => $assignment->bizRule, 'rule_name' => $assignment->ruleName,
'data' => $assignment->data === null ? null : serialize($assignment->data), 'data' => $assignment->data === null ? null : serialize($assignment->data),
], [ ], [
'user_id' => $assignment->userId, 'user_id' => $assignment->userId,
...@@ -403,12 +412,12 @@ class DbManager extends Manager ...@@ -403,12 +412,12 @@ class DbManager extends Manager
->where(['type' => $type]) ->where(['type' => $type])
->createCommand($this->db); ->createCommand($this->db);
} elseif ($type === null) { } elseif ($type === null) {
$command = $query->select(['name', 'type', 'description', 't1.biz_rule', 't1.data']) $command = $query->select(['name', 'type', 'description', 't1.rule_name', 't1.data'])
->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2']) ->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2'])
->where(['user_id' => $userId, 'name' => new Expression('item_name')]) ->where(['user_id' => $userId, 'name' => new Expression('item_name')])
->createCommand($this->db); ->createCommand($this->db);
} else { } else {
$command = $query->select(['name', 'type', 'description', 't1.biz_rule', 't1.data']) $command = $query->select(['name', 'type', 'description', 't1.rule_name', 't1.data'])
->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2']) ->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2'])
->where(['user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name')]) ->where(['user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name')])
->createCommand($this->db); ->createCommand($this->db);
...@@ -423,7 +432,7 @@ class DbManager extends Manager ...@@ -423,7 +432,7 @@ class DbManager extends Manager
'name' => $row['name'], 'name' => $row['name'],
'type' => $row['type'], 'type' => $row['type'],
'description' => $row['description'], 'description' => $row['description'],
'bizRule' => $row['biz_rule'], 'ruleName' => $row['rule_name'],
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -437,23 +446,24 @@ class DbManager extends Manager ...@@ -437,23 +446,24 @@ class DbManager extends Manager
* It has three types: operation, task and role. * It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing * Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items. * by lower level items.
*
* @param string $name the item name. This must be a unique identifier. * @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 integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item * @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. * PHP code that will be executed when [[checkAccess()]] is called for the item.
* @param mixed $data additional data associated with the item. * @param mixed $data additional data associated with the item.
* @return Item the authorization item * @return Item the authorization item
* @throws Exception if an item with the same name already exists * @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() $this->db->createCommand()
->insert($this->itemTable, [ ->insert($this->itemTable, [
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
'description' => $description, 'description' => $description,
'biz_rule' => $bizRule, 'rule_name' => $rule,
'data' => $data === null ? null : serialize($data), 'data' => $data === null ? null : serialize($data),
]) ])
->execute(); ->execute();
...@@ -463,7 +473,7 @@ class DbManager extends Manager ...@@ -463,7 +473,7 @@ class DbManager extends Manager
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
'description' => $description, 'description' => $description,
'bizRule' => $bizRule, 'ruleName' => $rule,
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -512,7 +522,7 @@ class DbManager extends Manager ...@@ -512,7 +522,7 @@ class DbManager extends Manager
'name' => $row['name'], 'name' => $row['name'],
'type' => $row['type'], 'type' => $row['type'],
'description' => $row['description'], 'description' => $row['description'],
'bizRule' => $row['biz_rule'], 'ruleName' => $row['rule_name'],
'data' => $data, 'data' => $data,
]); ]);
} else { } else {
...@@ -525,7 +535,7 @@ class DbManager extends Manager ...@@ -525,7 +535,7 @@ class DbManager extends Manager
* @param Item $item the item to be saved. * @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. * @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) { if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) {
$this->db->createCommand() $this->db->createCommand()
...@@ -544,7 +554,7 @@ class DbManager extends Manager ...@@ -544,7 +554,7 @@ class DbManager extends Manager
'name' => $item->getName(), 'name' => $item->getName(),
'type' => $item->type, 'type' => $item->type,
'description' => $item->description, 'description' => $item->description,
'biz_rule' => $item->bizRule, 'rule_name' => $item->ruleName,
'data' => $item->data === null ? null : serialize($item->data), 'data' => $item->data === null ? null : serialize($item->data),
], [ ], [
'name' => $oldName === null ? $item->getName() : $oldName, 'name' => $oldName === null ? $item->getName() : $oldName,
...@@ -604,4 +614,67 @@ class DbManager extends Manager ...@@ -604,4 +614,67 @@ class DbManager extends Manager
{ {
return $this->_usingSqlite; 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)
{
return $this->db->createCommand()->delete($this->ruleTable, ['name' => $name])->execute();
}
/**
* Saves the changes to the rule.
*
* @param Rule $rule the rule that has been changed.
*/
public function insertRule(Rule $rule)
{
$this->db->createCommand()->insert($this->ruleTable, ['name' => $rule->name, 'data' => serialize($rule)])->execute();
}
/**
* Updates existing rule.
*
* @param string $name the name of the rule to update
* @param Rule $rule new rule
*/
public function updateRule($name, Rule $rule)
{
$this->db->createCommand()->update($this->ruleTable, ['name' => $rule->name, 'data' => serialize($rule)], ['name' => $name])->execute();
}
/**
* Returns rule given its name.
*
* @param string $name name of the rule.
* @return Rule
*/
public function getRule($name)
{
$query = new Query;
$query->select(['data'])->from($this->ruleTable)->where(['name' => $name]);
$row = $query->createCommand($this->db)->queryOne();
return $row === false ? null : unserialize($row['data']);
}
/**
* Returns all rules.
*
* @return Rule[]
*/
public function getRules()
{
$query = new Query();
$rows = $query->from($this->ruleTable)->createCommand($this->db)->queryAll();
$rules = [];
foreach ($rows as $row) {
$rules[$row['name']] = unserialize($row['data']);
}
return $rules;
}
} }
...@@ -40,9 +40,9 @@ class Item extends Object ...@@ -40,9 +40,9 @@ class Item extends Object
*/ */
public $description; 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 * @var mixed the additional data associated with this item
*/ */
...@@ -66,7 +66,7 @@ class Item extends Object ...@@ -66,7 +66,7 @@ class Item extends Object
public function checkAccess($itemName, $params = []) public function checkAccess($itemName, $params = [])
{ {
Yii::trace('Checking permission: ' . $this->_name, __METHOD__); 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) { if ($this->_name == $itemName) {
return true; return true;
} }
...@@ -146,17 +146,18 @@ class Item extends Object ...@@ -146,17 +146,18 @@ class Item extends Object
/** /**
* Assigns this item to a user. * Assigns this 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 $bizRule the business rule to be executed when [[checkAccess()]] is called * @param Rule $rule the rule to be executed when [[checkAccess()]] is called
* for this particular authorization item. * for this particular authorization item.
* @param mixed $data additional data associated with this assignment * @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information. * @return Assignment the authorization assignment information.
* @throws \yii\base\Exception if the item has already been assigned to the user * @throws \yii\base\Exception if the item has already been assigned to the user
* @see Manager::assign * @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);
} }
/** /**
......
...@@ -44,17 +44,12 @@ use yii\base\InvalidParamException; ...@@ -44,17 +44,12 @@ use yii\base\InvalidParamException;
abstract class Manager extends Component 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. * @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. * These roles do not need to be explicitly assigned to any user.
* When calling [[checkAccess()]], these roles will be checked first. * When calling [[checkAccess()]], these roles will be checked first.
* For performance reason, you should minimize the number of such roles. * 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 * 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 * And then declare 'authenticated' in this property so that it can be applied to
* every authenticated user. * every authenticated user.
*/ */
...@@ -63,48 +58,52 @@ abstract class Manager extends Component ...@@ -63,48 +58,52 @@ abstract class Manager extends Component
/** /**
* Creates a role. * Creates a role.
* This is a shortcut method to [[Manager::createItem()]]. * This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name * @param string $name the item name
* @param string $description the item description. * @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 * @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item * @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. * Creates a task.
* This is a shortcut method to [[Manager::createItem()]]. * This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name * @param string $name the item name
* @param string $description the item description. * @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 * @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item * @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. * Creates an operation.
* This is a shortcut method to [[Manager::createItem()]]. * This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name * @param string $name the item name
* @param string $description the item description. * @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 * @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item * @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. * Returns roles.
* This is a shortcut method to [[Manager::getItems()]]. * This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the roles directly assigned to the user * @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. * will be returned. Otherwise, all roles will be returned.
* @return Item[] roles (name => AuthItem) * @return Item[] roles (name => AuthItem)
...@@ -117,6 +116,7 @@ abstract class Manager extends Component ...@@ -117,6 +116,7 @@ abstract class Manager extends Component
/** /**
* Returns tasks. * Returns tasks.
* This is a shortcut method to [[Manager::getItems()]]. * This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user * @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. * will be returned. Otherwise, all tasks will be returned.
* @return Item[] tasks (name => AuthItem) * @return Item[] tasks (name => AuthItem)
...@@ -129,6 +129,7 @@ abstract class Manager extends Component ...@@ -129,6 +129,7 @@ abstract class Manager extends Component
/** /**
* Returns operations. * Returns operations.
* This is a shortcut method to [[Manager::getItems()]]. * This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the operations directly assigned to the user * @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. * will be returned. Otherwise, all operations will be returned.
* @return Item[] operations (name => AuthItem) * @return Item[] operations (name => AuthItem)
...@@ -139,16 +140,21 @@ abstract class Manager extends Component ...@@ -139,16 +140,21 @@ abstract class Manager extends Component
} }
/** /**
* Executes the specified business rule. * Executes the specified rule.
* @param string $bizRule the business rule to be executed. *
* @param string $ruleName name of the rule to be executed.
* @param array $params parameters passed to [[Manager::checkAccess()]]. * @param array $params parameters passed to [[Manager::checkAccess()]].
* @param mixed $data additional data associated with the authorization item or assignment. * @param mixed $data additional data associated with the authorization item or assignment.
* @return boolean whether the business rule returns true. * @return boolean whether the rule execution returns true.
* If the business rule is empty, it will still return 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 ...@@ -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 * @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]]. * 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 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. * with the tasks and roles assigned to the user.
* @return boolean whether the operations can be performed by the user. * @return boolean whether the operations can be performed by the user.
*/ */
...@@ -182,22 +188,24 @@ abstract class Manager extends Component ...@@ -182,22 +188,24 @@ abstract class Manager extends Component
* It has three types: operation, task and role. * It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing * Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items. * by lower level items.
*
* @param string $name the item name. This must be a unique identifier. * @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 integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item * @param string $description description of the item
* @param string $bizRule business rule associated with the item. This is a piece of * @param string $ruleName name of the rule associated with the item.
* PHP code that will be executed when [[checkAccess()]] is called for the item.
* @param mixed $data additional data 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 * @throws \yii\base\Exception if an item with the same name already exists
* @return Item the authorization item * @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. * Removes the specified authorization item.
* @param string $name the name of the item to be removed * @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed * @return boolean whether the item exists in the storage and has been removed
*/ */
abstract public function removeItem($name); abstract public function removeItem($name);
/** /**
* Returns the authorization items of the specific type and user. * 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 * @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
...@@ -207,18 +215,20 @@ abstract class Manager extends Component ...@@ -207,18 +215,20 @@ abstract class Manager extends Component
* @return Item[] the authorization items of the specific type. * @return Item[] the authorization items of the specific type.
*/ */
abstract public function getItems($userId = null, $type = null); abstract public function getItems($userId = null, $type = null);
/** /**
* Returns the authorization item with the specified name. * Returns the authorization item with the specified name.
* @param string $name the name of the item * @param string $name the name of the item
* @return Item the authorization item. Null if the item cannot be found. * @return Item the authorization item. Null if the item cannot be found.
*/ */
abstract public function getItem($name); abstract public function getItem($name);
/** /**
* Saves an authorization item to persistent storage. * Saves an authorization item to persistent storage.
* @param Item $item the item to be saved. * @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. * @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. * Adds an item as a child of another item.
...@@ -227,6 +237,7 @@ abstract class Manager extends Component ...@@ -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. * @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); abstract public function addItemChild($itemName, $childName);
/** /**
* Removes a child from its parent. * Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed. * Note, the child item is not deleted. Only the parent-child relationship is removed.
...@@ -235,6 +246,7 @@ abstract class Manager extends Component ...@@ -235,6 +246,7 @@ abstract class Manager extends Component
* @return boolean whether the removal is successful * @return boolean whether the removal is successful
*/ */
abstract public function removeItemChild($itemName, $childName); abstract public function removeItemChild($itemName, $childName);
/** /**
* Returns a value indicating whether a child exists within a parent. * Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name * @param string $itemName the parent item name
...@@ -242,6 +254,7 @@ abstract class Manager extends Component ...@@ -242,6 +254,7 @@ abstract class Manager extends Component
* @return boolean whether the child exists * @return boolean whether the child exists
*/ */
abstract public function hasItemChild($itemName, $childName); abstract public function hasItemChild($itemName, $childName);
/** /**
* Returns the children of the specified item. * Returns the children of the specified item.
* @param mixed $itemName the parent item name. This can be either a string or an array. * @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 ...@@ -252,15 +265,17 @@ abstract class Manager extends Component
/** /**
* Assigns an authorization item to a user. * 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 $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. * for this particular authorization item.
* @param mixed $data additional data associated with this assignment * @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information. * @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 * @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. * Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param mixed $userId the user ID (see [[\yii\web\User::id]])
...@@ -268,12 +283,14 @@ abstract class Manager extends Component ...@@ -268,12 +283,14 @@ abstract class Manager extends Component
* @return boolean whether removal is successful * @return boolean whether removal is successful
*/ */
abstract public function revoke($userId, $itemName); abstract public function revoke($userId, $itemName);
/** /**
* Revokes all authorization assignments from a user. * Revokes all authorization assignments from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether removal is successful * @return boolean whether removal is successful
*/ */
abstract public function revokeAll($userId); abstract public function revokeAll($userId);
/** /**
* Returns a value indicating whether the item has been assigned to the user. * Returns a value indicating whether the item has been assigned to the user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param mixed $userId the user ID (see [[\yii\web\User::id]])
...@@ -281,6 +298,7 @@ abstract class Manager extends Component ...@@ -281,6 +298,7 @@ abstract class Manager extends Component
* @return boolean whether the item has been assigned to the user. * @return boolean whether the item has been assigned to the user.
*/ */
abstract public function isAssigned($userId, $itemName); abstract public function isAssigned($userId, $itemName);
/** /**
* Returns the item assignment information. * Returns the item assignment information.
* @param mixed $userId the user ID (see [[\yii\web\User::id]]) * @param mixed $userId the user ID (see [[\yii\web\User::id]])
...@@ -296,19 +314,60 @@ abstract class Manager extends Component ...@@ -296,19 +314,60 @@ abstract class Manager extends Component
* returned if there is no item assigned to the user. * returned if there is no item assigned to the user.
*/ */
abstract public function getAssignments($userId); 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);
/**
* Inserts new rule.
*
* @param Rule $rule the rule that needs to be stored.
*/
abstract public function insertRule(Rule $rule);
/**
* Updates existing rule.
*
* @param string $name the name of the rule to update
* @param Rule $rule new rule
*/
abstract public function updateRule($name, 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. * Saves the changes to an authorization assignment.
* @param Assignment $assignment the assignment that has been changed. * @param Assignment $assignment the assignment that has been changed.
*/ */
abstract public function saveAssignment($assignment); abstract public function saveAssignment(Assignment $assignment);
/** /**
* Removes all authorization data. * Removes all authorization data.
*/ */
abstract public function clearAll(); abstract public function clearAll();
/** /**
* Removes all authorization assignments. * Removes all authorization assignments.
*/ */
abstract public function clearAssignments(); abstract public function clearAssignments();
/** /**
* Saves authorization data into persistent storage. * Saves authorization data into persistent storage.
* If any change is made to the authorization data, please make * If any change is made to the authorization data, please make
......
...@@ -43,6 +43,7 @@ class PhpManager extends Manager ...@@ -43,6 +43,7 @@ class PhpManager extends Manager
private $_items = []; // itemName => item private $_items = []; // itemName => item
private $_children = []; // itemName, childName => child private $_children = []; // itemName, childName => child
private $_assignments = []; // userId, itemName => assignment private $_assignments = []; // userId, itemName => assignment
private $_rules = []; // ruleName => rule
/** /**
...@@ -62,7 +63,7 @@ class PhpManager extends Manager ...@@ -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 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 * @param string $itemName the name of the operation that need access check
* the unique identifier of a user. See [[\yii\web\User::id]]. * 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 * 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`. * this array, which holds the value of `$userId`.
* @return boolean whether the operations can be performed by the user. * @return boolean whether the operations can be performed by the user.
...@@ -78,14 +79,14 @@ class PhpManager extends Manager ...@@ -78,14 +79,14 @@ class PhpManager extends Manager
if (!isset($params['userId'])) { if (!isset($params['userId'])) {
$params['userId'] = $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)) { if (in_array($itemName, $this->defaultRoles)) {
return true; return true;
} }
if (isset($this->_assignments[$userId][$itemName])) { if (isset($this->_assignments[$userId][$itemName])) {
/** @var Assignment $assignment */ /** @var Assignment $assignment */
$assignment = $this->_assignments[$userId][$itemName]; $assignment = $this->_assignments[$userId][$itemName];
if ($this->executeBizRule($assignment->bizRule, $params, $assignment->data)) { if ($this->executeRule($assignment->ruleName, $params, $assignment->data)) {
return true; return true;
} }
} }
...@@ -181,15 +182,16 @@ class PhpManager extends Manager ...@@ -181,15 +182,16 @@ class PhpManager extends Manager
/** /**
* Assigns an authorization item to a user. * 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 $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. * for this particular authorization item.
* @param mixed $data additional data associated with this assignment * @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information. * @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 * @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])) { if (!isset($this->_items[$itemName])) {
throw new InvalidParamException("Unknown authorization item '$itemName'."); throw new InvalidParamException("Unknown authorization item '$itemName'.");
...@@ -200,7 +202,7 @@ class PhpManager extends Manager ...@@ -200,7 +202,7 @@ class PhpManager extends Manager
'manager' => $this, 'manager' => $this,
'userId' => $userId, 'userId' => $userId,
'itemName' => $itemName, 'itemName' => $itemName,
'bizRule' => $bizRule, 'ruleName' => $ruleName,
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -314,16 +316,17 @@ class PhpManager extends Manager ...@@ -314,16 +316,17 @@ class PhpManager extends Manager
* It has three types: operation, task and role. * It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing * Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items. * by lower level items.
*
* @param string $name the item name. This must be a unique identifier. * @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 integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item * @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. * PHP code that will be executed when [[checkAccess()]] is called for the item.
* @param mixed $data additional data associated with the item. * @param mixed $data additional data associated with the item.
* @return Item the authorization item * @return Item the authorization item
* @throws Exception if an item with the same name already exists * @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])) { if (isset($this->_items[$name])) {
throw new Exception('Unable to add an item whose name is the same as an existing item.'); 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 ...@@ -334,7 +337,7 @@ class PhpManager extends Manager
'name' => $name, 'name' => $name,
'type' => $type, 'type' => $type,
'description' => $description, 'description' => $description,
'bizRule' => $bizRule, 'ruleName' => $rule,
'data' => $data, 'data' => $data,
]); ]);
} }
...@@ -377,7 +380,7 @@ class PhpManager extends Manager ...@@ -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. * @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 * @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 ($oldName !== null && ($newName = $item->getName()) !== $oldName) { // name changed
if (isset($this->_items[$newName])) { if (isset($this->_items[$newName])) {
...@@ -410,7 +413,7 @@ class PhpManager extends Manager ...@@ -410,7 +413,7 @@ class PhpManager extends Manager
* Saves the changes to an authorization assignment. * Saves the changes to an authorization assignment.
* @param Assignment $assignment the assignment that has been changed. * @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 ...@@ -427,7 +430,7 @@ class PhpManager extends Manager
$items[$name] = [ $items[$name] = [
'type' => $item->type, 'type' => $item->type,
'description' => $item->description, 'description' => $item->description,
'bizRule' => $item->bizRule, 'ruleName' => $item->ruleName,
'data' => $item->data, 'data' => $item->data,
]; ];
if (isset($this->_children[$name])) { if (isset($this->_children[$name])) {
...@@ -443,14 +446,19 @@ class PhpManager extends Manager ...@@ -443,14 +446,19 @@ class PhpManager extends Manager
/** @var Assignment $assignment */ /** @var Assignment $assignment */
if (isset($items[$name])) { if (isset($items[$name])) {
$items[$name]['assignments'][$userId] = [ $items[$name]['assignments'][$userId] = [
'bizRule' => $assignment->bizRule, 'ruleName' => $assignment->ruleName,
'data' => $assignment->data, '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,20 +468,21 @@ class PhpManager extends Manager ...@@ -460,20 +468,21 @@ class PhpManager extends Manager
{ {
$this->clearAll(); $this->clearAll();
$items = $this->loadFromFile($this->authFile); $data = $this->loadFromFile($this->authFile);
foreach ($items as $name => $item) { if (isset($data['items'])) {
foreach ($data['items'] as $name => $item) {
$this->_items[$name] = new Item([ $this->_items[$name] = new Item([
'manager' => $this, 'manager' => $this,
'name' => $name, 'name' => $name,
'type' => $item['type'], 'type' => $item['type'],
'description' => $item['description'], 'description' => $item['description'],
'bizRule' => $item['bizRule'], 'ruleName' => $item['ruleName'],
'data' => $item['data'], 'data' => $item['data'],
]); ]);
} }
foreach ($items as $name => $item) { foreach ($data['items'] as $name => $item) {
if (isset($item['children'])) { if (isset($item['children'])) {
foreach ($item['children'] as $childName) { foreach ($item['children'] as $childName) {
if (isset($this->_items[$childName])) { if (isset($this->_items[$childName])) {
...@@ -487,7 +496,7 @@ class PhpManager extends Manager ...@@ -487,7 +496,7 @@ class PhpManager extends Manager
'manager' => $this, 'manager' => $this,
'userId' => $userId, 'userId' => $userId,
'itemName' => $name, 'itemName' => $name,
'bizRule' => $assignment['bizRule'], 'ruleName' => $assignment['ruleName'],
'data' => $assignment['data'], 'data' => $assignment['data'],
]); ]);
} }
...@@ -495,6 +504,13 @@ class PhpManager extends Manager ...@@ -495,6 +504,13 @@ class PhpManager extends Manager
} }
} }
if (isset($data['rules'])) {
foreach ($data['rules'] as $name => $ruleData) {
$this->_rules[$name] = unserialize($ruleData);
}
}
}
/** /**
* Removes all authorization data. * Removes all authorization data.
*/ */
...@@ -562,4 +578,70 @@ class PhpManager extends Manager ...@@ -562,4 +578,70 @@ class PhpManager extends Manager
{ {
file_put_contents($file, "<?php\nreturn " . var_export($data, true) . ";\n", LOCK_EX); file_put_contents($file, "<?php\nreturn " . var_export($data, true) . ";\n", LOCK_EX);
} }
/**
* 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)
{
if (isset($this->_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 insertRule(Rule $rule)
{
$this->_rules[$rule->name] = $rule;
}
/**
* Updates existing rule.
*
* @param string $name the name of the rule to update
* @param Rule $rule new rule
*/
public function updateRule($name, Rule $rule)
{
if ($rule->name !== $name) {
unset($this->_rules[$name]);
}
$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;
}
} }
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\rbac;
use yii\base\Object;
/**
* Rule represents a business constraint that may be assigned and the applied to
* an authorization item or assignment.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
*/
abstract class Rule extends Object
{
/**
* @var string name of the rule
*/
public $name;
/**
* 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);
}
...@@ -12,15 +12,24 @@ ...@@ -12,15 +12,24 @@
drop table if exists [auth_assignment]; drop table if exists [auth_assignment];
drop table if exists [auth_item_child]; drop table if exists [auth_item_child];
drop table if exists [auth_item]; 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] create table [auth_item]
( (
[name] varchar(64) not null, [name] varchar(64) not null,
[type] integer not null, [type] integer not null,
[description] text, [description] text,
[biz_rule] text, [rule_name] varchar(64),
[data] text, [data] text,
primary key ([name]), primary key ([name]),
foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade,
key [type] ([type]) key [type] ([type])
); );
...@@ -37,8 +46,9 @@ create table [auth_assignment] ...@@ -37,8 +46,9 @@ create table [auth_assignment]
( (
[item_name] varchar(64) not null, [item_name] varchar(64) not null,
[user_id] varchar(64) not null, [user_id] varchar(64) not null,
[biz_rule] text, [rule_name] varchar(64),
[data] text, [data] text,
primary key ([item_name],[user_id]), 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
); );
...@@ -12,15 +12,24 @@ ...@@ -12,15 +12,24 @@
drop table if exists `auth_assignment`; drop table if exists `auth_assignment`;
drop table if exists `auth_item_child`; drop table if exists `auth_item_child`;
drop table if exists `auth_item`; 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` create table `auth_item`
( (
`name` varchar(64) not null, `name` varchar(64) not null,
`type` integer not null, `type` integer not null,
`description` text, `description` text,
`biz_rule` text, `rule_name` varchar(64),
`data` text, `data` text,
primary key (`name`), primary key (`name`),
foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade,
key `type` (`type`) key `type` (`type`)
) engine InnoDB; ) engine InnoDB;
...@@ -28,7 +37,7 @@ create table `auth_item_child` ...@@ -28,7 +37,7 @@ create table `auth_item_child`
( (
`parent` varchar(64) not null, `parent` varchar(64) not null,
`child` 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 (`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 (`child`) references `auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB; ) engine InnoDB;
...@@ -37,8 +46,9 @@ create table `auth_assignment` ...@@ -37,8 +46,9 @@ create table `auth_assignment`
( (
`item_name` varchar(64) not null, `item_name` varchar(64) not null,
`user_id` varchar(64) not null, `user_id` varchar(64) not null,
`biz_rule` text, `rule_name` varchar(64),
`data` text, `data` text,
primary key (`item_name`,`user_id`), 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
) engine InnoDB; ) engine InnoDB;
\ No newline at end of file
...@@ -12,15 +12,24 @@ ...@@ -12,15 +12,24 @@
drop table if exists "auth_assignment"; drop table if exists "auth_assignment";
drop table if exists "auth_item_child"; drop table if exists "auth_item_child";
drop table if exists "auth_item"; 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" create table "auth_item"
( (
"name" varchar(64) not null, "name" varchar(64) not null,
"type" integer not null, "type" integer not null,
"description" text, "description" text,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "data" text,
primary key ("name"), primary key ("name"),
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade,
key "type" ("type") key "type" ("type")
); );
...@@ -37,8 +46,9 @@ create table "auth_assignment" ...@@ -37,8 +46,9 @@ create table "auth_assignment"
( (
"item_name" varchar(64) not null, "item_name" varchar(64) not null,
"user_id" varchar(64) not null, "user_id" varchar(64) not null,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "data" text,
primary key ("item_name","user_id"), 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
); );
...@@ -12,15 +12,24 @@ ...@@ -12,15 +12,24 @@
drop table if exists "auth_assignment"; drop table if exists "auth_assignment";
drop table if exists "auth_item_child"; drop table if exists "auth_item_child";
drop table if exists "auth_item"; 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" create table "auth_item"
( (
"name" varchar(64) not null, "name" varchar(64) not null,
"type" integer not null, "type" integer not null,
"description" text, "description" text,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "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"); create index auth_item_type_idx on "auth_item" ("type");
...@@ -38,8 +47,9 @@ create table "auth_assignment" ...@@ -38,8 +47,9 @@ create table "auth_assignment"
( (
"item_name" varchar(64) not null, "item_name" varchar(64) not null,
"user_id" varchar(64) not null, "user_id" varchar(64) not null,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "data" text,
primary key ("item_name","user_id"), 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
); );
...@@ -9,36 +9,47 @@ ...@@ -9,36 +9,47 @@
* @since 2.0 * @since 2.0
*/ */
drop table if exists 'auth_assignment'; drop table if exists "auth_assignment";
drop table if exists 'auth_item_child'; drop table if exists "auth_item_child";
drop table if exists 'auth_item'; 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, "name" varchar(64) not null,
"type" integer not null, "type" integer not null,
"description" text, "description" text,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "data" text,
primary key ("name"), primary key ("name"),
key "type" ("type") foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
); );
create table 'auth_item_child' create index "auth_item_type_idx" on "auth_item" ("type");
create table "auth_item_child"
( (
"parent" varchar(64) not null, "parent" varchar(64) not null,
"child" 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 ("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 ("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, "item_name" varchar(64) not null,
"user_id" varchar(64) not null, "user_id" varchar(64) not null,
"biz_rule" text, "rule_name" varchar(64),
"data" text, "data" text,
primary key ("item_name","user_id"), 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
); );
<?php
namespace yiiunit\framework\rbac;
use yii\rbac\Rule;
/**
* Checks if authorID matches userID passed via params
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
public $reallyReally = false;
/**
* @inheritdoc
*/
public function execute($params, $data)
{
return $params['authorID'] == $params['userID'];
}
}
\ No newline at end of file
<?php
namespace yiiunit\framework\rbac;
use yii\db\Connection;
use yii\rbac\DbManager;
/**
* DbManagerTestCase
*/
abstract class DbManagerTestCase extends ManagerTestCase
{
protected $database;
protected $driverName = 'mysql';
/**
* @var Connection
*/
protected $db;
protected function setUp()
{
parent::setUp();
$databases = $this->getParam('databases');
$this->database = $databases[$this->driverName];
$pdo_database = 'pdo_'.$this->driverName;
if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) {
$this->markTestSkipped('pdo and '.$pdo_database.' extension are required.');
}
$this->auth = new DbManager(['db' => $this->getConnection()]);
$this->auth->init();
$this->prepareData();
}
protected function tearDown()
{
parent::tearDown();
if ($this->db) {
$this->db->close();
}
$this->destroyApplication();
}
/**
* @param boolean $reset whether to clean up the test database
* @param boolean $open whether to open and populate test database
* @throws \yii\base\InvalidParamException
* @throws \yii\db\Exception
* @throws \yii\base\InvalidConfigException
* @return \yii\db\Connection
*/
public function getConnection($reset = true, $open = true)
{
if (!$reset && $this->db) {
return $this->db;
}
$db = new Connection;
$db->dsn = $this->database['dsn'];
if (isset($this->database['username'])) {
$db->username = $this->database['username'];
$db->password = $this->database['password'];
}
if (isset($this->database['attributes'])) {
$db->attributes = $this->database['attributes'];
}
if ($open) {
$db->open();
$lines = explode(';', file_get_contents(\Yii::getAlias('@yii/rbac/schema-'.$this->driverName.'.sql')));
foreach ($lines as $line) {
if (trim($line) !== '') {
$db->pdo->exec($line);
}
}
}
$this->db = $db;
return $db;
}
}
...@@ -8,7 +8,7 @@ use yiiunit\TestCase; ...@@ -8,7 +8,7 @@ use yiiunit\TestCase;
abstract class ManagerTestCase extends TestCase abstract class ManagerTestCase extends TestCase
{ {
/** @var \yii\rbac\PhpManager|\yii\rbac\DbManager */ /** @var \yii\rbac\Manager */
protected $auth; protected $auth;
public function testCreateItem() public function testCreateItem()
...@@ -16,24 +16,24 @@ abstract class ManagerTestCase extends TestCase ...@@ -16,24 +16,24 @@ abstract class ManagerTestCase extends TestCase
$type = Item::TYPE_TASK; $type = Item::TYPE_TASK;
$name = 'editUser'; $name = 'editUser';
$description = 'edit a user'; $description = 'edit a user';
$bizRule = 'checkUserIdentity()'; $ruleName = 'isAuthor';
$data = [1, 2, 3]; $data = [1, 2, 3];
$item = $this->auth->createItem($name, $type, $description, $bizRule, $data); $item = $this->auth->createItem($name, $type, $description, $ruleName, $data);
$this->assertTrue($item instanceof Item); $this->assertTrue($item instanceof Item);
$this->assertEquals($item->type, $type); $this->assertEquals($item->type, $type);
$this->assertEquals($item->name, $name); $this->assertEquals($item->name, $name);
$this->assertEquals($item->description, $description); $this->assertEquals($item->description, $description);
$this->assertEquals($item->bizRule, $bizRule); $this->assertEquals($item->ruleName, $ruleName);
$this->assertEquals($item->data, $data); $this->assertEquals($item->data, $data);
// test shortcut // test shortcut
$name2 = 'createUser'; $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); $this->assertEquals($item2->type, Item::TYPE_ROLE);
// test adding an item with the same name // test adding an item with the same name
$this->setExpectedException('\yii\base\Exception'); $this->setExpectedException('\yii\base\Exception');
$this->auth->createItem($name, $type, $description, $bizRule, $data); $this->auth->createItem($name, $type, $description, $ruleName, $data);
} }
public function testGetItem() public function testGetItem()
...@@ -98,11 +98,11 @@ abstract class ManagerTestCase extends TestCase ...@@ -98,11 +98,11 @@ abstract class ManagerTestCase extends TestCase
public function testAssign() public function testAssign()
{ {
$auth = $this->auth->assign('new user', 'createPost', 'rule', 'data'); $auth = $this->auth->assign('new user', 'createPost', 'isAuthor', 'data');
$this->assertTrue($auth instanceof Assignment); $this->assertTrue($auth instanceof Assignment);
$this->assertEquals($auth->userId, 'new user'); $this->assertEquals($auth->userId, 'new user');
$this->assertEquals($auth->itemName, 'createPost'); $this->assertEquals($auth->itemName, 'createPost');
$this->assertEquals($auth->bizRule, 'rule'); $this->assertEquals($auth->ruleName, 'isAuthor');
$this->assertEquals($auth->data, 'data'); $this->assertEquals($auth->data, 'data');
$this->setExpectedException('\yii\base\Exception'); $this->setExpectedException('\yii\base\Exception');
...@@ -168,14 +168,79 @@ abstract class ManagerTestCase extends TestCase ...@@ -168,14 +168,79 @@ abstract class ManagerTestCase extends TestCase
$this->auth->addItemChild('readPost', 'readPost'); $this->auth->addItemChild('readPost', 'readPost');
} }
public function testExecuteBizRule() public function testGetRule()
{ {
$this->assertTrue($this->auth->executeBizRule(null, [], null)); $rule = $this->auth->getRule('isAuthor');
$this->assertTrue($this->auth->executeBizRule('return 1 == true;', [], null)); $this->assertInstanceOf('yii\rbac\Rule', $rule);
$this->assertTrue($this->auth->executeBizRule('return $params[0] == $params[1];', [1, '1'], null)); $this->assertEquals('isAuthor', $rule->name);
if (!defined('HHVM_VERSION')) { // invalid code crashes on HHVM
$this->assertFalse($this->auth->executeBizRule('invalid;', [], null)); $rule = $this->auth->getRule('nonExisting');
$this->assertNull($rule);
} }
public function testInsertRule()
{
$ruleName = 'isReallyReallyAuthor';
$rule = new AuthorRule(['name' => $ruleName, 'reallyReally' => true]);
$this->auth->insertRule($rule);
/** @var AuthorRule $rule */
$rule = $this->auth->getRule($ruleName);
$this->assertEquals($ruleName, $rule->name);
$this->assertEquals(true, $rule->reallyReally);
}
public function testUpdateRule()
{
$rule = $this->auth->getRule('isAuthor');
$rule->name = "newName";
$rule->reallyReally = false;
$this->auth->updateRule('isAuthor', $rule);
/** @var AuthorRule $rule */
$rule = $this->auth->getRule('isAuthor');
$this->assertEquals(null, $rule);
$rule = $this->auth->getRule('newName');
$this->assertEquals("newName", $rule->name);
$this->assertEquals(false, $rule->reallyReally);
$rule->reallyReally = true;
$this->auth->updateRule('newName', $rule);
$rule = $this->auth->getRule('newName');
$this->assertEquals(true, $rule->reallyReally);
}
public function testGetRules()
{
$rule = new AuthorRule(['name' => 'isReallyReallyAuthor', 'reallyReally' => true]);
$this->auth->insertRule($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() public function testCheckAccess()
...@@ -231,12 +296,14 @@ abstract class ManagerTestCase extends TestCase ...@@ -231,12 +296,14 @@ abstract class ManagerTestCase extends TestCase
protected function prepareData() protected function prepareData()
{ {
$this->auth->insertRule(new AuthorRule());
$this->auth->createOperation('createPost', 'create a post'); $this->auth->createOperation('createPost', 'create a post');
$this->auth->createOperation('readPost', 'read a post'); $this->auth->createOperation('readPost', 'read a post');
$this->auth->createOperation('updatePost', 'update a post'); $this->auth->createOperation('updatePost', 'update a post');
$this->auth->createOperation('deletePost', 'delete 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'); $task->addChild('updatePost');
$role = $this->auth->createRole('reader'); $role = $this->auth->createRole('reader');
......
<?php
namespace yiiunit\framework\rbac;
/**
* MySQLManagerTest
*/
class MySQLManagerTest extends DbManagerTestCase
{
}
<?php
namespace yiiunit\framework\rbac;
/**
* PgSQLManagerTest
*/
class PgSQLManagerTest extends DbManagerTestCase
{
protected $driverName = 'pgsql';
}
...@@ -7,6 +7,7 @@ use yii\rbac\PhpManager; ...@@ -7,6 +7,7 @@ use yii\rbac\PhpManager;
/** /**
* @group rbac * @group rbac
* @property \yii\rbac\PhpManager $auth
*/ */
class PhpManagerTest extends ManagerTestCase class PhpManagerTest extends ManagerTestCase
{ {
......
<?php
namespace yiiunit\framework\rbac;
/**
* SqliteManagerTest
*/
class SqliteManagerTest extends DbManagerTestCase
{
protected $driverName = 'sqlite';
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment