Commit 3eee7b8e by Alexander Makarov

Fixes #4072: `\yii\rbac\PhpManager` adjustments

- Data is now stored in three separate files for items, assignments and rules. File format is simpler. - Removed `authFile`. Added `itemsFile`, `assignmentsFile` and `rulesFile`. - `createdAt` and `updatedAt` are now properly filled with corresponding file modification time. - `save()` and `load()` are now protected instead of public. - Added unit test for saving and loading data.
parent c8ee057e
...@@ -120,6 +120,12 @@ Yii Framework 2 Change Log ...@@ -120,6 +120,12 @@ Yii Framework 2 Change Log
- Improved overall slug results. - Improved overall slug results.
- Added note about the fact that intl is required for non-latin languages to requirements checker. - Added note about the fact that intl is required for non-latin languages to requirements checker.
- Enh #4028: Added ability to `yii\widgets\Menu` to encode each item's label separately (creocoder, umneeq) - Enh #4028: Added ability to `yii\widgets\Menu` to encode each item's label separately (creocoder, umneeq)
- Enh #4072: `\yii\rbac\PhpManager` adjustments (samdark)
- Data is now stored in three separate files for items, assignments and rules. File format is simpler.
- Removed `authFile`. Added `itemsFile`, `assignmentsFile` and `rulesFile`.
- `createdAt` and `updatedAt` are now properly filled with corresponding file modification time.
- `save()` and `load()` are now protected instead of public.
- Added unit test for saving and loading data.
- Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm) - Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
......
...@@ -72,3 +72,51 @@ Upgrade from Yii 2.0 Beta ...@@ -72,3 +72,51 @@ Upgrade from Yii 2.0 Beta
* `mail` component was renamed to `mailer`, `yii\log\EmailTarget::$mail` was renamed to `yii\log\EmailTarget::$mailer`. * `mail` component was renamed to `mailer`, `yii\log\EmailTarget::$mail` was renamed to `yii\log\EmailTarget::$mailer`.
Please update all references in the code and config files. Please update all references in the code and config files.
* `\yii\rbac\PhpManager` now stores data in three separate files instead of one. In order to convert old file to
new ones save the following code as `convert.php` that should be placed in the same directory your `rbac.php` is in:
```php
<?php
$oldFile = 'rbac.php';
$itemsFile = 'rbac-items.php';
$assignmentsFile = 'rbac-assignments.php';
$rulesFile = 'rbac-rules.php';
$oldData = include $oldFile;
function saveToFile($data, $fileName) {
$out = var_export($data, true);
$out = "<?php\nreturn " . $out . ";";
$out = str_replace(['array (', ')'], ['[', ']'], $out);
file_put_contents($fileName, $out);
}
$items = [];
$assignments = [];
if (isset($oldData['items'])) {
foreach ($oldData['items'] as $name => $data) {
if (isset($data['assignments'])) {
foreach ($data['assignments'] as $userId => $assignmentData) {
$assignments[$userId] = $assignmentData['roleName'];
}
unset($data['assignments']);
}
$items[$name] = $data;
}
}
$rules = [];
if (isset($oldData['rules'])) {
$rules = $oldData['rules'];
}
saveToFile($items, $itemsFile);
saveToFile($assignments, $assignmentsFile);
saveToFile($rules, $rulesFile);
echo "Done!\n";
```
Run it once, delete `rbac.php`. If you've configured `authFile` property, remove the line from config and instead
configure `itemsFile`, `assignmentsFile` and `rulesFile`.
\ No newline at end of file
...@@ -13,8 +13,6 @@ use yii\base\Object; ...@@ -13,8 +13,6 @@ use yii\base\Object;
/** /**
* Assignment represents an assignment of a role to a user. * Assignment represents an assignment of a role to a user.
* *
* It includes additional assignment information including [[ruleName]] and [[data]].
*
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com> * @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0 * @since 2.0
......
...@@ -26,34 +26,52 @@ use yii\helpers\VarDumper; ...@@ -26,34 +26,52 @@ use yii\helpers\VarDumper;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com> * @author Alexander Kochetov <creocoder@gmail.com>
* @author Christophe Boulain <christophe.boulain@gmail.com> * @author Christophe Boulain <christophe.boulain@gmail.com>
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0 * @since 2.0
*/ */
class PhpManager extends BaseManager class PhpManager extends BaseManager
{ {
/** /**
* @var string the path of the PHP script that contains the authorization data. * @var string the path of the PHP script that contains the authorization items.
* This can be either a file path or a path alias to the file. * This can be either a file path or a path alias to the file.
* Make sure this file is writable by the Web server process if the authorization needs to be changed online. * Make sure this file is writable by the Web server process if the authorization needs to be changed online.
* @see loadFromFile() * @see loadFromFile()
* @see saveToFile() * @see saveToFile()
*/ */
public $authFile = '@app/data/rbac.php'; public $itemsFile = '@app/data/rbac-items.php';
/**
* @var string the path of the PHP script that contains the authorization assignments.
* This can be either a file path or a path alias to the file.
* Make sure this file is writable by the Web server process if the authorization needs to be changed online.
* @see loadFromFile()
* @see saveToFile()
*/
public $assignmentsFile = '@app/data/rbac-assignments.php';
/**
* @var string the path of the PHP script that contains the authorization rules.
* This can be either a file path or a path alias to the file.
* Make sure this file is writable by the Web server process if the authorization needs to be changed online.
* @see loadFromFile()
* @see saveToFile()
*/
public $rulesFile = '@app/data/rbac-rules.php';
/** /**
* @var Item[] * @var Item[]
*/ */
private $_items = []; // itemName => item protected $items = []; // itemName => item
/** /**
* @var array * @var array
*/ */
private $_children = []; // itemName, childName => child protected $children = []; // itemName, childName => child
/** /**
* @var Assignment[] * @var Assignment[]
*/ */
private $_assignments = []; // userId, itemName => assignment protected $assignments = []; // userId, itemName => assignment
/** /**
* @var Rule[] * @var Rule[]
*/ */
private $_rules = []; // ruleName => rule protected $rules = []; // ruleName => rule
/** /**
...@@ -64,7 +82,9 @@ class PhpManager extends BaseManager ...@@ -64,7 +82,9 @@ class PhpManager extends BaseManager
public function init() public function init()
{ {
parent::init(); parent::init();
$this->authFile = Yii::getAlias($this->authFile); $this->itemsFile = Yii::getAlias($this->itemsFile);
$this->assignmentsFile = Yii::getAlias($this->assignmentsFile);
$this->rulesFile = Yii::getAlias($this->rulesFile);
$this->load(); $this->load();
} }
...@@ -82,7 +102,7 @@ class PhpManager extends BaseManager ...@@ -82,7 +102,7 @@ class PhpManager extends BaseManager
*/ */
public function getAssignments($userId) public function getAssignments($userId)
{ {
return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : []; return isset($this->assignments[$userId]) ? $this->assignments[$userId] : [];
} }
/** /**
...@@ -100,12 +120,12 @@ class PhpManager extends BaseManager ...@@ -100,12 +120,12 @@ class PhpManager extends BaseManager
*/ */
protected function checkAccessRecursive($user, $itemName, $params, $assignments) protected function checkAccessRecursive($user, $itemName, $params, $assignments)
{ {
if (!isset($this->_items[$itemName])) { if (!isset($this->items[$itemName])) {
return false; return false;
} }
/* @var $item Item */ /* @var $item Item */
$item = $this->_items[$itemName]; $item = $this->items[$itemName];
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__); Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__);
if (!$this->executeRule($user, $item, $params)) { if (!$this->executeRule($user, $item, $params)) {
...@@ -116,7 +136,7 @@ class PhpManager extends BaseManager ...@@ -116,7 +136,7 @@ class PhpManager extends BaseManager
return true; return true;
} }
foreach ($this->_children as $parentName => $children) { foreach ($this->children as $parentName => $children) {
if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) { if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) {
return true; return true;
} }
...@@ -130,7 +150,7 @@ class PhpManager extends BaseManager ...@@ -130,7 +150,7 @@ class PhpManager extends BaseManager
*/ */
public function addChild($parent, $child) public function addChild($parent, $child)
{ {
if (!isset($this->_items[$parent->name], $this->_items[$child->name])) { if (!isset($this->items[$parent->name], $this->items[$child->name])) {
throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist."); throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist.");
} }
...@@ -144,11 +164,11 @@ class PhpManager extends BaseManager ...@@ -144,11 +164,11 @@ class PhpManager extends BaseManager
if ($this->detectLoop($parent, $child)) { if ($this->detectLoop($parent, $child)) {
throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected."); throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
} }
if (isset($this->_children[$parent->name][$child->name])) { if (isset($this->children[$parent->name][$child->name])) {
throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'."); throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'.");
} }
$this->_children[$parent->name][$child->name] = $this->_items[$child->name]; $this->children[$parent->name][$child->name] = $this->items[$child->name];
$this->save(); $this->saveItems();
return true; return true;
} }
...@@ -165,10 +185,10 @@ class PhpManager extends BaseManager ...@@ -165,10 +185,10 @@ class PhpManager extends BaseManager
if ($child->name === $parent->name) { if ($child->name === $parent->name) {
return true; return true;
} }
if (!isset($this->_children[$child->name], $this->_items[$parent->name])) { if (!isset($this->children[$child->name], $this->items[$parent->name])) {
return false; return false;
} }
foreach ($this->_children[$child->name] as $grandchild) { foreach ($this->children[$child->name] as $grandchild) {
/* @var $grandchild Item */ /* @var $grandchild Item */
if ($this->detectLoop($parent, $grandchild)) { if ($this->detectLoop($parent, $grandchild)) {
return true; return true;
...@@ -183,9 +203,9 @@ class PhpManager extends BaseManager ...@@ -183,9 +203,9 @@ class PhpManager extends BaseManager
*/ */
public function removeChild($parent, $child) public function removeChild($parent, $child)
{ {
if (isset($this->_children[$parent->name][$child->name])) { if (isset($this->children[$parent->name][$child->name])) {
unset($this->_children[$parent->name][$child->name]); unset($this->children[$parent->name][$child->name]);
$this->save(); $this->saveItems();
return true; return true;
} else { } else {
return false; return false;
...@@ -197,7 +217,7 @@ class PhpManager extends BaseManager ...@@ -197,7 +217,7 @@ class PhpManager extends BaseManager
*/ */
public function hasChild($parent, $child) public function hasChild($parent, $child)
{ {
return isset($this->_children[$parent->name][$child->name]); return isset($this->children[$parent->name][$child->name]);
} }
/** /**
...@@ -205,18 +225,18 @@ class PhpManager extends BaseManager ...@@ -205,18 +225,18 @@ class PhpManager extends BaseManager
*/ */
public function assign($role, $userId, $ruleName = null, $data = null) public function assign($role, $userId, $ruleName = null, $data = null)
{ {
if (!isset($this->_items[$role->name])) { if (!isset($this->items[$role->name])) {
throw new InvalidParamException("Unknown role '{$role->name}'."); throw new InvalidParamException("Unknown role '{$role->name}'.");
} elseif (isset($this->_assignments[$userId][$role->name])) { } elseif (isset($this->assignments[$userId][$role->name])) {
throw new InvalidParamException("Authorization item '{$role->name}' has already been assigned to user '$userId'."); throw new InvalidParamException("Authorization item '{$role->name}' has already been assigned to user '$userId'.");
} else { } else {
$this->_assignments[$userId][$role->name] = new Assignment([ $this->assignments[$userId][$role->name] = new Assignment([
'userId' => $userId, 'userId' => $userId,
'roleName' => $role->name, 'roleName' => $role->name,
'createdAt' => time(), 'createdAt' => time(),
]); ]);
$this->save(); $this->saveAssignments();
return $this->_assignments[$userId][$role->name]; return $this->assignments[$userId][$role->name];
} }
} }
...@@ -225,9 +245,9 @@ class PhpManager extends BaseManager ...@@ -225,9 +245,9 @@ class PhpManager extends BaseManager
*/ */
public function revoke($role, $userId) public function revoke($role, $userId)
{ {
if (isset($this->_assignments[$userId][$role->name])) { if (isset($this->assignments[$userId][$role->name])) {
unset($this->_assignments[$userId][$role->name]); unset($this->assignments[$userId][$role->name]);
$this->save(); $this->saveAssignments();
return true; return true;
} else { } else {
return false; return false;
...@@ -239,11 +259,11 @@ class PhpManager extends BaseManager ...@@ -239,11 +259,11 @@ class PhpManager extends BaseManager
*/ */
public function revokeAll($userId) public function revokeAll($userId)
{ {
if (isset($this->_assignments[$userId]) && is_array($this->_assignments[$userId])) { if (isset($this->assignments[$userId]) && is_array($this->assignments[$userId])) {
foreach ($this->_assignments[$userId] as $itemName => $value) { foreach ($this->assignments[$userId] as $itemName => $value) {
unset($this->_assignments[$userId][$itemName]); unset($this->assignments[$userId][$itemName]);
} }
$this->save(); $this->saveAssignments();
return true; return true;
} else { } else {
return false; return false;
...@@ -255,7 +275,7 @@ class PhpManager extends BaseManager ...@@ -255,7 +275,7 @@ class PhpManager extends BaseManager
*/ */
public function getAssignment($roleName, $userId) public function getAssignment($roleName, $userId)
{ {
return isset($this->_assignments[$userId][$roleName]) ? $this->_assignments[$userId][$roleName] : null; return isset($this->assignments[$userId][$roleName]) ? $this->assignments[$userId][$roleName] : null;
} }
/** /**
...@@ -265,7 +285,7 @@ class PhpManager extends BaseManager ...@@ -265,7 +285,7 @@ class PhpManager extends BaseManager
{ {
$items = []; $items = [];
foreach ($this->_items as $name => $item) { foreach ($this->items as $name => $item) {
/* @var $item Item */ /* @var $item Item */
if ($item->type == $type) { if ($item->type == $type) {
$items[$name] = $item; $items[$name] = $item;
...@@ -281,15 +301,15 @@ class PhpManager extends BaseManager ...@@ -281,15 +301,15 @@ class PhpManager extends BaseManager
*/ */
public function removeItem($item) public function removeItem($item)
{ {
if (isset($this->_items[$item->name])) { if (isset($this->items[$item->name])) {
foreach ($this->_children as &$children) { foreach ($this->children as &$children) {
unset($children[$item->name]); unset($children[$item->name]);
} }
foreach ($this->_assignments as &$assignments) { foreach ($this->assignments as &$assignments) {
unset($assignments[$item->name]); unset($assignments[$item->name]);
} }
unset($this->_items[$item->name]); unset($this->items[$item->name]);
$this->save(); $this->saveItems();
return true; return true;
} else { } else {
return false; return false;
...@@ -301,7 +321,7 @@ class PhpManager extends BaseManager ...@@ -301,7 +321,7 @@ class PhpManager extends BaseManager
*/ */
public function getItem($name) public function getItem($name)
{ {
return isset($this->_items[$name]) ? $this->_items[$name] : null; return isset($this->items[$name]) ? $this->items[$name] : null;
} }
/** /**
...@@ -310,10 +330,10 @@ class PhpManager extends BaseManager ...@@ -310,10 +330,10 @@ class PhpManager extends BaseManager
public function updateRule($name, $rule) public function updateRule($name, $rule)
{ {
if ($rule->name !== $name) { if ($rule->name !== $name) {
unset($this->_rules[$name]); unset($this->rules[$name]);
} }
$this->_rules[$rule->name] = $rule; $this->rules[$rule->name] = $rule;
$this->save(); $this->saveRules();
return true; return true;
} }
...@@ -322,7 +342,7 @@ class PhpManager extends BaseManager ...@@ -322,7 +342,7 @@ class PhpManager extends BaseManager
*/ */
public function getRule($name) public function getRule($name)
{ {
return isset($this->_rules[$name]) ? $this->_rules[$name] : null; return isset($this->rules[$name]) ? $this->rules[$name] : null;
} }
/** /**
...@@ -330,7 +350,7 @@ class PhpManager extends BaseManager ...@@ -330,7 +350,7 @@ class PhpManager extends BaseManager
*/ */
public function getRules() public function getRules()
{ {
return $this->_rules; return $this->rules;
} }
/** /**
...@@ -340,7 +360,7 @@ class PhpManager extends BaseManager ...@@ -340,7 +360,7 @@ class PhpManager extends BaseManager
{ {
$roles = []; $roles = [];
foreach ($this->getAssignments($userId) as $name => $assignment) { foreach ($this->getAssignments($userId) as $name => $assignment) {
$roles[$name] = $this->_items[$assignment->roleName]; $roles[$name] = $this->items[$assignment->roleName];
} }
return $roles; return $roles;
...@@ -358,8 +378,8 @@ class PhpManager extends BaseManager ...@@ -358,8 +378,8 @@ class PhpManager extends BaseManager
} }
$permissions = []; $permissions = [];
foreach (array_keys($result) as $itemName) { foreach (array_keys($result) as $itemName) {
if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) { if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) {
$permissions[$itemName] = $this->_items[$itemName]; $permissions[$itemName] = $this->items[$itemName];
} }
} }
return $permissions; return $permissions;
...@@ -373,8 +393,8 @@ class PhpManager extends BaseManager ...@@ -373,8 +393,8 @@ class PhpManager extends BaseManager
*/ */
protected function getChildrenRecursive($name, &$result) protected function getChildrenRecursive($name, &$result)
{ {
if (isset($this->_children[$name])) { if (isset($this->children[$name])) {
foreach ($this->_children[$name] as $child) { foreach ($this->children[$name] as $child) {
$result[$child->name] = true; $result[$child->name] = true;
$this->getChildrenRecursive($child->name, $result); $this->getChildrenRecursive($child->name, $result);
} }
...@@ -398,8 +418,8 @@ class PhpManager extends BaseManager ...@@ -398,8 +418,8 @@ class PhpManager extends BaseManager
$permissions = []; $permissions = [];
foreach (array_keys($result) as $itemName) { foreach (array_keys($result) as $itemName) {
if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) { if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) {
$permissions[$itemName] = $this->_items[$itemName]; $permissions[$itemName] = $this->items[$itemName];
} }
} }
return $permissions; return $permissions;
...@@ -410,7 +430,7 @@ class PhpManager extends BaseManager ...@@ -410,7 +430,7 @@ class PhpManager extends BaseManager
*/ */
public function getChildren($name) public function getChildren($name)
{ {
return isset($this->_children[$name]) ? $this->_children[$name] : []; return isset($this->children[$name]) ? $this->children[$name] : [];
} }
/** /**
...@@ -418,10 +438,10 @@ class PhpManager extends BaseManager ...@@ -418,10 +438,10 @@ class PhpManager extends BaseManager
*/ */
public function removeAll() public function removeAll()
{ {
$this->_children = []; $this->children = [];
$this->_items = []; $this->items = [];
$this->_assignments = []; $this->assignments = [];
$this->_rules = []; $this->rules = [];
$this->save(); $this->save();
} }
...@@ -448,9 +468,9 @@ class PhpManager extends BaseManager ...@@ -448,9 +468,9 @@ class PhpManager extends BaseManager
protected function removeAllItems($type) protected function removeAllItems($type)
{ {
$names = []; $names = [];
foreach ($this->_items as $name => $item) { foreach ($this->items as $name => $item) {
if ($item->type == $type) { if ($item->type == $type) {
unset($this->_items[$name]); unset($this->items[$name]);
$names[$name] = true; $names[$name] = true;
} }
} }
...@@ -458,25 +478,25 @@ class PhpManager extends BaseManager ...@@ -458,25 +478,25 @@ class PhpManager extends BaseManager
return; return;
} }
foreach ($this->_assignments as $i => $assignment) { foreach ($this->assignments as $i => $assignment) {
if (isset($names[$assignment->roleName])) { if (isset($names[$assignment->roleName])) {
unset($this->_assignments[$i]); unset($this->assignments[$i]);
} }
} }
foreach ($this->_children as $name => $children) { foreach ($this->children as $name => $children) {
if (isset($names[$name])) { if (isset($names[$name])) {
unset($this->_children[$name]); unset($this->children[$name]);
} else { } else {
foreach ($children as $childName => $item) { foreach ($children as $childName => $item) {
if (isset($names[$childName])) { if (isset($names[$childName])) {
unset($children[$childName]); unset($children[$childName]);
} }
} }
$this->_children[$name] = $children; $this->children[$name] = $children;
} }
} }
$this->save(); $this->saveItems();
} }
/** /**
...@@ -484,11 +504,11 @@ class PhpManager extends BaseManager ...@@ -484,11 +504,11 @@ class PhpManager extends BaseManager
*/ */
public function removeAllRules() public function removeAllRules()
{ {
foreach ($this->_items as $item) { foreach ($this->items as $item) {
$item->ruleName = null; $item->ruleName = null;
} }
$this->_rules = []; $this->rules = [];
$this->save(); $this->saveRules();
} }
/** /**
...@@ -496,8 +516,8 @@ class PhpManager extends BaseManager ...@@ -496,8 +516,8 @@ class PhpManager extends BaseManager
*/ */
public function removeAllAssignments() public function removeAllAssignments()
{ {
$this->_assignments = []; $this->assignments = [];
$this->save(); $this->saveAssignments();
} }
/** /**
...@@ -505,14 +525,14 @@ class PhpManager extends BaseManager ...@@ -505,14 +525,14 @@ class PhpManager extends BaseManager
*/ */
protected function removeRule($rule) protected function removeRule($rule)
{ {
if (isset($this->_rules[$rule->name])) { if (isset($this->rules[$rule->name])) {
unset($this->_rules[$rule->name]); unset($this->rules[$rule->name]);
foreach ($this->_items as $item) { foreach ($this->items as $item) {
if ($item->ruleName === $rule->name) { if ($item->ruleName === $rule->name) {
$item->ruleName = null; $item->ruleName = null;
} }
} }
$this->save(); $this->saveRules();
return true; return true;
} else { } else {
return false; return false;
...@@ -524,8 +544,8 @@ class PhpManager extends BaseManager ...@@ -524,8 +544,8 @@ class PhpManager extends BaseManager
*/ */
protected function addRule($rule) protected function addRule($rule)
{ {
$this->_rules[$rule->name] = $rule; $this->rules[$rule->name] = $rule;
$this->save(); $this->saveRules();
return true; return true;
} }
...@@ -534,25 +554,25 @@ class PhpManager extends BaseManager ...@@ -534,25 +554,25 @@ class PhpManager extends BaseManager
*/ */
protected function updateItem($name, $item) protected function updateItem($name, $item)
{ {
$this->_items[$item->name] = $item; $this->items[$item->name] = $item;
if ($name !== $item->name) { if ($name !== $item->name) {
if (isset($this->_items[$item->name])) { if (isset($this->items[$item->name])) {
throw new InvalidParamException("Unable to change the item name. The name '{$item->name}' is already used by another item."); throw new InvalidParamException("Unable to change the item name. The name '{$item->name}' is already used by another item.");
} }
if (isset($this->_items[$name])) { if (isset($this->items[$name])) {
unset ($this->_items[$name]); unset ($this->items[$name]);
if (isset($this->_children[$name])) { if (isset($this->children[$name])) {
$this->_children[$item->name] = $this->_children[$name]; $this->children[$item->name] = $this->children[$name];
unset ($this->_children[$name]); unset ($this->children[$name]);
} }
foreach ($this->_children as &$children) { foreach ($this->children as &$children) {
if (isset($children[$name])) { if (isset($children[$name])) {
$children[$item->name] = $children[$name]; $children[$item->name] = $children[$name];
unset ($children[$name]); unset ($children[$name]);
} }
} }
foreach ($this->_assignments as &$assignments) { foreach ($this->assignments as &$assignments) {
if (isset($assignments[$name])) { if (isset($assignments[$name])) {
$assignments[$item->name] = $assignments[$name]; $assignments[$item->name] = $assignments[$name];
unset($assignments[$name]); unset($assignments[$name]);
...@@ -560,7 +580,7 @@ class PhpManager extends BaseManager ...@@ -560,7 +580,7 @@ class PhpManager extends BaseManager
} }
} }
} }
$this->save(); $this->saveItems();
return true; return true;
} }
...@@ -577,9 +597,9 @@ class PhpManager extends BaseManager ...@@ -577,9 +597,9 @@ class PhpManager extends BaseManager
$item->updatedAt = $time; $item->updatedAt = $time;
} }
$this->_items[$item->name] = $item; $this->items[$item->name] = $item;
$this->save(); $this->saveItems();
return true; return true;
...@@ -588,95 +608,63 @@ class PhpManager extends BaseManager ...@@ -588,95 +608,63 @@ class PhpManager extends BaseManager
/** /**
* Loads authorization data from persistent storage. * Loads authorization data from persistent storage.
*/ */
public function load() protected function load()
{ {
$this->_children = []; $this->children = [];
$this->_rules = []; $this->rules = [];
$this->_assignments = []; $this->assignments = [];
$this->_items = []; $this->items = [];
$data = $this->loadFromFile($this->authFile); $items = $this->loadFromFile($this->itemsFile);
$itemsMtime = @filemtime($this->itemsFile);
$assignments = $this->loadFromFile($this->assignmentsFile);
$assignmentsMtime = @filemtime($this->assignmentsFile);
$rules = $this->loadFromFile($this->rulesFile);
if (isset($data['items'])) { foreach ($items as $name => $item) {
foreach ($data['items'] as $name => $item) {
$class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className(); $class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
$this->_items[$name] = new $class([ $this->items[$name] = new $class([
'name' => $name, 'name' => $name,
'description' => isset($item['description']) ? $item['description'] : null, 'description' => isset($item['description']) ? $item['description'] : null,
'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null, 'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null,
'data' => isset($item['data']) ? $item['data'] : null, 'data' => isset($item['data']) ? $item['data'] : null,
'createdAt' => isset($item['createdAt']) ? $item['createdAt'] : null, 'createdAt' => $itemsMtime,
'updatedAt' => isset($item['updatedAt']) ? $item['updatedAt'] : null, 'updatedAt' => $itemsMtime,
]); ]);
} }
foreach ($data['items'] as $name => $item) { foreach ($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])) {
$this->_children[$name][$childName] = $this->_items[$childName]; $this->children[$name][$childName] = $this->items[$childName];
}
} }
} }
} }
if (isset($item['assignments'])) {
foreach ($item['assignments'] as $userId => $assignment) { foreach ($assignments as $userId => $role) {
$this->_assignments[$userId][$name] = new Assignment([ $this->assignments[$userId][$role] = new Assignment([
'userId' => $userId, 'userId' => $userId,
'roleName' => $assignment['roleName'], 'roleName' => $role,
'createdAt' => isset($assignment['createdAt']) ? $assignment['createdAt'] : null, 'createdAt' => $assignmentsMtime,
]); ]);
} }
}
}
}
if (isset($data['rules'])) { foreach ($rules as $name => $ruleData) {
foreach ($data['rules'] as $name => $ruleData) { $this->rules[$name] = unserialize($ruleData);
$this->_rules[$name] = unserialize($ruleData);
}
} }
} }
/** /**
* Saves authorization data into persistent storage. * Saves authorization data into persistent storage.
*/ */
public function save() protected function save()
{ {
$items = []; $this->saveItems();
foreach ($this->_items as $name => $item) { $this->saveAssignments();
/* @var $item Item */ $this->saveRules();
$items[$name] = array_filter([
'type' => $item->type,
'description' => $item->description,
'ruleName' => $item->ruleName,
'data' => $item->data,
]);
if (isset($this->_children[$name])) {
foreach ($this->_children[$name] as $child) {
/* @var $child Item */
$items[$name]['children'][] = $child->name;
}
}
}
foreach ($this->_assignments as $userId => $assignments) {
foreach ($assignments as $name => $assignment) {
/* @var $assignment Assignment */
if (isset($items[$name])) {
$items[$name]['assignments'][$userId] = [
'roleName' => $assignment->roleName,
];
}
}
}
$rules = [];
foreach ($this->_rules as $name => $rule) {
$rules[$name] = serialize($rule);
}
$this->saveToFile(['items' => $items, 'rules' => $rules], $this->authFile);
} }
/** /**
...@@ -706,4 +694,57 @@ class PhpManager extends BaseManager ...@@ -706,4 +694,57 @@ class PhpManager extends BaseManager
{ {
file_put_contents($file, "<?php\nreturn " . VarDumper::export($data) . ";\n", LOCK_EX); file_put_contents($file, "<?php\nreturn " . VarDumper::export($data) . ";\n", LOCK_EX);
} }
/**
* Saves items data into persistent storage.
*/
protected function saveItems()
{
$items = [];
foreach ($this->items as $name => $item) {
/* @var $item Item */
$items[$name] = array_filter(
[
'type' => $item->type,
'description' => $item->description,
'ruleName' => $item->ruleName,
'data' => $item->data,
]
);
if (isset($this->children[$name])) {
foreach ($this->children[$name] as $child) {
/* @var $child Item */
$items[$name]['children'][] = $child->name;
}
}
}
$this->saveToFile($items, $this->itemsFile);
}
/**
* Saves assignments data into persistent storage.
*/
protected function saveAssignments()
{
$assignmentData = [];
foreach ($this->assignments as $userId => $assignments) {
foreach ($assignments as $name => $assignment) {
/* @var $assignment Assignment */
$assignmentData[$userId] = $assignment->roleName;
}
}
$this->saveToFile($assignmentData, $this->assignmentsFile);
}
/**
* Saves rules data into persistent storage.
*/
protected function saveRules()
{
$rules = [];
foreach ($this->rules as $name => $rule) {
$rules[$name] = serialize($rule);
}
$this->saveToFile($rules, $this->rulesFile);
}
} }
<?php
namespace yiiunit\framework\rbac;
use yii\rbac\PhpManager;
/**
* Exposes protected properties and methods to inspect from outside
*/
class ExposedPhpManager extends PhpManager
{
/**
* @var \yii\rbac\Item[]
*/
public $items = []; // itemName => item
/**
* @var array
*/
public $children = []; // itemName, childName => child
/**
* @var \yii\rbac\Assignment[]
*/
public $assignments = []; // userId, itemName => assignment
/**
* @var \yii\rbac\Rule[]
*/
public $rules = []; // ruleName => rule
public function load()
{
parent::load();
}
public function save()
{
parent::save();
}
}
\ No newline at end of file
...@@ -7,6 +7,9 @@ use yii\rbac\Permission; ...@@ -7,6 +7,9 @@ use yii\rbac\Permission;
use yii\rbac\Role; use yii\rbac\Role;
use yiiunit\TestCase; use yiiunit\TestCase;
/**
* ManagerTestCase
*/
abstract class ManagerTestCase extends TestCase abstract class ManagerTestCase extends TestCase
{ {
/** /**
...@@ -14,7 +17,7 @@ abstract class ManagerTestCase extends TestCase ...@@ -14,7 +17,7 @@ abstract class ManagerTestCase extends TestCase
*/ */
protected $auth; protected $auth;
public function testCreateRoleAndPermission() public function testCreateRole()
{ {
$role = $this->auth->createRole('admin'); $role = $this->auth->createRole('admin');
$this->assertTrue($role instanceof Role); $this->assertTrue($role instanceof Role);
...@@ -57,174 +60,6 @@ abstract class ManagerTestCase extends TestCase ...@@ -57,174 +60,6 @@ abstract class ManagerTestCase extends TestCase
$this->auth->addChild($user, $changeName); $this->auth->addChild($user, $changeName);
$this->assertCount(1, $this->auth->getChildren($user->name)); $this->assertCount(1, $this->auth->getChildren($user->name));
} }
/*
public function testRemove()
{
}
public function testUpdate()
{
}
public function testCreateItem()
{
$type = Item::TYPE_TASK;
$name = 'editUser';
$description = 'edit a user';
$ruleName = 'isAuthor';
$data = [1, 2, 3];
$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->ruleName, $ruleName);
$this->assertEquals($item->data, $data);
// test shortcut
$name2 = 'createUser';
$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, $ruleName, $data);
}
public function testGetItem()
{
$this->assertTrue($this->auth->getItem('readPost') instanceof Item);
$this->assertTrue($this->auth->getItem('reader') instanceof Item);
$this->assertNull($this->auth->getItem('unknown'));
}
public function testRemoveItem()
{
$this->assertTrue($this->auth->getItem('updatePost') instanceof Item);
$this->assertTrue($this->auth->removeItem('updatePost'));
$this->assertNull($this->auth->getItem('updatePost'));
$this->assertFalse($this->auth->removeItem('updatePost'));
}
public function testChangeItemName()
{
$item = $this->auth->getItem('readPost');
$this->assertTrue($item instanceof Item);
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost'));
$item->name = 'readPost2';
$item->save();
$this->assertNull($this->auth->getItem('readPost'));
$this->assertEquals($this->auth->getItem('readPost2'), $item);
$this->assertFalse($this->auth->hasItemChild('reader', 'readPost'));
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost2'));
}
public function testAddItemChild()
{
$this->auth->addItemChild('createPost', 'updatePost');
// test adding upper level item to lower one
$this->setExpectedException('\yii\base\Exception');
$this->auth->addItemChild('readPost', 'reader');
}
public function testAddItemChild2()
{
// test adding inexistent items
$this->setExpectedException('\yii\base\Exception');
$this->assertFalse($this->auth->addItemChild('createPost2', 'updatePost'));
}
public function testRemoveItemChild()
{
$this->assertTrue($this->auth->hasItemChild('reader', 'readPost'));
$this->assertTrue($this->auth->removeItemChild('reader', 'readPost'));
$this->assertFalse($this->auth->hasItemChild('reader', 'readPost'));
$this->assertFalse($this->auth->removeItemChild('reader', 'readPost'));
}
public function testGetItemChildren()
{
$this->assertEquals([], $this->auth->getItemChildren('readPost'));
$children = $this->auth->getItemChildren('author');
$this->assertEquals(3, count($children));
$this->assertTrue(reset($children) instanceof Item);
}
public function testAssign()
{
$auth = $this->auth->assign('new user', 'createPost', 'isAuthor', 'data');
$this->assertTrue($auth instanceof Assignment);
$this->assertEquals($auth->userId, 'new user');
$this->assertEquals($auth->itemName, 'createPost');
$this->assertEquals($auth->ruleName, 'isAuthor');
$this->assertEquals($auth->data, 'data');
$this->setExpectedException('\yii\base\Exception');
$this->auth->assign('new user', 'createPost2', 'rule', 'data');
}
public function testRevoke()
{
$this->assertTrue($this->auth->isAssigned('author B', 'author'));
$auth = $this->auth->getAssignment('author B', 'author');
$this->assertTrue($auth instanceof Assignment);
$this->assertTrue($this->auth->revoke('author B', 'author'));
$this->assertFalse($this->auth->isAssigned('author B', 'author'));
$this->assertFalse($this->auth->revoke('author B', 'author'));
}
public function testRevokeAll()
{
$this->assertTrue($this->auth->revokeAll('reader E'));
$this->assertFalse($this->auth->isAssigned('reader E', 'reader'));
}
public function testGetAssignments()
{
$this->auth->assign('author B', 'deletePost');
$auths = $this->auth->getAssignments('author B');
$this->assertEquals(2, count($auths));
$this->assertTrue(reset($auths) instanceof Assignment);
}
public function testGetItems()
{
$this->assertEquals(count($this->auth->getRoles()), 4);
$this->assertEquals(count($this->auth->getOperations()), 4);
$this->assertEquals(count($this->auth->getTasks()), 1);
$this->assertEquals(count($this->auth->getItems()), 9);
$this->assertEquals(count($this->auth->getItems('author B', null)), 1);
$this->assertEquals(count($this->auth->getItems('author C', null)), 0);
$this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_ROLE)), 1);
$this->assertEquals(count($this->auth->getItems('author B', Item::TYPE_OPERATION)), 0);
}
public function testClearAll()
{
$this->auth->clearAll();
$this->assertEquals(count($this->auth->getRoles()), 0);
$this->assertEquals(count($this->auth->getOperations()), 0);
$this->assertEquals(count($this->auth->getTasks()), 0);
$this->assertEquals(count($this->auth->getItems()), 0);
$this->assertEquals(count($this->auth->getAssignments('author B')), 0);
}
public function testClearAssignments()
{
$this->auth->clearAssignments();
$this->assertEquals(count($this->auth->getAssignments('author B')), 0);
}
public function testDetectLoop()
{
$this->setExpectedException('\yii\base\Exception');
$this->auth->addItemChild('readPost', 'readPost');
}
*/
public function testGetRule() public function testGetRule()
{ {
......
...@@ -3,38 +3,74 @@ ...@@ -3,38 +3,74 @@
namespace yiiunit\framework\rbac; namespace yiiunit\framework\rbac;
use Yii; use Yii;
use yii\rbac\PhpManager;
/** /**
* @group rbac * @group rbac
* @property \yii\rbac\PhpManager $auth * @property ExposedPhpManager $auth
*/ */
class PhpManagerTest extends ManagerTestCase class PhpManagerTest extends ManagerTestCase
{ {
protected function getItemsFile()
{
return Yii::$app->getRuntimePath() . '/rbac-items.php';
}
protected function getAssignmentsFile()
{
return Yii::$app->getRuntimePath() . '/rbac-assignments.php';
}
protected function getRulesFile()
{
return Yii::$app->getRuntimePath() . '/rbac-rules.php';
}
protected function removeDataFiles()
{
@unlink($this->getItemsFile());
@unlink($this->getAssignmentsFile());
@unlink($this->getRulesFile());
}
protected function createManager()
{
return new ExposedPhpManager([
'itemsFile' => $this->getItemsFile(),
'assignmentsFile' => $this->getAssignmentsFile(),
'rulesFile' => $this->getRulesFile(),
]);
}
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->mockApplication(); $this->mockApplication();
$authFile = Yii::$app->getRuntimePath() . '/rbac.php'; $this->removeDataFiles();
@unlink($authFile); $this->auth = $this->createManager();
$this->auth = new PhpManager();
$this->auth->authFile = $authFile;
$this->auth->init();
} }
protected function tearDown() protected function tearDown()
{ {
$this->removeDataFiles();
parent::tearDown(); parent::tearDown();
@unlink($this->auth->authFile);
} }
public function testSaveLoad() public function testSaveLoad()
{ {
$this->prepareData(); $this->prepareData();
$items = $this->auth->items;
$children = $this->auth->children;
$assignments = $this->auth->assignments;
$rules = $this->auth->rules;
$this->auth->save(); $this->auth->save();
$this->auth->removeAll();
$this->auth = $this->createManager();
$this->auth->load(); $this->auth->load();
// TODO : Check if loaded and saved data are the same.
}
$this->assertEquals($items, $this->auth->items);
$this->assertEquals($children, $this->auth->children);
$this->assertEquals($assignments, $this->auth->assignments);
$this->assertEquals($rules, $this->auth->rules);
}
} }
\ No newline at end of file
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