Commit 7bc52f32 by Qiang Xue

Re-implemented RBAC by following more closely to the original NIST RBAC model.…

Re-implemented RBAC by following more closely to the original NIST RBAC model. Dropped `yii\rbac\PhpManager`.
parent f7396fa0
......@@ -86,209 +86,3 @@ Role based access control (RBAC)
Role based access control is very flexible approach to controlling access that is a perfect match for complex systems
where permissions are customizable.
### Using file-based config for RBAC
In order to start using it some extra steps are required. First of all we need to configure `authManager` application
component in application config file (`web.php` or `main.php` depending on template you've used):
```php
'authManager' => [
'class' => 'app\components\PhpManager',
'defaultRoles' => ['guest'],
],
```
Often use role is stored in the same database table as other user data. In this case we may defined it by creating our
own component (`app/components/PhpManager.php`):
```php
<?php
namespace app\components;
use Yii;
class PhpManager extends \yii\rbac\PhpManager
{
public function init()
{
parent::init();
if (!Yii::$app->user->isGuest) {
// we suppose that user's role is stored in identity
$this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role);
}
}
}
```
Now create custom rule class:
```php
namespace app\rbac;
use yii\rbac\Rule;
use Yii;
class NotGuestRule extends Rule
{
public $name = 'notGuestRule';
public function execute($params, $data)
{
return !Yii::$app->user->isGuest;
}
}
```
Then create permissions hierarchy in `@app/data/rbac.php`:
```php
<?php
use yii\rbac\Item;
use app\rbac\NotGuestRule;
$notGuest = new NotGuestRule();
return [
'rules' => [
$notGuest->name => serialize($notGuest),
],
'items' => [
// HERE ARE YOUR MANAGEMENT TASKS
'manageThing0' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => null, 'data' => null],
'manageThing1' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => null, 'data' => null],
'manageThing2' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => null, 'data' => null],
'manageThing3' => ['type' => Item::TYPE_OPERATION, 'description' => '...', 'ruleName' => null, 'data' => null],
// AND THE ROLES
'guest' => [
'type' => Item::TYPE_ROLE,
'description' => 'Guest',
'ruleName' => null,
'data' => null
],
'user' => [
'type' => Item::TYPE_ROLE,
'description' => 'User',
'children' => [
'guest',
'manageThing0', // User can edit thing0
],
'ruleName' => $notGuest->name,
'data' => null
],
'moderator' => [
'type' => Item::TYPE_ROLE,
'description' => 'Moderator',
'children' => [
'user', // Can manage all that user can
'manageThing1', // and also thing1
],
'ruleName' => null,
'data' => null
],
'admin' => [
'type' => Item::TYPE_ROLE,
'description' => 'Admin',
'children' => [
'moderator', // can do all the stuff that moderator can
'manageThing2', // and also manage thing2
],
'ruleName' => null,
'data' => null
],
'godmode' => [
'type' => Item::TYPE_ROLE,
'description' => 'Super admin',
'children' => [
'admin', // can do all that admin can
'manageThing3', // and also thing3
],
'ruleName' => null,
'data' => null
],
],
];
```
Now you can specify roles from RBAC in controller's access control configuration:
```php
public function behaviors()
{
return [
'access' => [
'class' => 'yii\filters\AccessControl',
'except' => ['something'],
'rules' => [
[
'allow' => true,
'roles' => ['manageThing1'],
],
],
],
];
}
```
Another way is to call [[yii\web\User::checkAccess()]] where appropriate.
### Using DB-based storage for RBAC
Storing RBAC hierarchy in database is less efficient performancewise but is much more flexible. It is easier to create
a good management UI for it so in case you need permissions structure that is managed by end user DB is your choice.
In order to get started you need to configure database connection in `db` component. After it is done [get `schema-*.sql`
file for your database](https://github.com/yiisoft/yii2/tree/master/framework/rbac) and execute it.
Next step is to configure `authManager` application component in application config file (`web.php` or `main.php`
depending on template you've used):
```php
'authManager' => [
'class' => 'yii\rbac\DbManager',
'defaultRoles' => ['guest'],
],
```
TBD
### How it works
TBD: write about how it works with pictures :)
### Avoiding too much RBAC
In order to keep auth hierarchy simple and efficient you should avoid creating and using too much nodes. Most of the time
simple checks could be used instead. For example such code that uses RBAC:
```php
public function editArticle($id)
{
$article = Article::findOne($id);
if (!$article) {
throw new NotFoundHttpException;
}
if (!\Yii::$app->user->checkAccess('edit_article', ['article' => $article])) {
throw new ForbiddenHttpException;
}
// ...
}
```
can be replaced with simpler code that doesn't use RBAC:
```php
public function editArticle($id)
{
$article = Article::findOne(['id' => $id, 'author_id' => \Yii::$app->user->id]);
if (!$article) {
throw new NotFoundHttpException;
}
// ...
}
```
......@@ -282,6 +282,7 @@ Yii Framework 2 Change Log
- Chg: `yii\log\Logger` is split into `yii\log\Logger` and `yii\log\Dispatcher`. (qiangxue)
- Chg: Moved all filter classes to namespace `yii\filters` (qiangxue)
- Chg: Removed `Application::preload` in favor of `Application::bootstrap` (qiangxue)
- Chg: Re-implemented RBAC by following more closely to the original NIST RBAC model. Dropped `yii\rbac\PhpManager`. (qiangxue)
- New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul)
- New #303: Added built-in support for REST API (qiangxue)
- New #503: Added `yii\di\Container` and `yii\di\ServiceLocator` (qiangxue)
......
......@@ -12,9 +12,8 @@ use yii\base\Object;
/**
* Assignment represents an assignment of a role to a user.
* It includes additional assignment information such as [[ruleName]] and [[data]].
* Do not create a Assignment instance using the 'new' operator.
* Instead, call [[Manager::assign()]].
*
* It includes additional assignment information including [[ruleName]] and [[data]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
......@@ -23,33 +22,15 @@ use yii\base\Object;
class Assignment extends Object
{
/**
* @var Manager the auth manager of this item
*/
public $manager;
/**
* @var string name of the rule associated with this assignment
*/
public $ruleName;
/**
* @var mixed additional data for this assignment
*/
public $data;
/**
* @var mixed user ID (see [[\yii\web\User::id]]). Do not modify this property after it is populated.
* To modify the user ID of an assignment, you must remove the assignment and create a new one.
* @var string|integer user ID (see [[\yii\web\User::id]])
*/
public $userId;
/**
* @return string the authorization item name. Do not modify this property after it is populated.
* To modify the item name of an assignment, you must remove the assignment and create a new one.
* @return string the role name
*/
public $itemName;
public $roleName;
/**
* Saves the changes to an authorization assignment.
* @var integer UNIX timestamp representing the assignment creation time
*/
public function save()
{
$this->manager->saveAssignment($this);
}
public $createdAt;
}
<?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\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
/**
* BaseManager is a base class implementing [[ManagerInterface]] for RBAC management.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
abstract class BaseManager extends Component implements ManagerInterface
{
/**
* @var array a list of role names that are assigned to every user automatically without calling [[assign()]].
*/
public $defaultRoles = [];
/**
* Returns the named auth item.
* @param string $name the auth item name.
* @return Item the auth item corresponding to the specified name. Null is returned if no such item.
*/
abstract protected function getItem($name);
/**
* Returns the items of the specified type.
* @param integer $type the auth item type (either [[Item::TYPE_ROLE]] or [[Item::TYPE_PERMISSION]]
* @return Item[] the auth items of the specified type.
*/
abstract protected function getItems($type);
/**
* Adds an auth item to the RBAC system.
* @param Item $item
* @return boolean whether the auth item is successfully added to the system
* @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
*/
abstract protected function addItem($item);
/**
* Adds a rule to the RBAC system.
* @param Rule $rule
* @return boolean whether the rule is successfully added to the system
* @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
*/
abstract protected function addRule($rule);
/**
* Removes an auth item from the RBAC system.
* @param Item $item
* @return boolean whether the role or permission is successfully removed
* @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
*/
abstract protected function removeItem($item);
/**
* Removes a rule from the RBAC system.
* @param Rule $rule
* @return boolean whether the rule is successfully removed
* @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
*/
abstract protected function removeRule($rule);
/**
* Updates an auth item in the RBAC system.
* @param string $name the old name of the auth item
* @param Item $item
* @return boolean whether the auth item is successfully updated
* @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
*/
abstract protected function updateItem($name, $item);
/**
* Updates a rule to the RBAC system.
* @param string $name the old name of the rule
* @param Rule $rule
* @return boolean whether the rule is successfully updated
* @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
*/
abstract protected function updateRule($name, $rule);
/**
* @inheritdoc
*/
public function createRole($name)
{
$role = new Role;
$role->name = $name;
return $role;
}
/**
* @inheritdoc
*/
public function createPermission($name)
{
$permission = new Permission();
$permission->name = $name;
return $permission;
}
/**
* @inheritdoc
*/
public function add($object)
{
if ($object instanceof Item) {
return $this->addItem($object);
} elseif ($object instanceof Rule) {
return $this->addRule($object);
} else {
throw new InvalidParamException("Adding unsupported object type.");
}
}
/**
* @inheritdoc
*/
public function remove($object)
{
if ($object instanceof Item) {
return $this->removeItem($object);
} elseif ($object instanceof Rule) {
return $this->removeRule($object);
} else {
throw new InvalidParamException("Removing unsupported object type.");
}
}
/**
* @inheritdoc
*/
public function update($name, $object)
{
if ($object instanceof Item) {
return $this->updateItem($name, $object);
} elseif ($object instanceof Rule) {
return $this->updateRule($name, $object);
} else {
throw new InvalidParamException("Updating unsupported object type.");
}
}
/**
* @inheritdoc
*/
public function getRole($name)
{
$item = $this->getItem($name);
return $item instanceof Item && $item->type == Item::TYPE_ROLE ? $item : null;
}
/**
* @inheritdoc
*/
public function getPermission($name)
{
$item = $this->getItem($name);
return $item instanceof Item && $item->type == Item::TYPE_PERMISSION ? $item : null;
}
/**
* @inheritdoc
*/
public function getRoles()
{
return $this->getItems(Item::TYPE_ROLE);
}
/**
* @inheritdoc
*/
public function getPermissions()
{
return $this->getItems(Item::TYPE_PERMISSION);
}
/**
* Executes the rule associated with the specified auth item.
*
* If the item does not specify a rule, this method will return true. Otherwise, it will
* return the value of [[Rule::execute()]].
*
* @param Item $item the auth item that needs to execute its rule
* @param array $params parameters passed to [[ManagerInterface::checkAccess()]] and will be passed to the rule
* @return boolean the return value of [[Rule::execute()]]. If the auth item does not specify a rule, true will be returned.
* @throws InvalidConfigException if the auth item has an invalid rule.
*/
protected function executeRule($item, $params)
{
if ($item->ruleName === null) {
return true;
}
$rule = $this->getRule($item->ruleName);
if ($rule instanceof Rule) {
return $rule->execute($item, $params);
} else {
throw new InvalidConfigException("Rule not found: {$item->ruleName}");
}
}
}
......@@ -11,7 +11,6 @@ use Yii;
use yii\db\Connection;
use yii\db\Query;
use yii\db\Expression;
use yii\base\Exception;
use yii\base\InvalidCallException;
use yii\base\InvalidParamException;
use yii\di\Instance;
......@@ -24,14 +23,11 @@ use yii\di\Instance;
* the three tables used to store the authorization data by setting [[itemTable]],
* [[itemChildTable]] and [[assignmentTable]].
*
* @property Item[] $items The authorization items of the specific type. This property is read-only.
* @property Rule[] $rules This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class DbManager extends Manager
class DbManager extends BaseManager
{
/**
* @var Connection|string the DB connection object or the application component ID of the DB connection.
......@@ -60,7 +56,6 @@ class DbManager extends Manager
*/
public $ruleTable = '{{%auth_rule}}';
private $_usingSqlite;
/**
* Initializes the application component.
......@@ -69,613 +64,584 @@ class DbManager extends Manager
public function init()
{
parent::init();
$this->db = Instance::ensure($this->db, Connection::className());
$this->_usingSqlite = !strncmp($this->db->getDriverName(), 'sqlite', 6);
}
/**
* Performs access check for the specified user.
* @param mixed $userId the user ID. This should can be either an integer or a string representing
* the unique identifier of a user. See [[\yii\web\User::id]].
* @param string $itemName the name of the operation that need access check
* @param array $params name-value pairs that would be passed to rules associated
* with the tasks and roles assigned to the user. A param with name 'userId' is added to this array,
* which holds the value of `$userId`.
* @return boolean whether the operations can be performed by the user.
* @inheritdoc
*/
public function checkAccess($userId, $itemName, $params = [])
public function checkAccess($userId, $permissionName, $params = [])
{
$assignments = $this->getAssignments($userId);
return $this->checkAccessRecursive($userId, $itemName, $params, $assignments);
if (!isset($params['user'])) {
$params['user'] = $userId;
}
return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
}
/**
* Performs access check for the specified user.
* This method is internally called by [[checkAccess()]].
* @param mixed $userId the user ID. This should can be either an integer or a string representing
* @param string|integer $user 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]].
* @param string $itemName the name of the operation that need access check
* @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 'user' is added to this array,
* which holds the value of `$userId`.
* @param Assignment[] $assignments the assignments to the specified user
* @return boolean whether the operations can be performed by the user.
*/
protected function checkAccessRecursive($userId, $itemName, $params, $assignments)
protected function checkAccessRecursive($user, $itemName, $params, $assignments)
{
if (($item = $this->getItem($itemName)) === null) {
return false;
}
Yii::trace('Checking permission: ' . $item->getName(), __METHOD__);
if (!isset($params['userId'])) {
$params['userId'] = $userId;
}
if ($this->executeRule($item->ruleName, $params, $item->data)) {
if (in_array($itemName, $this->defaultRoles)) {
return true;
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
if (!$this->executeRule($item, $params)) {
return false;
}
if (isset($assignments[$itemName])) {
$assignment = $assignments[$itemName];
if ($this->executeRule($assignment->ruleName, $params, $assignment->data)) {
if (isset($this->defaultRoles[$itemName]) || isset($assignments[$itemName])) {
return true;
}
}
$query = new Query;
$parents = $query->select(['parent'])
->from($this->itemChildTable)
->where(['child' => $itemName])
->createCommand($this->db)
->queryColumn();
->column($this->db);
foreach ($parents as $parent) {
if ($this->checkAccessRecursive($userId, $parent, $params, $assignments)) {
if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
return true;
}
}
}
return false;
}
/**
* Adds an item as a child of another item.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the item is added successfully
* @throws Exception if either parent or child doesn't exist.
* @throws InvalidCallException if a loop has been detected.
* @inheritdoc
*/
public function addItemChild($itemName, $childName)
protected function getItem($name)
{
if ($itemName === $childName) {
throw new Exception("Cannot add '$itemName' as a child of itself.");
}
$query = new Query;
$rows = $query->from($this->itemTable)
->where(['or', 'name=:name1', 'name=:name2'], [':name1' => $itemName, ':name2' => $childName])
->createCommand($this->db)
->queryAll();
if (count($rows) == 2) {
if ($rows[0]['name'] === $itemName) {
$parentType = $rows[0]['type'];
$childType = $rows[1]['type'];
} else {
$childType = $rows[0]['type'];
$parentType = $rows[1]['type'];
}
$this->checkItemChildType($parentType, $childType);
if ($this->detectLoop($itemName, $childName)) {
throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected.");
$row = (new Query)->from($this->itemTable)
->where(['name' => $name])
->one($this->db);
if ($row === false) {
return null;
}
$this->db->createCommand()
->insert($this->itemChildTable, ['parent' => $itemName, 'child' => $childName])
->execute();
return true;
} else {
throw new Exception("Either '$itemName' or '$childName' does not exist.");
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
return $this->populateItem($row);
}
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the removal is successful
* Returns a value indicating whether the database supports cascading update and delete.
* The default implementation will return false for SQLite database and true for all other databases.
* @return boolean whether the database supports cascading update and delete.
*/
public function removeItemChild($itemName, $childName)
protected function supportsCascadeUpdate()
{
return $this->db->createCommand()
->delete($this->itemChildTable, ['parent' => $itemName, 'child' => $childName])
->execute() > 0;
return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0;
}
/**
* Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the child exists
* @inheritdoc
*/
public function hasItemChild($itemName, $childName)
protected function addItem($item)
{
$query = new Query;
$time = time();
if ($item->createdAt === null) {
$item->createdAt = $time;
}
if ($item->updatedAt === null) {
$item->updatedAt = $time;
}
$this->db->createCommand()
->insert($this->itemTable, [
'name' => $item->name,
'type' => $item->type,
'description' => $item->description,
'rule_name' => $item->ruleName,
'data' => $item->data === null ? null : serialize($item->data),
'created_at' => $item->createdAt,
'updated_at' => $item->updatedAt,
])->execute();
return $query->select(['parent'])
->from($this->itemChildTable)
->where(['parent' => $itemName, 'child' => $childName])
->createCommand($this->db)
->queryScalar() !== false;
return true;
}
/**
* Returns the children of the specified item.
* @param mixed $names the parent item name. This can be either a string or an array.
* The latter represents a list of item names.
* @return Item[] all child items of the parent
* @inheritdoc
*/
public function getItemChildren($names)
protected function removeItem($item)
{
$query = new Query;
$rows = $query->select(['name', 'type', 'description', 'rule_name', 'data'])
->from([$this->itemTable, $this->itemChildTable])
->where(['parent' => $names, 'name' => new Expression('child')])
->createCommand($this->db)
->queryAll();
$children = [];
foreach ($rows as $row) {
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
$children[$row['name']] = new Item([
'manager' => $this,
'name' => $row['name'],
'type' => $row['type'],
'description' => $row['description'],
'ruleName' => $row['rule_name'],
'data' => $data,
]);
if (!$this->supportsCascadeUpdate()) {
$this->db->createCommand()
->delete($this->itemChildTable, ['or', 'parent=:name', 'child=:name'], [':name' => $item->name])
->execute();
$this->db->createCommand()
->delete($this->assignmentTable, ['item_name' => $item->name])
->execute();
}
return $children;
$this->db->createCommand()
->delete($this->itemTable, ['name' => $item->name])
->execute();
return true;
}
/**
* Assigns an authorization item to a user.
*
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @param string $ruleName the business rule to be executed when [[checkAccess()]] is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information.
* @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user
*/
public function assign($userId, $itemName, $ruleName = null, $data = null)
* @inheritdoc
*/
protected function updateItem($name, $item)
{
if ($this->usingSqlite() && $this->getItem($itemName) === null) {
throw new InvalidParamException("The item '$itemName' does not exist.");
}
if (!$this->supportsCascadeUpdate() && $item->name !== $name) {
$this->db->createCommand()
->insert($this->assignmentTable, [
'user_id' => $userId,
'item_name' => $itemName,
'rule_name' => $ruleName,
'data' => $data === null ? null : serialize($data),
])
->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
->execute();
$this->db->createCommand()
->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
->execute();
$this->db->createCommand()
->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
->execute();
}
return new Assignment([
'manager' => $this,
'userId' => $userId,
'itemName' => $itemName,
'ruleName' => $ruleName,
'data' => $data,
]);
$item->updatedAt = time();
$this->db->createCommand()
->update($this->itemTable, [
'name' => $item->name,
'description' => $item->description,
'rule_name' => $item->ruleName,
'data' => $item->data === null ? null : serialize($item->data),
'updated_at' => $item->updatedAt,
], [
'name' => $name,
])->execute();
return true;
}
/**
* Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return boolean whether removal is successful
* @inheritdoc
*/
public function revoke($userId, $itemName)
protected function addRule($rule)
{
return $this->db->createCommand()
->delete($this->assignmentTable, ['user_id' => $userId, 'item_name' => $itemName])
->execute() > 0;
$time = time();
if ($rule->createdAt === null) {
$rule->createdAt = $time;
}
if ($rule->updatedAt === null) {
$rule->updatedAt = $time;
}
$this->db->createCommand()
->insert($this->ruleTable, [
'name' => $rule->name,
'data' => serialize($rule),
'created_at' => $rule->createdAt,
'updated_at' => $rule->updatedAt,
])->execute();
return true;
}
/**
* Revokes all authorization assignments from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether removal is successful
* @inheritdoc
*/
public function revokeAll($userId)
protected function updateRule($name, $rule)
{
return $this->db->createCommand()
->delete($this->assignmentTable, ['user_id' => $userId])
->execute() > 0;
if (!$this->supportsCascadeUpdate() && $rule->name !== $name) {
$this->db->createCommand()
->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
->execute();
}
$rule->updatedAt = time();
$this->db->createCommand()
->update($this->ruleTable, [
'name' => $rule->name,
'data' => serialize($rule),
'updated_at' => $rule->updatedAt,
], [
'name' => $name,
])->execute();
return true;
}
/**
* 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 string $itemName the item name
* @return boolean whether the item has been assigned to the user.
* @inheritdoc
*/
public function isAssigned($userId, $itemName)
protected function removeRule($rule)
{
$query = new Query;
if (!$this->supportsCascadeUpdate()) {
$this->db->createCommand()
->delete($this->itemTable, ['rule_name' => $rule->name])
->execute();
}
return $query->select(['item_name'])
->from($this->assignmentTable)
->where(['user_id' => $userId, 'item_name' => $itemName])
->createCommand($this->db)
->queryScalar() !== false;
$this->db->createCommand()
->delete($this->ruleTable, ['name' => $rule->name])
->execute();
return true;
}
/**
* Returns the item assignment information.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return Assignment the item assignment information. Null is returned if
* the item is not assigned to the user.
* @inheritdoc
*/
public function getAssignment($userId, $itemName)
protected function getItems($type)
{
$query = new Query;
$row = $query->from($this->assignmentTable)
->where(['user_id' => $userId, 'item_name' => $itemName])
->createCommand($this->db)
->queryOne();
if ($row !== false) {
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
$query = (new Query)
->from($this->itemTable)
->where(['type' => $type]);
return new Assignment([
'manager' => $this,
'userId' => $row['user_id'],
'itemName' => $row['item_name'],
'ruleName' => $row['rule_name'],
'data' => $data,
]);
} else {
return null;
$items = [];
foreach ($query->all($this->db) as $row) {
$items[$row['name']] = $this->populateItem($row);
}
return $items;
}
/**
* Returns the item assignments for the specified user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return Assignment[] the item assignment information for the user. An empty array will be
* returned if there is no item assigned to the user.
* Populates an auth item with the data fetched from database
* @param array $row the data from the auth item table
* @return Item the populated auth item instance (either Role or Permission)
*/
public function getAssignments($userId)
protected function populateItem($row)
{
$query = new Query;
$rows = $query->from($this->assignmentTable)
->where(['user_id' => $userId])
->createCommand($this->db)
->queryAll();
$assignments = [];
foreach ($rows as $row) {
$class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
$assignments[$row['item_name']] = new Assignment([
'manager' => $this,
'userId' => $row['user_id'],
'itemName' => $row['item_name'],
return new $class([
'name' => $row['name'],
'type' => $row['type'],
'description' => $row['description'],
'ruleName' => $row['rule_name'],
'data' => $data,
'createdAt' => $row['created_at'],
'updatedAt' => $row['updated_at'],
]);
}
return $assignments;
/**
* @inheritdoc
*/
public function getRolesByUser($userId)
{
$query = (new Query)->select('b.*')
->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
->where('a.item_name=b.name')
->andWhere(['a.user_id' => $userId]);
$roles = [];
foreach ($query->all($this->db) as $row) {
$roles[$row['name']] = $this->populateItem($row);
}
return $roles;
}
/**
* Saves the changes to an authorization assignment.
* @param Assignment $assignment the assignment that has been changed.
* @inheritdoc
*/
public function saveAssignment(Assignment $assignment)
public function getPermissionsByRole($roleName)
{
$this->db->createCommand()
->update($this->assignmentTable, [
'rule_name' => $assignment->ruleName,
'data' => $assignment->data === null ? null : serialize($assignment->data),
], [
'user_id' => $assignment->userId,
'item_name' => $assignment->itemName,
])
->execute();
$childrenList = $this->getChildrenList();
$result = [];
$this->getChildrenRecursive($roleName, $childrenList, $result);
if (empty($result)) {
return [];
}
$query = (new Query)->from($this->itemTable)->where([
'type' => Item::TYPE_PERMISSION,
'name' => array_keys($result),
]);
$permissions = [];
foreach ($query->all($this->db) as $row) {
$permissions[$row['name']] = $this->populateItem($row);
}
return $permissions;
}
/**
* Returns the authorization items of the specific type and user.
* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
* they are not assigned to a user.
* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
* meaning returning all items regardless of their type.
* @return Item[] the authorization items of the specific type.
* @inheritdoc
*/
public function getItems($userId = null, $type = null)
public function getPermissionsByUser($userId)
{
$query = new Query;
if ($userId === null && $type === null) {
$command = $query->from($this->itemTable)
->createCommand($this->db);
} elseif ($userId === null) {
$command = $query->from($this->itemTable)
->where(['type' => $type])
->createCommand($this->db);
} elseif ($type === null) {
$command = $query->select(['name', 'type', 'description', 't1.rule_name', 't1.data'])
->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2'])
->where(['user_id' => $userId, 'name' => new Expression('item_name')])
->createCommand($this->db);
} else {
$command = $query->select(['name', 'type', 'description', 't1.rule_name', 't1.data'])
->from([$this->itemTable . ' t1', $this->assignmentTable . ' t2'])
->where(['user_id' => $userId, 'type' => $type, 'name' => new Expression('item_name')])
->createCommand($this->db);
$query = (new Query)->select('item_name')
->from($this->assignmentTable)
->where(['user_id' => $userId]);
$childrenList = $this->getChildrenList();
$result = [];
foreach ($query->column($this->db) as $roleName) {
$this->getChildrenRecursive($roleName, $childrenList, $result);
}
$items = [];
foreach ($command->queryAll() as $row) {
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
if (empty($result)) {
return [];
}
$items[$row['name']] = new Item([
'manager' => $this,
'name' => $row['name'],
'type' => $row['type'],
'description' => $row['description'],
'ruleName' => $row['rule_name'],
'data' => $data,
$query = (new Query)->from($this->itemTable)->where([
'type' => Item::TYPE_PERMISSION,
'name' => array_keys($result),
]);
$permissions = [];
foreach ($query->all($this->db) as $row) {
$permissions[$row['name']] = $this->populateItem($row);
}
return $permissions;
}
return $items;
/**
* Returns the children for every parent.
* @return array the children list. Each array key is a parent item name,
* and the corresponding array value is a list of child item names.
*/
protected function getChildrenList()
{
$query = (new Query)->from($this->itemChildTable);
$parents = [];
foreach ($query->all($this->db) as $row) {
$parents[$row['parent']][] = $row['child'];
}
return $parents;
}
/**
* Creates an authorization item.
* An authorization item represents an action permission (e.g. creating a post).
* It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items.
*
* @param string $name the item name. This must be a unique identifier.
* @param integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item
* @param string $rule business rule associated with the item. This is a piece of
* PHP code that will be executed when [[checkAccess()]] is called for the item.
* @param mixed $data additional data associated with the item.
* @return Item the authorization item
* @throws Exception if an item with the same name already exists
*/
public function createItem($name, $type, $description = '', $rule = null, $data = null)
* Recursively finds all children and grand children of the specified item.
* @param string $name the name of the item whose children are to be looked for.
* @param array $childrenList the child list built via [[getChildrenList()]]
* @param array $result the children and grand children (in array keys)
*/
protected function getChildrenRecursive($name, $childrenList, &$result)
{
$this->db->createCommand()
->insert($this->itemTable, [
'name' => $name,
'type' => $type,
'description' => $description,
'rule_name' => $rule,
'data' => $data === null ? null : serialize($data),
])
->execute();
if (isset($childrenList[$name])) {
foreach ($childrenList[$name] as $child) {
$result[$child] = true;
$this->getChildrenRecursive($child, $childrenList, $result);
}
}
}
return new Item([
'manager' => $this,
'name' => $name,
'type' => $type,
'description' => $description,
'ruleName' => $rule,
'data' => $data,
]);
/**
* @inheritdoc
*/
public function getRule($name)
{
$row = (new Query)->select(['data'])
->from($this->ruleTable)
->where(['name' => $name])
->one($this->db);
return $row === false ? null : unserialize($row['data']);
}
/**
* Removes the specified authorization item.
* @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed
* @inheritdoc
*/
public function removeItem($name)
public function getRules()
{
if ($this->usingSqlite()) {
$this->db->createCommand()
->delete($this->itemChildTable, ['or', 'parent=:name', 'child=:name'], [':name' => $name])
->execute();
$this->db->createCommand()
->delete($this->assignmentTable, ['item_name' => $name])
->execute();
$query = (new Query)->from($this->ruleTable);
$rules = [];
foreach ($query->all($this->db) as $row) {
$rules[$row['name']] = unserialize($row['data']);
}
return $this->db->createCommand()
->delete($this->itemTable, ['name' => $name])
->execute() > 0;
return $rules;
}
/**
* Returns the authorization item with the specified name.
* @param string $name the name of the item
* @return Item the authorization item. Null if the item cannot be found.
* @inheritdoc
*/
public function getItem($name)
public function getAssignment($roleName, $userId)
{
$query = new Query;
$row = $query->from($this->itemTable)
->where(['name' => $name])
->createCommand($this->db)
->queryOne();
$row = (new Query)->from($this->assignmentTable)
->where(['user_id' => $userId, 'item_name' => $roleName])
->one($this->db);
if ($row === false) {
return null;
}
if ($row !== false) {
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
return new Item([
'manager' => $this,
'name' => $row['name'],
'type' => $row['type'],
'description' => $row['description'],
'ruleName' => $row['rule_name'],
'data' => $data,
return new Assignment([
'userId' => $row['user_id'],
'roleName' => $row['item_name'],
'createdAt' => $row['created_at'],
]);
} else {
return null;
}
}
/**
* Saves an authorization item to persistent storage.
* @param Item $item the item to be saved.
* @param string $oldName the old item name. If null, it means the item name is not changed.
* @inheritdoc
*/
public function saveItem(Item $item, $oldName = null)
public function getAssignments($userId)
{
if ($this->usingSqlite() && $oldName !== null && $item->getName() !== $oldName) {
$this->db->createCommand()
->update($this->itemChildTable, ['parent' => $item->getName()], ['parent' => $oldName])
->execute();
$this->db->createCommand()
->update($this->itemChildTable, ['child' => $item->getName()], ['child' => $oldName])
->execute();
$this->db->createCommand()
->update($this->assignmentTable, ['item_name' => $item->getName()], ['item_name' => $oldName])
->execute();
$query = (new Query)
->from($this->assignmentTable)
->where(['user_id' => $userId]);
$assignments = [];
foreach ($query->all($this->db) as $row) {
if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
$data = null;
}
$assignments[$row['item_name']] = new Assignment([
'userId' => $row['user_id'],
'roleName' => $row['item_name'],
'createdAt' => $row['created_at'],
]);
}
$this->db->createCommand()
->update($this->itemTable, [
'name' => $item->getName(),
'type' => $item->type,
'description' => $item->description,
'rule_name' => $item->ruleName,
'data' => $item->data === null ? null : serialize($item->data),
], [
'name' => $oldName === null ? $item->getName() : $oldName,
])
->execute();
return $assignments;
}
/**
* Saves the authorization data to persistent storage.
* @inheritdoc
*/
public function save()
public function addChild($parent, $child)
{
if ($parent->name === $child->name) {
throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
}
if ($parent instanceof Permission && $child instanceof Role) {
throw new InvalidParamException("Cannot add a role as a child of a permission.");
}
if ($this->detectLoop($parent, $child)) {
throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
}
$this->db->createCommand()
->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
->execute();
return true;
}
/**
* Removes all authorization data.
* @inheritdoc
*/
public function clearAll()
public function removeChild($parent, $child)
{
$this->clearAssignments();
$this->db->createCommand()->delete($this->itemChildTable)->execute();
$this->db->createCommand()->delete($this->itemTable)->execute();
return $this->db->createCommand()
->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name])
->execute() > 0;
}
/**
* Removes all authorization assignments.
* @inheritdoc
*/
public function clearAssignments()
public function getChildren($name)
{
$this->db->createCommand()->delete($this->assignmentTable)->execute();
$query = (new Query)
->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at'])
->from([$this->itemTable, $this->itemChildTable])
->where(['parent' => $name, 'name' => new Expression('child')]);
$children = [];
foreach ($query->all($this->db) as $row) {
$children[$row['name']] = $this->populateItem($row);
}
return $children;
}
/**
* Checks whether there is a loop in the authorization item hierarchy.
* @param string $itemName parent item name
* @param string $childName the name of the child item that is to be added to the hierarchy
* @param Item $parent the parent item
* @param Item $child the child item to be added to the hierarchy
* @return boolean whether a loop exists
*/
protected function detectLoop($itemName, $childName)
protected function detectLoop($parent, $child)
{
if ($childName === $itemName) {
if ($child->name === $parent->name) {
return true;
}
foreach ($this->getItemChildren($childName) as $child) {
if ($this->detectLoop($itemName, $child->getName())) {
foreach ($this->getChildren($child->name) as $grandchild) {
if ($this->detectLoop($parent, $grandchild)) {
return true;
}
}
return false;
}
/**
* @return boolean whether the database is a SQLite database
* @inheritdoc
*/
protected function usingSqlite()
public function assign($role, $userId, $rule = null, $data = null)
{
return $this->_usingSqlite;
}
$assignment = new Assignment([
'userId' => $userId,
'roleName' => $role->name,
'createdAt' => time(),
]);
/**
* 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();
$this->db->createCommand()
->insert($this->assignmentTable, [
'user_id' => $assignment->userId,
'item_name' => $assignment->roleName,
'created_at' => $assignment->createdAt,
])->execute();
return $assignment;
}
/**
* Saves the changes to the rule.
*
* @param Rule $rule the rule that has been changed.
* @inheritdoc
*/
public function insertRule(Rule $rule)
public function revoke($role, $userId)
{
$this->db->createCommand()->insert($this->ruleTable, ['name' => $rule->name, 'data' => serialize($rule)])->execute();
return $this->db->createCommand()
->delete($this->assignmentTable, ['user_id' => $userId, 'item_name' => $role->name])
->execute() > 0;
}
/**
* Updates existing rule.
*
* @param string $name the name of the rule to update
* @param Rule $rule new rule
* @inheritdoc
*/
public function updateRule($name, Rule $rule)
public function revokeAll($userId)
{
$this->db->createCommand()->update($this->ruleTable, ['name' => $rule->name, 'data' => serialize($rule)], ['name' => $name])->execute();
return $this->db->createCommand()
->delete($this->assignmentTable, ['user_id' => $userId])
->execute() > 0;
}
/**
* Returns rule given its name.
*
* @param string $name name of the rule.
* @return Rule
* Removes all authorization data.
*/
public function getRule($name)
public function clearAll()
{
$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']);
$this->clearAssignments();
$this->db->createCommand()->delete($this->itemChildTable)->execute();
$this->db->createCommand()->delete($this->itemTable)->execute();
$this->db->createCommand()->delete($this->ruleTable)->execute();
}
/**
* Returns all rules.
*
* @return Rule[]
* Removes all authorization assignments.
*/
public function getRules()
public function clearAssignments()
{
$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;
$this->db->createCommand()->delete($this->assignmentTable)->execute();
}
}
......@@ -7,34 +7,25 @@
namespace yii\rbac;
use Yii;
use yii\base\Object;
/**
* Item represents an authorization item.
* An authorization item can be an operation, a task or a role.
* They form an authorization hierarchy. Items on higher levels of the hierarchy
* inherit the permissions represented by items on lower levels.
* A user may be assigned one or several authorization items (called [[Assignment]] assignments).
* He can perform an operation only when it is among his assigned items.
*
* @property Item[] $children All child items of this item. This property is read-only.
* @property string $name The item name.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class Item extends Object
{
const TYPE_OPERATION = 0;
const TYPE_TASK = 1;
const TYPE_ROLE = 2;
const TYPE_ROLE = 1;
const TYPE_PERMISSION = 2;
/**
* @var Manager the auth manager of this item
* @var integer the type of the item. This should be either [[TYPE_ROLE]] or [[TYPE_PERMISSION]].
*/
public $manager;
public $type;
/**
* @var string the name of the item. This must be globally unique.
*/
public $name;
/**
* @var string the item description
*/
......@@ -48,158 +39,11 @@ class Item extends Object
*/
public $data;
/**
* @var integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role).
*/
public $type;
private $_name;
private $_oldName;
/**
* Checks to see if the specified item is within the hierarchy starting from this item.
* This method is expected to be internally used by the actual implementations
* of the [[Manager::checkAccess()]].
* @param string $itemName the name of the item to be checked
* @param array $params the parameters to be passed to business rule evaluation
* @return boolean whether the specified item is within the hierarchy starting from this item.
*/
public function checkAccess($itemName, $params = [])
{
Yii::trace('Checking permission: ' . $this->_name, __METHOD__);
if ($this->manager->executeRule($this->ruleName, $params, $this->data)) {
if ($this->_name == $itemName) {
return true;
}
foreach ($this->manager->getItemChildren($this->_name) as $item) {
if ($item->checkAccess($itemName, $params)) {
return true;
}
}
}
return false;
}
/**
* @return string the item name
*/
public function getName()
{
return $this->_name;
}
/**
* @param string $value the item name
*/
public function setName($value)
{
if ($this->_name !== $value) {
$this->_oldName = $this->_name;
$this->_name = $value;
}
}
/**
* Adds a child item.
* @param string $name the name of the child item
* @return boolean whether the item is added successfully
* @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected.
* @see Manager::addItemChild
*/
public function addChild($name)
{
return $this->manager->addItemChild($this->_name, $name);
}
/**
* Removes a child item.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $name the child item name
* @return boolean whether the removal is successful
* @see Manager::removeItemChild
*/
public function removeChild($name)
{
return $this->manager->removeItemChild($this->_name, $name);
}
/**
* Returns a value indicating whether a child exists
* @param string $name the child item name
* @return boolean whether the child exists
* @see Manager::hasItemChild
*/
public function hasChild($name)
{
return $this->manager->hasItemChild($this->_name, $name);
}
/**
* Returns the children of this item.
* @return Item[] all child items of this item.
* @see Manager::getItemChildren
*/
public function getChildren()
{
return $this->manager->getItemChildren($this->_name);
}
/**
* Assigns this item to a user.
*
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param Rule $rule the rule to be executed when [[checkAccess()]] is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information.
* @throws \yii\base\Exception if the item has already been assigned to the user
* @see Manager::assign
* @var integer UNIX timestamp representing the item creation time
*/
public function assign($userId, Rule $rule = null, $data = null)
{
return $this->manager->assign($userId, $this->_name, $rule, $data);
}
/**
* Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether removal is successful
* @see Manager::revoke
*/
public function revoke($userId)
{
return $this->manager->revoke($userId, $this->_name);
}
/**
* Returns a value indicating whether this item has been assigned to the user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether the item has been assigned to the user.
* @see Manager::isAssigned
*/
public function isAssigned($userId)
{
return $this->manager->isAssigned($userId, $this->_name);
}
/**
* Returns the item assignment information.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return Assignment the item assignment information. Null is returned if
* this item is not assigned to the user.
* @see Manager::getAssignment
*/
public function getAssignment($userId)
{
return $this->manager->getAssignment($userId, $this->_name);
}
public $createdAt;
/**
* Saves an authorization item to persistent storage.
* @var integer UNIX timestamp representing the item updating time
*/
public function save()
{
$this->manager->saveItem($this, $this->_oldName);
$this->_oldName = null;
}
public $updatedAt;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\rbac;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
/**
* Manager is the base class for authorization manager classes.
*
* Manager extends [[Component]] and implements some methods
* that are common among authorization manager classes.
*
* Manager together with its concrete child classes implement the Role-Based
* Access Control (RBAC).
*
* The main idea is that permissions are organized as a hierarchy of
* [[Item]] authorization items. Items on higher level inherit the permissions
* represented by items on lower level. And roles are simply top-level authorization items
* that may be assigned to individual users. A user is said to have a permission
* to do something if the corresponding authorization item is inherited by one of his roles.
*
* Using authorization manager consists of two aspects. First, the authorization hierarchy
* and assignments have to be established. Manager and its child classes
* provides APIs to accomplish this task. Developers may need to develop some GUI
* so that it is more intuitive to end-users. Second, developers call [[Manager::checkAccess()]]
* at appropriate places in the application code to check if the current user
* has the needed permission for an operation.
*
* @property Item[] $operations Operations (name => AuthItem). This property is read-only.
* @property Item[] $roles Roles (name => AuthItem). This property is read-only.
* @property Item[] $tasks Tasks (name => AuthItem). This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
abstract class Manager extends Component
{
/**
* @var array list of role names that are assigned to all users implicitly.
* These roles do not need to be explicitly assigned to any user.
* When calling [[checkAccess()]], these roles will be checked first.
* For performance reason, you should minimize the number of such roles.
* A typical usage of such roles is to define an 'authenticated' role and associate
* it with a rule which checks if the current user is authenticated.
* And then declare 'authenticated' in this property so that it can be applied to
* every authenticated user.
*/
public $defaultRoles = [];
/**
* Creates a role.
* This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name
* @param string $description the item description.
* @param string $ruleName name of the rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item
*/
public function createRole($name, $description = '', $ruleName = null, $data = null)
{
return $this->createItem($name, Item::TYPE_ROLE, $description, $ruleName, $data);
}
/**
* Creates a task.
* This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name
* @param string $description the item description.
* @param string $ruleName name of the rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item
*/
public function createTask($name, $description = '', $ruleName = null, $data = null)
{
return $this->createItem($name, Item::TYPE_TASK, $description, $ruleName, $data);
}
/**
* Creates an operation.
* This is a shortcut method to [[Manager::createItem()]].
*
* @param string $name the item name
* @param string $description the item description.
* @param string $ruleName name of the rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return Item the authorization item
*/
public function createOperation($name, $description = '', $ruleName = null, $data = null)
{
return $this->createItem($name, Item::TYPE_OPERATION, $description, $ruleName, $data);
}
/**
* Returns roles.
* This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the roles directly assigned to the user
* will be returned. Otherwise, all roles will be returned.
* @return Item[] roles (name => AuthItem)
*/
public function getRoles($userId = null)
{
return $this->getItems($userId, Item::TYPE_ROLE);
}
/**
* Returns tasks.
* This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user
* will be returned. Otherwise, all tasks will be returned.
* @return Item[] tasks (name => AuthItem)
*/
public function getTasks($userId = null)
{
return $this->getItems($userId, Item::TYPE_TASK);
}
/**
* Returns operations.
* This is a shortcut method to [[Manager::getItems()]].
*
* @param mixed $userId the user ID. If not null, only the operations directly assigned to the user
* will be returned. Otherwise, all operations will be returned.
* @return Item[] operations (name => AuthItem)
*/
public function getOperations($userId = null)
{
return $this->getItems($userId, Item::TYPE_OPERATION);
}
/**
* Executes the specified rule.
*
* @param string $ruleName name of the rule to be executed.
* @param array $params parameters passed to [[Manager::checkAccess()]].
* @param mixed $data additional data associated with the authorization item or assignment.
* @return boolean whether the rule execution returns true. If `$ruleName` is null, true will be returned.
* @throws InvalidConfigException if `$ruleName` does not correspond to a valid rule.
*/
public function executeRule($ruleName, $params, $data)
{
if ($ruleName === null) {
return true;
}
$rule = $this->getRule($ruleName);
if ($rule instanceof Rule) {
return $rule->execute($params, $data);
} else {
throw new InvalidConfigException("Rule not found: $ruleName");
}
}
/**
* Checks the item types to make sure a child can be added to a parent.
* @param integer $parentType parent item type
* @param integer $childType child item type
* @throws InvalidParamException if the item cannot be added as a child due to its incompatible type.
*/
protected function checkItemChildType($parentType, $childType)
{
static $types = ['operation', 'task', 'role'];
if ($parentType < $childType) {
throw new InvalidParamException("Cannot add an item of type '{$types[$childType]}' to an item of type '{$types[$parentType]}'.");
}
}
/**
* Performs access check for the specified user.
* @param mixed $userId the user ID. This should be either an integer or a string representing
* the unique identifier of a user. See [[\yii\web\User::id]].
* @param string $itemName the name of the operation that we are checking access to
* @param array $params name-value pairs that would be passed to rules associated
* with the tasks and roles assigned to the user.
* @return boolean whether the operations can be performed by the user.
*/
abstract public function checkAccess($userId, $itemName, $params = []);
/**
* Creates an authorization item.
* An authorization item represents an action permission (e.g. creating a post).
* It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items.
*
* @param string $name the item name. This must be a unique identifier.
* @param integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item
* @param string $ruleName name of the rule associated with the item.
* @param mixed $data additional data associated with the item.
* @throws \yii\base\Exception if an item with the same name already exists
* @return Item the authorization item
*/
abstract public function createItem($name, $type, $description = '', $ruleName = null, $data = null);
/**
* Removes the specified authorization item.
* @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed
*/
abstract public function removeItem($name);
/**
* Returns the authorization items of the specific type and user.
* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
* they are not assigned to a user.
* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
* meaning returning all items regardless of their type.
* @return Item[] the authorization items of the specific type.
*/
abstract public function getItems($userId = null, $type = null);
/**
* Returns the authorization item with the specified name.
* @param string $name the name of the item
* @return Item the authorization item. Null if the item cannot be found.
*/
abstract public function getItem($name);
/**
* Saves an authorization item to persistent storage.
* @param Item $item the item to be saved.
* @param string $oldName the old item name. If null, it means the item name is not changed.
*/
abstract public function saveItem(Item $item, $oldName = null);
/**
* Adds an item as a child of another item.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @throws \yii\base\Exception if either parent or child doesn't exist or if a loop has been detected.
*/
abstract public function addItemChild($itemName, $childName);
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the removal is successful
*/
abstract public function removeItemChild($itemName, $childName);
/**
* Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the child exists
*/
abstract public function hasItemChild($itemName, $childName);
/**
* Returns the children of the specified item.
* @param mixed $itemName the parent item name. This can be either a string or an array.
* The latter represents a list of item names.
* @return Item[] all child items of the parent
*/
abstract public function getItemChildren($itemName);
/**
* Assigns an authorization item to a user.
*
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @param string $ruleName name of the rule to be executed when [[checkAccess()]] is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information.
* @throws \yii\base\Exception if the item does not exist or if the item has already been assigned to the user
*/
abstract public function assign($userId, $itemName, $ruleName = null, $data = null);
/**
* Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return boolean whether removal is successful
*/
abstract public function revoke($userId, $itemName);
/**
* Revokes all authorization assignments from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether removal is successful
*/
abstract public function revokeAll($userId);
/**
* Returns a value indicating whether the item has been assigned to the user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return boolean whether the item has been assigned to the user.
*/
abstract public function isAssigned($userId, $itemName);
/**
* Returns the item assignment information.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return Assignment the item assignment information. Null is returned if
* the item is not assigned to the user.
*/
abstract public function getAssignment($userId, $itemName);
/**
* Returns the item assignments for the specified user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return Item[] the item assignment information for the user. An empty array will be
* returned if there is no item assigned to the user.
*/
abstract public function getAssignments($userId);
/**
* 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.
* @param Assignment $assignment the assignment that has been changed.
*/
abstract public function saveAssignment(Assignment $assignment);
/**
* Removes all authorization data.
*/
abstract public function clearAll();
/**
* Removes all authorization assignments.
*/
abstract public function clearAssignments();
/**
* Saves authorization data into persistent storage.
* If any change is made to the authorization data, please make
* sure you call this method to save the changed data into persistent storage.
*/
abstract public function save();
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\rbac;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface ManagerInterface
{
/**
* Checks if the user has the specified permission.
* @param string|integer $userId the user ID. This should be either an integer or a string representing
* the unique identifier of a user. See [[\yii\web\User::id]].
* @param string $permissionName the name of the permission to be checked against
* @param array $params name-value pairs that will be passed to the rules associated
* with the roles and permissions assigned to the user.
* @return boolean whether the user has the specified permission.
* @throws \yii\base\InvalidParamException if $permissionName does not refer to an existing permission
*/
public function checkAccess($userId, $permissionName, $params = []);
/**
* Creates a new Role object.
* Note that the newly created role is not added to the RBAC system yet.
* You must fill in the needed data and call [[add()]] to add it to the system.
* @param string $name the role name
* @return Role the new Role object
*/
public function createRole($name);
/**
* Creates a new Permission object.
* Note that the newly created permission is not added to the RBAC system yet.
* You must fill in the needed data and call [[add()]] to add it to the system.
* @param string $name the permission name
* @return Permission the new Permission object
*/
public function createPermission($name);
/**
* Adds a role, permission or rule to the RBAC system.
* @param Role|Permission|Rule $object
* @return boolean whether the role, permission or rule is successfully added to the system
* @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
*/
public function add($object);
/**
* Removes a role, permission or rule from the RBAC system.
* @param Role|Permission|Rule $object
* @return boolean whether the role, permission or rule is successfully removed
*/
public function remove($object);
/**
* Updates the specified role, permission or rule in the system.
* @param string $name the old name of the role, permission or rule
* @param Role|Permission|Rule $object
* @return boolean whether the update is successful
* @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
*/
public function update($name, $object);
/**
* Returns the named role.
* @param string $name the role name.
* @return Role the role corresponding to the specified name. Null is returned if no such role.
*/
public function getRole($name);
/**
* Returns all roles in the system.
* @return Role[] all roles in the system. The array is indexed by the role names.
*/
public function getRoles();
/**
* Returns the roles that are assigned to the user via [[assign()]].
* Note that child roles that are not assigned directly to the user will not be returned.
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @return Role[] all roles directly or indirectly assigned to the user. The array is indexed by the role names.
*/
public function getRolesByUser($userId);
/**
* Returns the named permission.
* @param string $name the permission name.
* @return Permission the permission corresponding to the specified name. Null is returned if no such permission.
*/
public function getPermission($name);
/**
* Returns all permissions in the system.
* @return Permission[] all permissions in the system. The array is indexed by the permission names.
*/
public function getPermissions();
/**
* Returns all permissions that the specified role represents.
* @param string $roleName the role name
* @return Permission[] all permissions that the role represents. The array is indexed by the permission names.
*/
public function getPermissionsByRole($roleName);
/**
* Returns all permissions that the user has.
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @return Permission[] all permissions that the user has. The array is indexed by the permission names.
*/
public function getPermissionsByUser($userId);
/**
* Returns the rule of the specified name.
* @param string $name the rule name
* @return Rule the rule object, or null if the specified name does not correspond to a rule.
*/
public function getRule($name);
/**
* Returns all rules available in the system.
* @return Rule[] the rules indexed by the rule names
*/
public function getRules();
/**
* Adds an item as a child of another item.
* @param Item $parent
* @param Item $child
* @throws \yii\base\Exception if the parent-child relationship already exists or if a loop has been detected.
*/
public function addChild($parent, $child);
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param Item $parent
* @param Item $child
* @return boolean whether the removal is successful
*/
public function removeChild($parent, $child);
/**
* Returns the child permissions and/or roles.
* @param string $name the parent name
* @return Item[] the child permissions and/or roles
*/
public function getChildren($name);
/**
* Assigns a role to a user.
*
* @param Role $role
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @param Rule $rule the rule to be associated with this assignment. If not null, the rule
* will be executed when [[allow()]] is called to check the user permission.
* @param mixed $data additional data associated with this assignment.
* @return Assignment the role assignment information.
* @throws \Exception if the role has already been assigned to the user
*/
public function assign($role, $userId, $rule = null, $data = null);
/**
* Revokes a role from a user.
* @param Role $role
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether the revoking is successful
*/
public function revoke($role, $userId);
/**
* Revokes all roles from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether the revoking is successful
*/
public function revokeAll($userId);
/**
* Returns the assignment information regarding a role and a user.
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @param string $roleName the role name
* @return Assignment the assignment information. Null is returned if
* the role is not assigned to the user.
*/
public function getAssignment($roleName, $userId);
/**
* Returns all role assignment information for the specified user.
* @param string|integer $userId the user ID (see [[\yii\web\User::id]])
* @return Assignment[] the assignments indexed by role names. An empty array will be
* returned if there is no role assigned to the user.
*/
public function getAssignments($userId);
/**
* Removes all authorization data.
*/
public function clearAll();
/**
* Removes all authorization assignments.
*/
public function clearAssignments();
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\rbac;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Permission extends Item
{
/**
* @inheritdoc
*/
public $type = self::TYPE_PERMISSION;
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\rbac;
use Yii;
use yii\base\Exception;
use yii\base\InvalidCallException;
use yii\base\InvalidParamException;
/**
* PhpManager represents an authorization manager that stores authorization
* information in terms of a PHP script file.
*
* The authorization data will be saved to and loaded from a file
* specified by [[authFile]], which defaults to 'protected/data/rbac.php'.
*
* PhpManager is mainly suitable for authorization data that is not too big
* (for example, the authorization data for a personal blog system).
* Use [[DbManager]] for more complex authorization data.
*
* @property Item[] $items The authorization items of the specific type. This property is read-only.
* @property Rule[] $rules This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Alexander Kochetov <creocoder@gmail.com>
* @since 2.0
*/
class PhpManager extends Manager
{
/**
* @var string the path of the PHP script that contains the authorization data.
* 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 $authFile = '@app/data/rbac.php';
private $_items = []; // itemName => item
private $_children = []; // itemName, childName => child
private $_assignments = []; // userId, itemName => assignment
private $_rules = []; // ruleName => rule
/**
* Initializes the application component.
* This method overrides parent implementation by loading the authorization data
* from PHP script.
*/
public function init()
{
parent::init();
$this->authFile = Yii::getAlias($this->authFile);
$this->load();
}
/**
* Performs access check for the specified user.
* @param mixed $userId the user ID. This can be either an integer or a string representing
* @param string $itemName the name of the operation that need access check
* the unique identifier of a user. See [[\yii\web\User::id]].
* @param array $params name-value pairs that would be passed to rules associated
* with the tasks and roles assigned to the user. A param with name 'userId' is added to
* this array, which holds the value of `$userId`.
* @return boolean whether the operations can be performed by the user.
*/
public function checkAccess($userId, $itemName, $params = [])
{
if (!isset($this->_items[$itemName])) {
return false;
}
/** @var Item $item */
$item = $this->_items[$itemName];
Yii::trace('Checking permission: ' . $item->getName(), __METHOD__);
if (!isset($params['userId'])) {
$params['userId'] = $userId;
}
if ($this->executeRule($item->ruleName, $params, $item->data)) {
if (in_array($itemName, $this->defaultRoles)) {
return true;
}
if (isset($this->_assignments[$userId][$itemName])) {
/** @var Assignment $assignment */
$assignment = $this->_assignments[$userId][$itemName];
if ($this->executeRule($assignment->ruleName, $params, $assignment->data)) {
return true;
}
}
foreach ($this->_children as $parentName => $children) {
if (isset($children[$itemName]) && $this->checkAccess($userId, $parentName, $params)) {
return true;
}
}
}
return false;
}
/**
* Adds an item as a child of another item.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the item is added successfully
* @throws Exception if either parent or child doesn't exist.
* @throws InvalidCallException if item already has a child with $itemName or if a loop has been detected.
*/
public function addItemChild($itemName, $childName)
{
if (!isset($this->_items[$childName], $this->_items[$itemName])) {
throw new Exception("Either '$itemName' or '$childName' does not exist.");
}
/** @var Item $child */
$child = $this->_items[$childName];
/** @var Item $item */
$item = $this->_items[$itemName];
$this->checkItemChildType($item->type, $child->type);
if ($this->detectLoop($itemName, $childName)) {
throw new InvalidCallException("Cannot add '$childName' as a child of '$itemName'. A loop has been detected.");
}
if (isset($this->_children[$itemName][$childName])) {
throw new InvalidCallException("The item '$itemName' already has a child '$childName'.");
}
$this->_children[$itemName][$childName] = $this->_items[$childName];
return true;
}
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the removal is successful
*/
public function removeItemChild($itemName, $childName)
{
if (isset($this->_children[$itemName][$childName])) {
unset($this->_children[$itemName][$childName]);
return true;
} else {
return false;
}
}
/**
* Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the child exists
*/
public function hasItemChild($itemName, $childName)
{
return isset($this->_children[$itemName][$childName]);
}
/**
* Returns the children of the specified item.
* @param string|array $names the parent item name. This can be either a string or an array.
* The latter represents a list of item names.
* @return Item[] all child items of the parent
*/
public function getItemChildren($names)
{
if (is_string($names)) {
return isset($this->_children[$names]) ? $this->_children[$names] : [];
}
$children = [];
foreach ($names as $name) {
if (isset($this->_children[$name])) {
$children = array_merge($children, $this->_children[$name]);
}
}
return $children;
}
/**
* Assigns an authorization item to a user.
*
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @param string $ruleName the business rule to be executed when [[checkAccess()]] is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return Assignment the authorization assignment information.
* @throws InvalidParamException if the item does not exist or if the item has already been assigned to the user
*/
public function assign($userId, $itemName, $ruleName = null, $data = null)
{
if (!isset($this->_items[$itemName])) {
throw new InvalidParamException("Unknown authorization item '$itemName'.");
} elseif (isset($this->_assignments[$userId][$itemName])) {
throw new InvalidParamException("Authorization item '$itemName' has already been assigned to user '$userId'.");
} else {
return $this->_assignments[$userId][$itemName] = new Assignment([
'manager' => $this,
'userId' => $userId,
'itemName' => $itemName,
'ruleName' => $ruleName,
'data' => $data,
]);
}
}
/**
* Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return boolean whether removal is successful
*/
public function revoke($userId, $itemName)
{
if (isset($this->_assignments[$userId][$itemName])) {
unset($this->_assignments[$userId][$itemName]);
return true;
} else {
return false;
}
}
/**
* Revokes all authorization assignments from a user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return boolean whether removal is successful
*/
public function revokeAll($userId)
{
if (isset($this->_assignments[$userId]) && is_array($this->_assignments[$userId])) {
foreach ($this->_assignments[$userId] as $itemName => $value)
unset($this->_assignments[$userId][$itemName]);
return true;
} else {
return false;
}
}
/**
* Returns a value indicating whether the item has been assigned to the user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return boolean whether the item has been assigned to the user.
*/
public function isAssigned($userId, $itemName)
{
return isset($this->_assignments[$userId][$itemName]);
}
/**
* Returns the item assignment information.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @param string $itemName the item name
* @return Assignment the item assignment information. Null is returned if
* the item is not assigned to the user.
*/
public function getAssignment($userId, $itemName)
{
return isset($this->_assignments[$userId][$itemName]) ? $this->_assignments[$userId][$itemName] : null;
}
/**
* Returns the item assignments for the specified user.
* @param mixed $userId the user ID (see [[\yii\web\User::id]])
* @return Assignment[] the item assignment information for the user. An empty array will be
* returned if there is no item assigned to the user.
*/
public function getAssignments($userId)
{
return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : [];
}
/**
* Returns the authorization items of the specific type and user.
* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
* they are not assigned to a user.
* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
* meaning returning all items regardless of their type.
* @return Item[] the authorization items of the specific type.
*/
public function getItems($userId = null, $type = null)
{
if ($userId === null && $type === null) {
return $this->_items;
}
$items = [];
if ($userId === null) {
foreach ($this->_items as $name => $item) {
/** @var Item $item */
if ($item->type == $type) {
$items[$name] = $item;
}
}
} elseif (isset($this->_assignments[$userId])) {
foreach ($this->_assignments[$userId] as $assignment) {
/** @var Assignment $assignment */
$name = $assignment->itemName;
if (isset($this->_items[$name]) && ($type === null || $this->_items[$name]->type == $type)) {
$items[$name] = $this->_items[$name];
}
}
}
return $items;
}
/**
* Creates an authorization item.
* An authorization item represents an action permission (e.g. creating a post).
* It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items.
*
* @param string $name the item name. This must be a unique identifier.
* @param integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item
* @param string $rule business rule associated with the item. This is a piece of
* PHP code that will be executed when [[checkAccess()]] is called for the item.
* @param mixed $data additional data associated with the item.
* @return Item the authorization item
* @throws Exception if an item with the same name already exists
*/
public function createItem($name, $type, $description = '', $rule = null, $data = null)
{
if (isset($this->_items[$name])) {
throw new Exception('Unable to add an item whose name is the same as an existing item.');
}
return $this->_items[$name] = new Item([
'manager' => $this,
'name' => $name,
'type' => $type,
'description' => $description,
'ruleName' => $rule,
'data' => $data,
]);
}
/**
* Removes the specified authorization item.
* @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed
*/
public function removeItem($name)
{
if (isset($this->_items[$name])) {
foreach ($this->_children as &$children) {
unset($children[$name]);
}
foreach ($this->_assignments as &$assignments) {
unset($assignments[$name]);
}
unset($this->_items[$name]);
return true;
} else {
return false;
}
}
/**
* Returns the authorization item with the specified name.
* @param string $name the name of the item
* @return Item the authorization item. Null if the item cannot be found.
*/
public function getItem($name)
{
return isset($this->_items[$name]) ? $this->_items[$name] : null;
}
/**
* Saves an authorization item to persistent storage.
* @param Item $item the item to be saved.
* @param string $oldName the old item name. If null, it means the item name is not changed.
* @throws InvalidParamException if an item with the same name already taken
*/
public function saveItem(Item $item, $oldName = null)
{
if ($oldName !== null && ($newName = $item->getName()) !== $oldName) { // name changed
if (isset($this->_items[$newName])) {
throw new InvalidParamException("Unable to change the item name. The name '$newName' is already used by another item.");
}
if (isset($this->_items[$oldName]) && $this->_items[$oldName] === $item) {
unset($this->_items[$oldName]);
$this->_items[$newName] = $item;
if (isset($this->_children[$oldName])) {
$this->_children[$newName] = $this->_children[$oldName];
unset($this->_children[$oldName]);
}
foreach ($this->_children as &$children) {
if (isset($children[$oldName])) {
$children[$newName] = $children[$oldName];
unset($children[$oldName]);
}
}
foreach ($this->_assignments as &$assignments) {
if (isset($assignments[$oldName])) {
$assignments[$newName] = $assignments[$oldName];
unset($assignments[$oldName]);
}
}
}
}
}
/**
* Saves the changes to an authorization assignment.
* @param Assignment $assignment the assignment that has been changed.
*/
public function saveAssignment(Assignment $assignment)
{
}
/**
* Saves authorization data into persistent storage.
* If any change is made to the authorization data, please make
* sure you call this method to save the changed data into persistent storage.
*/
public function save()
{
$items = [];
foreach ($this->_items as $name => $item) {
/** @var Item $item */
$items[$name] = [
'type' => $item->type,
'description' => $item->description,
'ruleName' => $item->ruleName,
'data' => $item->data,
];
if (isset($this->_children[$name])) {
foreach ($this->_children[$name] as $child) {
/** @var Item $child */
$items[$name]['children'][] = $child->getName();
}
}
}
foreach ($this->_assignments as $userId => $assignments) {
foreach ($assignments as $name => $assignment) {
/** @var Assignment $assignment */
if (isset($items[$name])) {
$items[$name]['assignments'][$userId] = [
'ruleName' => $assignment->ruleName,
'data' => $assignment->data,
];
}
}
}
$rules = [];
foreach ($this->_rules as $name => $rule) {
$rules[$name] = serialize($rule);
}
$this->saveToFile(['items' => $items, 'rules' => $rules], $this->authFile);
}
/**
* Loads authorization data.
*/
public function load()
{
$this->clearAll();
$data = $this->loadFromFile($this->authFile);
if (isset($data['items'])) {
foreach ($data['items'] as $name => $item) {
$this->_items[$name] = new Item([
'manager' => $this,
'name' => $name,
'type' => $item['type'],
'description' => $item['description'],
'ruleName' => $item['ruleName'],
'data' => $item['data'],
]);
}
foreach ($data['items'] as $name => $item) {
if (isset($item['children'])) {
foreach ($item['children'] as $childName) {
if (isset($this->_items[$childName])) {
$this->_children[$name][$childName] = $this->_items[$childName];
}
}
}
if (isset($item['assignments'])) {
foreach ($item['assignments'] as $userId => $assignment) {
$this->_assignments[$userId][$name] = new Assignment([
'manager' => $this,
'userId' => $userId,
'itemName' => $name,
'ruleName' => $assignment['ruleName'],
'data' => $assignment['data'],
]);
}
}
}
}
if (isset($data['rules'])) {
foreach ($data['rules'] as $name => $ruleData) {
$this->_rules[$name] = unserialize($ruleData);
}
}
}
/**
* Removes all authorization data.
*/
public function clearAll()
{
$this->clearAssignments();
$this->_children = [];
$this->_items = [];
}
/**
* Removes all authorization assignments.
*/
public function clearAssignments()
{
$this->_assignments = [];
}
/**
* Checks whether there is a loop in the authorization item hierarchy.
* @param string $itemName parent item name
* @param string $childName the name of the child item that is to be added to the hierarchy
* @return boolean whether a loop exists
*/
protected function detectLoop($itemName, $childName)
{
if ($childName === $itemName) {
return true;
}
if (!isset($this->_children[$childName], $this->_items[$itemName])) {
return false;
}
foreach ($this->_children[$childName] as $child) {
/** @var Item $child */
if ($this->detectLoop($itemName, $child->getName())) {
return true;
}
}
return false;
}
/**
* Loads the authorization data from a PHP script file.
* @param string $file the file path.
* @return array the authorization data
* @see saveToFile()
*/
protected function loadFromFile($file)
{
if (is_file($file)) {
return require($file);
} else {
return [];
}
}
/**
* Saves the authorization data to a PHP script file.
* @param array $data the authorization data
* @param string $file the file path.
* @see loadFromFile()
*/
protected function saveToFile($data, $file)
{
file_put_contents($file, "<?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)
{
return isset($this->_rules[$name]) ? $this->_rules[$name] : null;
}
/**
* 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;
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Role extends Item
{
/**
* @inheritdoc
*/
public $type = self::TYPE_ROLE;
}
......@@ -10,8 +10,7 @@ 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.
* Rule represents a business constraint that may be associated with a role, permission or assignment.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
......@@ -22,13 +21,21 @@ abstract class Rule extends Object
* @var string name of the rule
*/
public $name;
/**
* @var integer UNIX timestamp representing the rule creation time
*/
public $createdAt;
/**
* @var integer UNIX timestamp representing the rule updating time
*/
public $updatedAt;
/**
* 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.
* @param Item $item the auth item that this rule is associated with
* @param array $params parameters passed to [[ManagerInterface::allow()]].
* @return boolean a value indicating whether the rule permits the auth item it is associated with.
*/
abstract public function execute($params, $data);
abstract public function execute($item, $params);
}
......@@ -18,6 +18,8 @@ create table [auth_rule]
(
[name] varchar(64) not null,
[data] text,
[created_at] integer,
[updated_at] integer,
primary key ([name])
);
......@@ -28,6 +30,8 @@ create table [auth_item]
[description] text,
[rule_name] varchar(64),
[data] text,
[created_at] integer,
[updated_at] integer,
primary key ([name]),
foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade,
key [type] ([type])
......@@ -46,9 +50,7 @@ create table [auth_assignment]
(
[item_name] varchar(64) not null,
[user_id] varchar(64) not null,
[rule_name] varchar(64),
[data] text,
[created_at] integer,
primary key ([item_name], [user_id]),
foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade,
foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade
foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade
);
......@@ -18,6 +18,8 @@ create table `auth_rule`
(
`name` varchar(64) not null,
`data` text,
`created_at` integer,
`updated_at` integer,
primary key (`name`)
) engine InnoDB;
......@@ -28,6 +30,8 @@ create table `auth_item`
`description` text,
`rule_name` varchar(64),
`data` text,
`created_at` integer,
`updated_at` integer,
primary key (`name`),
foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade,
key `type` (`type`)
......@@ -46,9 +50,7 @@ create table `auth_assignment`
(
`item_name` varchar(64) not null,
`user_id` varchar(64) not null,
`rule_name` varchar(64),
`data` text,
`created_at` integer,
primary key (`item_name`, `user_id`),
foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade,
foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade
foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade
) engine InnoDB;
......@@ -18,6 +18,8 @@ create table "auth_rule"
(
"name" varchar(64) not null,
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name")
);
......@@ -28,6 +30,8 @@ create table "auth_item"
"description" text,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name"),
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade,
key "type" ("type")
......@@ -46,9 +50,7 @@ create table "auth_assignment"
(
"item_name" varchar(64) not null,
"user_id" varchar(64) not null,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
primary key ("item_name","user_id"),
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade,
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade
);
......@@ -18,6 +18,8 @@ create table "auth_rule"
(
"name" varchar(64) not null,
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name")
);
......@@ -28,6 +30,8 @@ create table "auth_item"
"description" text,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name"),
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
);
......@@ -47,9 +51,7 @@ create table "auth_assignment"
(
"item_name" varchar(64) not null,
"user_id" varchar(64) not null,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
primary key ("item_name","user_id"),
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade,
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade
);
......@@ -18,6 +18,8 @@ create table "auth_rule"
(
"name" varchar(64) not null,
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name")
);
......@@ -28,6 +30,8 @@ create table "auth_item"
"description" text,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
"updated_at" integer,
primary key ("name"),
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
);
......@@ -47,9 +51,7 @@ create table "auth_assignment"
(
"item_name" varchar(64) not null,
"user_id" varchar(64) not null,
"rule_name" varchar(64),
"data" text,
"created_at" integer,
primary key ("item_name","user_id"),
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade,
foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade
foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade
);
......@@ -14,9 +14,8 @@ class AuthorRule extends Rule
/**
* @inheritdoc
*/
public function execute($params, $data)
public function execute($item, $params)
{
return $params['authorID'] == $params['userID'];
return $params['authorID'] == $params['user'];
}
}
\ No newline at end of file
......@@ -29,8 +29,6 @@ abstract class DbManagerTestCase extends ManagerTestCase
}
$this->auth = new DbManager(['db' => $this->getConnection()]);
$this->auth->init();
$this->prepareData();
}
protected function tearDown()
......
......@@ -4,13 +4,59 @@ namespace yiiunit\framework\rbac;
use yii\rbac\Assignment;
use yii\rbac\Item;
use yii\rbac\Permission;
use yii\rbac\Role;
use yiiunit\TestCase;
abstract class ManagerTestCase extends TestCase
{
/** @var \yii\rbac\Manager */
/**
* @var \yii\rbac\ManagerInterface
*/
protected $auth;
public function testCreateRoleAndPermission()
{
$role = $this->auth->createRole('admin');
$this->assertTrue($role instanceof Role);
$this->assertEquals(Item::TYPE_ROLE, $role->type);
$this->assertEquals('admin', $role->name);
}
public function testCreatePermission()
{
$permission = $this->auth->createPermission('edit post');
$this->assertTrue($permission instanceof Permission);
$this->assertEquals(Item::TYPE_PERMISSION, $permission->type);
$this->assertEquals('edit post', $permission->name);
}
public function testAdd()
{
$role = $this->auth->createRole('admin');
$role->description = 'administrator';
$this->assertTrue($this->auth->add($role));
$permission = $this->auth->createPermission('edit post');
$permission->description = 'edit a post';
$this->assertTrue($this->auth->add($permission));
$rule = new AuthorRule(['name' => 'is author', 'reallyReally' => true]);
$this->assertTrue($this->auth->add($rule));
// todo: check duplication of name
}
/*
public function testRemove()
{
}
public function testUpdate()
{
}
public function testCreateItem()
{
$type = Item::TYPE_TASK;
......@@ -43,7 +89,7 @@ abstract class ManagerTestCase extends TestCase
$this->assertNull($this->auth->getItem('unknown'));
}
public function testRemoveAuthItem()
public function testRemoveItem()
{
$this->assertTrue($this->auth->getItem('updatePost') instanceof Item);
$this->assertTrue($this->auth->removeItem('updatePost'));
......@@ -167,9 +213,12 @@ abstract class ManagerTestCase extends TestCase
$this->setExpectedException('\yii\base\Exception');
$this->auth->addItemChild('readPost', 'readPost');
}
*/
public function testGetRule()
{
$this->prepareData();
$rule = $this->auth->getRule('isAuthor');
$this->assertInstanceOf('yii\rbac\Rule', $rule);
$this->assertEquals('isAuthor', $rule->name);
......@@ -178,13 +227,14 @@ abstract class ManagerTestCase extends TestCase
$this->assertNull($rule);
}
public function testInsertRule()
public function testAddRule()
{
$this->prepareData();
$ruleName = 'isReallyReallyAuthor';
$rule = new AuthorRule(['name' => $ruleName, 'reallyReally' => true]);
$this->auth->insertRule($rule);
$this->auth->add($rule);
/** @var AuthorRule $rule */
$rule = $this->auth->getRule($ruleName);
$this->assertEquals($ruleName, $rule->name);
$this->assertEquals(true, $rule->reallyReally);
......@@ -192,12 +242,13 @@ abstract class ManagerTestCase extends TestCase
public function testUpdateRule()
{
$this->prepareData();
$rule = $this->auth->getRule('isAuthor');
$rule->name = "newName";
$rule->reallyReally = false;
$this->auth->updateRule('isAuthor', $rule);
$this->auth->update('isAuthor', $rule);
/** @var AuthorRule $rule */
$rule = $this->auth->getRule('isAuthor');
$this->assertEquals(null, $rule);
......@@ -206,7 +257,7 @@ abstract class ManagerTestCase extends TestCase
$this->assertEquals(false, $rule->reallyReally);
$rule->reallyReally = true;
$this->auth->updateRule('newName', $rule);
$this->auth->update('newName', $rule);
$rule = $this->auth->getRule('newName');
$this->assertEquals(true, $rule->reallyReally);
......@@ -214,8 +265,10 @@ abstract class ManagerTestCase extends TestCase
public function testGetRules()
{
$this->prepareData();
$rule = new AuthorRule(['name' => 'isReallyReallyAuthor', 'reallyReally' => true]);
$this->auth->insertRule($rule);
$this->auth->add($rule);
$rules = $this->auth->getRules();
......@@ -230,103 +283,87 @@ abstract class ManagerTestCase extends TestCase
public function testRemoveRule()
{
$this->auth->removeRule('isAuthor');
$this->prepareData();
$this->auth->remove($this->auth->getRule('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()
{
$results = [
$this->prepareData();
$testSuites = [
'reader A' => [
'createPost' => false,
'readPost' => true,
'updatePost' => false,
'updateOwnPost' => false,
'deletePost' => false,
'updateAnyPost' => false,
],
'author B' => [
'createPost' => true,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => true,
'deletePost' => false,
],
'editor C' => [
'createPost' => false,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => false,
'deletePost' => false,
'updateAnyPost' => false,
],
'admin D' => [
'admin C' => [
'createPost' => true,
'readPost' => true,
'updatePost' => true,
'updateOwnPost' => false,
'deletePost' => true,
],
'reader E' => [
'createPost' => false,
'readPost' => false,
'updatePost' => false,
'updateOwnPost' => false,
'deletePost' => false,
'updateAnyPost' => true,
],
];
$params = ['authorID' => 'author B'];
foreach (['reader A', 'author B', 'editor C', 'admin D'] as $user) {
$params['userID'] = $user;
foreach (['createPost', 'readPost', 'updatePost', 'updateOwnPost', 'deletePost'] as $operation) {
$result = $this->auth->checkAccess($user, $operation, $params);
$this->assertEquals($results[$user][$operation], $result);
foreach ($testSuites as $user => $tests) {
foreach ($tests as $permission => $result) {
$this->assertEquals($result, $this->auth->checkAccess($user, $permission, $params), "Checking $user can $permission");
}
}
}
protected function prepareData()
{
$this->auth->insertRule(new AuthorRule());
$this->auth->createOperation('createPost', 'create a post');
$this->auth->createOperation('readPost', 'read a post');
$this->auth->createOperation('updatePost', 'update a post');
$this->auth->createOperation('deletePost', 'delete a post');
$task = $this->auth->createTask('updateOwnPost', 'update a post by author himself', 'isAuthor');
$task->addChild('updatePost');
$role = $this->auth->createRole('reader');
$role->addChild('readPost');
$role = $this->auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
$role = $this->auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
$role = $this->auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
$this->auth->assign('reader A', 'reader');
$this->auth->assign('author B', 'author');
$this->auth->assign('editor C', 'editor');
$this->auth->assign('admin D', 'admin');
$this->auth->assign('reader E', 'reader');
$rule = new AuthorRule;
$this->auth->add($rule);
$createPost = $this->auth->createPermission('createPost');
$createPost->description = 'create a post';
$this->auth->add($createPost);
$readPost = $this->auth->createPermission('readPost');
$readPost->description = 'read a post';
$this->auth->add($readPost);
$updatePost = $this->auth->createPermission('updatePost');
$updatePost->description = 'update a post';
$updatePost->ruleName = $rule->name;
$this->auth->add($updatePost);
$updateAnyPost = $this->auth->createPermission('updateAnyPost');
$updateAnyPost->description = 'update any post';
$this->auth->add($updateAnyPost);
$reader = $this->auth->createRole('reader');
$this->auth->add($reader);
$this->auth->addChild($reader, $readPost);
$author = $this->auth->createRole('author');
$this->auth->add($author);
$this->auth->addChild($author, $createPost);
$this->auth->addChild($author, $updatePost);
$this->auth->addChild($author, $reader);
$admin = $this->auth->createRole('admin');
$this->auth->add($admin);
$this->auth->addChild($admin, $author);
$this->auth->addChild($admin, $updateAnyPost);
$this->auth->assign($reader, 'reader A');
$this->auth->assign($author, 'author B');
$this->auth->assign($admin, 'admin C');
}
}
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