You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
711 lines
20 KiB
711 lines
20 KiB
<?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\InvalidCallException; |
|
use yii\base\InvalidParamException; |
|
use Yii; |
|
|
|
/** |
|
* 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> |
|
* @author Christophe Boulain <christophe.boulain@gmail.com> |
|
* @since 2.0 |
|
*/ |
|
class PhpManager extends BaseManager |
|
{ |
|
/** |
|
* @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'; |
|
/** |
|
* @var Item[] |
|
*/ |
|
private $_items = []; // itemName => item |
|
/** |
|
* @var array |
|
*/ |
|
private $_children = []; // itemName, childName => child |
|
/** |
|
* @var Assignment[] |
|
*/ |
|
private $_assignments = []; // userId, itemName => assignment |
|
/** |
|
* @var Rule[] |
|
*/ |
|
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(); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function checkAccess($userId, $permissionName, $params = []) |
|
{ |
|
$assignments = $this->getAssignments($userId); |
|
return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getAssignments($userId) |
|
{ |
|
return isset($this->_assignments[$userId]) ? $this->_assignments[$userId] : []; |
|
} |
|
|
|
/** |
|
* Performs access check for the specified user. |
|
* This method is internally called by [[checkAccess()]]. |
|
* |
|
* @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 '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($user, $itemName, $params, $assignments) |
|
{ |
|
if (!isset($this->_items[$itemName])) { |
|
return false; |
|
} |
|
|
|
/** @var Item $item */ |
|
$item = $this->_items[$itemName]; |
|
Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__); |
|
|
|
if (!$this->executeRule($user, $item, $params)) { |
|
return false; |
|
} |
|
|
|
if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) { |
|
return true; |
|
} |
|
|
|
foreach ($this->_children as $parentName => $children) { |
|
if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function addChild($parent, $child) |
|
{ |
|
if (!isset($this->_items[$parent->name], $this->_items[$child->name])) { |
|
throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist."); |
|
} |
|
|
|
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."); |
|
} |
|
if (isset($this->_children[$parent->name][$child->name])) { |
|
throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'."); |
|
} |
|
$this->_children[$parent->name][$child->name] = $this->_items[$child->name]; |
|
$this->save(); |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* Checks whether there is a loop in the authorization item hierarchy. |
|
* |
|
* @param Item $parent parent item |
|
* @param Item $child the child item that is to be added to the hierarchy |
|
* @return boolean whether a loop exists |
|
*/ |
|
protected function detectLoop($parent, $child) |
|
{ |
|
if ($child->name === $parent->name) { |
|
return true; |
|
} |
|
if (!isset($this->_children[$child->name], $this->_items[$parent->name])) { |
|
return false; |
|
} |
|
foreach ($this->_children[$child->name] as $grandchild) { |
|
/** @var Item $grandchild */ |
|
if ($this->detectLoop($parent, $grandchild)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeChild($parent, $child) |
|
{ |
|
if (isset($this->_children[$parent->name][$child->name])) { |
|
unset($this->_children[$parent->name][$child->name]); |
|
$this->save(); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function hasChild($parent, $child) |
|
{ |
|
return isset($this->_children[$parent->name][$child->name]); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function assign($role, $userId, $ruleName = null, $data = null) |
|
{ |
|
if (!isset($this->_items[$role->name])) { |
|
throw new InvalidParamException("Unknown role '{$role->name}'."); |
|
} elseif (isset($this->_assignments[$userId][$role->name])) { |
|
throw new InvalidParamException("Authorization item '{$role->name}' has already been assigned to user '$userId'."); |
|
} else { |
|
$this->_assignments[$userId][$role->name] = new Assignment([ |
|
'userId' => $userId, |
|
'roleName' => $role->name, |
|
'createdAt' => time(), |
|
]); |
|
$this->save(); |
|
return $this->_assignments[$userId][$role->name]; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function revoke($role, $userId) |
|
{ |
|
if (isset($this->_assignments[$userId][$role->name])) { |
|
unset($this->_assignments[$userId][$role->name]); |
|
$this->save(); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
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]); |
|
} |
|
$this->save(); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getAssignment($roleName, $userId) |
|
{ |
|
return isset($this->_assignments[$userId][$roleName]) ? $this->_assignments[$userId][$roleName] : null; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getItems($type) |
|
{ |
|
$items = []; |
|
|
|
foreach ($this->_items as $name => $item) { |
|
/** @var Item $item */ |
|
if ($item->type == $type) { |
|
$items[$name] = $item; |
|
} |
|
} |
|
|
|
return $items; |
|
} |
|
|
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeItem($item) |
|
{ |
|
if (isset($this->_items[$item->name])) { |
|
foreach ($this->_children as &$children) { |
|
unset($children[$item->name]); |
|
} |
|
foreach ($this->_assignments as &$assignments) { |
|
unset($assignments[$item->name]); |
|
} |
|
unset($this->_items[$item->name]); |
|
$this->save(); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getItem($name) |
|
{ |
|
return isset($this->_items[$name]) ? $this->_items[$name] : null; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function updateRule($name, $rule) |
|
{ |
|
if ($rule->name !== $name) { |
|
unset($this->_rules[$name]); |
|
} |
|
$this->_rules[$rule->name] = $rule; |
|
$this->save(); |
|
return true; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getRule($name) |
|
{ |
|
return isset($this->_rules[$name]) ? $this->_rules[$name] : null; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getRules() |
|
{ |
|
return $this->_rules; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getRolesByUser($userId) |
|
{ |
|
$roles = []; |
|
foreach ($this->getAssignments($userId) as $name => $assignment) { |
|
$roles[$name] = $this->_items[$assignment->roleName]; |
|
} |
|
|
|
return $roles; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getPermissionsByRole($roleName) |
|
{ |
|
$result = []; |
|
$this->getChildrenRecursive($roleName, $result); |
|
if (empty($result)) { |
|
return []; |
|
} |
|
$permissions = []; |
|
foreach (array_keys($result) as $itemName) { |
|
if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) { |
|
$permissions[$itemName] = $this->_items[$itemName]; |
|
} |
|
} |
|
return $permissions; |
|
} |
|
|
|
/** |
|
* 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 $result the children and grand children (in array keys) |
|
*/ |
|
protected function getChildrenRecursive($name, &$result) |
|
{ |
|
if (isset($this->_children[$name])) { |
|
foreach ($this->_children[$name] as $child) { |
|
$result[$child->name] = true; |
|
$this->getChildrenRecursive($child->name, $result); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getPermissionsByUser($userId) |
|
{ |
|
$assignments = $this->getAssignments($userId); |
|
$result = []; |
|
foreach (array_keys($assignments) as $roleName) { |
|
$this->getChildrenRecursive($roleName, $result); |
|
} |
|
|
|
if (empty($result)) { |
|
return []; |
|
} |
|
|
|
$permissions = []; |
|
foreach (array_keys($result) as $itemName) { |
|
if (isset($this->_items[$itemName]) && $this->_items[$itemName] instanceof Permission) { |
|
$permissions[$itemName] = $this->_items[$itemName]; |
|
} |
|
} |
|
return $permissions; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function getChildren($name) |
|
{ |
|
return (isset($this->_children[$name])) ? $this->_children[$name] : null; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeAll() |
|
{ |
|
$this->_children = []; |
|
$this->_items = []; |
|
$this->_assignments = []; |
|
$this->_rules = []; |
|
$this->save(); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeAllPermissions() |
|
{ |
|
$this->removeAllItems(Item::TYPE_PERMISSION); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeAllRoles() |
|
{ |
|
$this->removeAllItems(Item::TYPE_ROLE); |
|
} |
|
|
|
/** |
|
* Removes all auth items of the specified type. |
|
* @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE) |
|
*/ |
|
protected function removeAllItems($type) |
|
{ |
|
$names = []; |
|
foreach ($this->_items as $name => $item) { |
|
if ($item->type == $type) { |
|
unset($this->_items[$name]); |
|
$names[$name] = true; |
|
} |
|
} |
|
if (empty($names)) { |
|
return; |
|
} |
|
|
|
foreach ($this->_assignments as $i => $assignment) { |
|
if (isset($names[$assignment->roleName])) { |
|
unset($this->_assignments[$i]); |
|
} |
|
} |
|
foreach ($this->_children as $name => $children) { |
|
if (isset($names[$name])) { |
|
unset($this->_children[$name]); |
|
} else { |
|
foreach ($children as $childName => $item) { |
|
if (isset($names[$childName])) { |
|
unset($children[$childName]); |
|
} |
|
} |
|
$this->_children[$name] = $children; |
|
} |
|
} |
|
|
|
$this->save(); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeAllRules() |
|
{ |
|
foreach ($this->_items as $item) { |
|
$item->ruleName = null; |
|
} |
|
$this->_rules = []; |
|
$this->save(); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function removeAllAssignments() |
|
{ |
|
$this->_assignments = []; |
|
$this->save(); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
protected function removeRule($rule) |
|
{ |
|
if (isset($this->_rules[$rule->name])) { |
|
unset($this->_rules[$rule->name]); |
|
foreach ($this->_items as $item) { |
|
if ($item->ruleName === $rule->name) { |
|
$item->ruleName = null; |
|
} |
|
} |
|
$this->save(); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
protected function addRule($rule) |
|
{ |
|
$this->_rules[$rule->name] = $rule; |
|
$this->save(); |
|
return true; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
protected function updateItem($name, $item) |
|
{ |
|
$this->_items[$item->name] = $item; |
|
if ($name !== $item->name) { |
|
if (isset($this->_items[$item->name])) { |
|
throw new InvalidParamException("Unable to change the item name. The name '{$item->name} is already used by another item."); |
|
} |
|
if (isset($this->_items[$name])) { |
|
unset ($this->_items[$name]); |
|
|
|
if (isset($this->_children[$name])) { |
|
$this->_children[$item->name] = $this->_children[$name]; |
|
unset ($this->_children[$name]); |
|
} |
|
foreach ($this->_children as &$children) { |
|
if (isset($children[$name])) { |
|
$children[$item->name] = $children[$name]; |
|
unset ($children[$name]); |
|
} |
|
} |
|
foreach ($this->_assignments as &$assignments) { |
|
if (isset($assignments[$name])) { |
|
$assignments[$item->name] = $assignments[$name]; |
|
unset($assignments[$name]); |
|
} |
|
} |
|
} |
|
} |
|
$this->save(); |
|
return true; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
protected function addItem($item) |
|
{ |
|
$time = time(); |
|
if ($item->createdAt === null) { |
|
$item->createdAt = $time; |
|
} |
|
if ($item->updatedAt === null) { |
|
$item->updatedAt = $time; |
|
} |
|
|
|
$this->_items[$item->name] = $item; |
|
|
|
$this->save(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
/** |
|
* Loads authorization data from persistent storage. |
|
*/ |
|
public function load() |
|
{ |
|
$this->_children = []; |
|
$this->_rules = []; |
|
$this->_assignments = []; |
|
$this->_items = []; |
|
|
|
$data = $this->loadFromFile($this->authFile); |
|
|
|
if (isset($data['items'])) { |
|
foreach ($data['items'] as $name => $item) { |
|
$class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className(); |
|
|
|
$this->_items[$name] = new $class([ |
|
'name' => $name, |
|
'description' => isset($item['description']) ? $item['description'] : null, |
|
'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null, |
|
'data' => isset($item['data']) ? $item['data'] : null, |
|
'createdAt' => isset($item['createdAt']) ? $item['createdAt'] : null, |
|
'updatedAt' => isset($item['updatedAt']) ? $item['updatedAt'] : null, |
|
]); |
|
} |
|
|
|
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([ |
|
'userId' => $userId, |
|
'roleName' => $assignment['roleName'], |
|
'createdAt' => isset($assignment['createdAt']) ? $assignment['createdAt'] : null, |
|
]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (isset($data['rules'])) { |
|
foreach ($data['rules'] as $name => $ruleData) { |
|
$this->_rules[$name] = unserialize($ruleData); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Saves authorization data into persistent storage. |
|
*/ |
|
public function save() |
|
{ |
|
$items = []; |
|
foreach ($this->_items as $name => $item) { |
|
/** @var Item $item */ |
|
$items[$name] = array_filter([ |
|
'type' => $item->type, |
|
'description' => $item->description, |
|
'ruleName' => $item->ruleName, |
|
'data' => $item->data, |
|
]); |
|
if (isset($this->_children[$name])) { |
|
foreach ($this->_children[$name] as $child) { |
|
/** @var Item $child */ |
|
$items[$name]['children'][] = $child->name; |
|
} |
|
} |
|
} |
|
|
|
foreach ($this->_assignments as $userId => $assignments) { |
|
foreach ($assignments as $name => $assignment) { |
|
/** @var Assignment $assignment */ |
|
if (isset($items[$name])) { |
|
$items[$name]['assignments'][$userId] = [ |
|
'roleName' => $assignment->roleName, |
|
]; |
|
} |
|
} |
|
} |
|
|
|
$rules = []; |
|
foreach ($this->_rules as $name => $rule) { |
|
$rules[$name] = serialize($rule); |
|
} |
|
|
|
$this->saveToFile(['items' => $items, 'rules' => $rules], $this->authFile); |
|
} |
|
|
|
/** |
|
* 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); |
|
} |
|
}
|
|
|