| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888 | <?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\InvalidArgumentException;use yii\base\InvalidCallException;use yii\helpers\VarDumper;/** * 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 three files * specified by [[itemFile]], [[assignmentFile]] and [[ruleFile]]. * * 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. * * Note that PhpManager is not compatible with facebooks [HHVM](http://hhvm.com/) because * it relies on writing php files and including them afterwards which is not supported by HHVM. * * For more details and usage information on PhpManager, see the [guide article on security authorization](guide:security-authorization). * * @author Qiang Xue <qiang.xue@gmail.com> * @author Alexander Kochetov <creocoder@gmail.com> * @author Christophe Boulain <christophe.boulain@gmail.com> * @author Alexander Makarov <sam@rmcreative.ru> * @since 2.0 */class PhpManager extends BaseManager{    /**     * @var string the path of the PHP script that contains the authorization items.     * This can be either a file path or a [path alias](guide:concept-aliases) 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 $itemFile = '@app/rbac/items.php';    /**     * @var string the path of the PHP script that contains the authorization assignments.     * This can be either a file path or a [path alias](guide:concept-aliases) 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 $assignmentFile = '@app/rbac/assignments.php';    /**     * @var string the path of the PHP script that contains the authorization rules.     * This can be either a file path or a [path alias](guide:concept-aliases) 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 $ruleFile = '@app/rbac/rules.php';    /**     * @var Item[]     */    protected $items = []; // itemName => item    /**     * @var array     */    protected $children = []; // itemName, childName => child    /**     * @var array     */    protected $assignments = []; // userId, itemName => assignment    /**     * @var Rule[]     */    protected $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->itemFile = Yii::getAlias($this->itemFile);        $this->assignmentFile = Yii::getAlias($this->assignmentFile);        $this->ruleFile = Yii::getAlias($this->ruleFile);        $this->load();    }    /**     * {@inheritdoc}     */    public function checkAccess($userId, $permissionName, $params = [])    {        $assignments = $this->getAssignments($userId);        if ($this->hasNoAssignments($assignments)) {            return false;        }        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|int $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 bool 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::debug($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}     * @since 2.0.8     */    public function canAddChild($parent, $child)    {        return !$this->detectLoop($parent, $child);    }    /**     * {@inheritdoc}     */    public function addChild($parent, $child)    {        if (!isset($this->items[$parent->name], $this->items[$child->name])) {            throw new InvalidArgumentException("Either '{$parent->name}' or '{$child->name}' does not exist.");        }        if ($parent->name === $child->name) {            throw new InvalidArgumentException("Cannot add '{$parent->name} ' as a child of itself.");        }        if ($parent instanceof Permission && $child instanceof Role) {            throw new InvalidArgumentException('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->saveItems();        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 bool 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 $grandchild Item */            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->saveItems();            return true;        }        return false;    }    /**     * {@inheritdoc}     */    public function removeChildren($parent)    {        if (isset($this->children[$parent->name])) {            unset($this->children[$parent->name]);            $this->saveItems();            return true;        }        return false;    }    /**     * {@inheritdoc}     */    public function hasChild($parent, $child)    {        return isset($this->children[$parent->name][$child->name]);    }    /**     * {@inheritdoc}     */    public function assign($role, $userId)    {        if (!isset($this->items[$role->name])) {            throw new InvalidArgumentException("Unknown role '{$role->name}'.");        } elseif (isset($this->assignments[$userId][$role->name])) {            throw new InvalidArgumentException("Authorization item '{$role->name}' has already been assigned to user '$userId'.");        }        $this->assignments[$userId][$role->name] = new Assignment([            'userId' => $userId,            'roleName' => $role->name,            'createdAt' => time(),        ]);        $this->saveAssignments();        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->saveAssignments();            return true;        }        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->saveAssignments();            return true;        }        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->saveItems();            $this->saveAssignments();            return true;        }        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->saveRules();        return true;    }    /**     * {@inheritdoc}     */    public function getRule($name)    {        return isset($this->rules[$name]) ? $this->rules[$name] : null;    }    /**     * {@inheritdoc}     */    public function getRules()    {        return $this->rules;    }    /**     * {@inheritdoc}     * The roles returned by this method include the roles assigned via [[$defaultRoles]].     */    public function getRolesByUser($userId)    {        $roles = $this->getDefaultRoleInstances();        foreach ($this->getAssignments($userId) as $name => $assignment) {            $role = $this->items[$assignment->roleName];            if ($role->type === Item::TYPE_ROLE) {                $roles[$name] = $role;            }        }        return $roles;    }    /**     * {@inheritdoc}     */    public function getChildRoles($roleName)    {        $role = $this->getRole($roleName);        if ($role === null) {            throw new InvalidArgumentException("Role \"$roleName\" not found.");        }        $result = [];        $this->getChildrenRecursive($roleName, $result);        $roles = [$roleName => $role];        $roles += array_filter($this->getRoles(), function (Role $roleItem) use ($result) {            return array_key_exists($roleItem->name, $result);        });        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)    {        $directPermission = $this->getDirectPermissionsByUser($userId);        $inheritedPermission = $this->getInheritedPermissionsByUser($userId);        return array_merge($directPermission, $inheritedPermission);    }    /**     * Returns all permissions that are directly assigned to user.     * @param string|int $userId the user ID (see [[\yii\web\User::id]])     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.     * @since 2.0.7     */    protected function getDirectPermissionsByUser($userId)    {        $permissions = [];        foreach ($this->getAssignments($userId) as $name => $assignment) {            $permission = $this->items[$assignment->roleName];            if ($permission->type === Item::TYPE_PERMISSION) {                $permissions[$name] = $permission;            }        }        return $permissions;    }    /**     * Returns all permissions that the user inherits from the roles assigned to him.     * @param string|int $userId the user ID (see [[\yii\web\User::id]])     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.     * @since 2.0.7     */    protected function getInheritedPermissionsByUser($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] : [];    }    /**     * {@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 int $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 => $assignments) {            foreach ($assignments as $n => $assignment) {                if (isset($names[$assignment->roleName])) {                    unset($this->assignments[$i][$n]);                }            }        }        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->saveItems();    }    /**     * {@inheritdoc}     */    public function removeAllRules()    {        foreach ($this->items as $item) {            $item->ruleName = null;        }        $this->rules = [];        $this->saveRules();    }    /**     * {@inheritdoc}     */    public function removeAllAssignments()    {        $this->assignments = [];        $this->saveAssignments();    }    /**     * {@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->saveRules();            return true;        }        return false;    }    /**     * {@inheritdoc}     */    protected function addRule($rule)    {        $this->rules[$rule->name] = $rule;        $this->saveRules();        return true;    }    /**     * {@inheritdoc}     */    protected function updateItem($name, $item)    {        if ($name !== $item->name) {            if (isset($this->items[$item->name])) {                throw new InvalidArgumentException("Unable to change the item name. The name '{$item->name}' is already used by another item.");            }            // Remove old item in case of renaming            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];                    $assignments[$item->name]->roleName = $item->name;                    unset($assignments[$name]);                }            }            $this->saveAssignments();        }        $this->items[$item->name] = $item;        $this->saveItems();        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->saveItems();        return true;    }    /**     * Loads authorization data from persistent storage.     */    protected function load()    {        $this->children = [];        $this->rules = [];        $this->assignments = [];        $this->items = [];        $items = $this->loadFromFile($this->itemFile);        $itemsMtime = @filemtime($this->itemFile);        $assignments = $this->loadFromFile($this->assignmentFile);        $assignmentsMtime = @filemtime($this->assignmentFile);        $rules = $this->loadFromFile($this->ruleFile);        foreach ($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' => $itemsMtime,                'updatedAt' => $itemsMtime,            ]);        }        foreach ($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];                    }                }            }        }        foreach ($assignments as $userId => $roles) {            foreach ($roles as $role) {                $this->assignments[$userId][$role] = new Assignment([                    'userId' => $userId,                    'roleName' => $role,                    'createdAt' => $assignmentsMtime,                ]);            }        }        foreach ($rules as $name => $ruleData) {            $this->rules[$name] = unserialize($ruleData);        }    }    /**     * Saves authorization data into persistent storage.     */    protected function save()    {        $this->saveItems();        $this->saveAssignments();        $this->saveRules();    }    /**     * 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;        }        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 " . VarDumper::export($data) . ";\n", LOCK_EX);        $this->invalidateScriptCache($file);    }    /**     * Invalidates precompiled script cache (such as OPCache or APC) for the given file.     * @param string $file the file path.     * @since 2.0.9     */    protected function invalidateScriptCache($file)    {        if (function_exists('opcache_invalidate')) {            opcache_invalidate($file, true);        }        if (function_exists('apc_delete_file')) {            @apc_delete_file($file);        }    }    /**     * Saves items data into persistent storage.     */    protected function saveItems()    {        $items = [];        foreach ($this->items as $name => $item) {            /* @var $item Item */            $items[$name] = array_filter(                [                    'type' => $item->type,                    'description' => $item->description,                    'ruleName' => $item->ruleName,                    'data' => $item->data,                ]            );            if (isset($this->children[$name])) {                foreach ($this->children[$name] as $child) {                    /* @var $child Item */                    $items[$name]['children'][] = $child->name;                }            }        }        $this->saveToFile($items, $this->itemFile);    }    /**     * Saves assignments data into persistent storage.     */    protected function saveAssignments()    {        $assignmentData = [];        foreach ($this->assignments as $userId => $assignments) {            foreach ($assignments as $name => $assignment) {                /* @var $assignment Assignment */                $assignmentData[$userId][] = $assignment->roleName;            }        }        $this->saveToFile($assignmentData, $this->assignmentFile);    }    /**     * Saves rules data into persistent storage.     */    protected function saveRules()    {        $rules = [];        foreach ($this->rules as $name => $rule) {            $rules[$name] = serialize($rule);        }        $this->saveToFile($rules, $this->ruleFile);    }    /**     * {@inheritdoc}     * @since 2.0.7     */    public function getUserIdsByRole($roleName)    {        $result = [];        foreach ($this->assignments as $userID => $assignments) {            foreach ($assignments as $userAssignment) {                if ($userAssignment->roleName === $roleName && $userAssignment->userId == $userID) {                    $result[] = (string) $userID;                }            }        }        return $result;    }}
 |