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
......
<?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