Yii2 RBAC несколько назначений для каждого пользователя на основе групп

мое приложение технически имеет две области: глобальную область (обратная связь, Профиль пользователя, настройки пользователя и т. д.) и область группы (контакты, проекты, профиль группы, Настройки группы и т. д.).

Я использую RBAC DBManager для глобальной области, и он работает просто отлично, но у меня возникли проблемы с реализацией механизма авторизации для групповой области.

причина в том, что группы могут совместно использоваться пользователями, и пользователь может иметь несколько назначений в group_access таблица (id, group_id, user_id, item_name), поскольку они могут быть членами нескольких групп и могут иметь разные уровни разрешений для этих групп.

вот моя настройка auth:

$auth = Yii::$app->authManager;

    // group permissions
    $manageGroupUsers = $auth->createPermission('manage_group_users');
    $manageGroupUsers->description = 'Manage Group Users';
    $auth->add($manageGroupUsers);

    $manageGroupSettings = $auth->createPermission('manage_group_settings');
    $manageGroupSettings->description = 'Manage Group Settings';
    $auth->add($manageGroupSettings);

    // app permissions
    $manageAppUsers = $auth->createPermission('manage_app_users');
    $manageAppUsers->description = 'Manage App Users';
    $auth->add($manageAppUsers);

    $manageAppGroups = $auth->createPermission('manage_app_groups');
    $manageAppGroups->description = 'Manage App Groups';
    $auth->add($manageAppGroups);

    $manageAppSettings = $auth->createPermission('manage_app_settings');
    $manageAppSettings->description = 'Manage App Settings';
    $auth->add($manageAppSettings);

    $manageAppFeedback = $auth->createPermission('manage_app_feedback');
    $manageAppFeedback->description = 'Manage App Feedback';
    $auth->add($manageAppFeedback);

    // group roles
    // -- create role
    $groupUser = $auth->createRole('group_user');
    $groupUser->description = 'Group Users';
    $auth->add($groupUser);

    // -- create role
    $groupAdmin = $auth->createRole('group_admin');
    $groupAdmin->description = 'Group Administrators';
    $auth->add($groupAdmin);
    // add permissions
    $auth->addChild($groupAdmin, $manageGroupUsers);
    $auth->addChild($groupAdmin, $manageGroupSettings);
    // inherit permissions
    $auth->addChild($groupAdmin, $groupUser);

    // -- create role
    $groupCreator = $auth->createRole('group_creator');
    $groupCreator->description = 'Group Creators';
    $auth->add($groupCreator);
    // inherit permissions
    $auth->addChild($groupCreator, $groupAdmin);

    // app roles
    // -- create role
    $appUser = $auth->createRole('app_user');
    $appUser->description = 'App Users';
    $auth->add($appUser);

    // -- create role
    $appSupport = $auth->createRole('app_support');
    $appSupport->description = 'Support Users';
    $auth->add($appSupport);
    // add permissions
    $auth->addChild($appSupport, $manageAppFeedback);

    // -- create role
    $appAdmin = $auth->createRole('app_admin');
    $appAdmin->description = 'App Administrators';
    $auth->add($appAdmin);
    // add permissions
    $auth->addChild($appAdmin, $manageAppUsers);
    $auth->addChild($appAdmin, $manageAppGroups);
    $auth->addChild($appAdmin, $manageAppSettings);
    // inherit permissions
    $auth->addChild($appAdmin, $appUser);
    $auth->addChild($appAdmin, $appSupport);

    // -- create role
    $appCreator = $auth->createRole('app_creator');
    $appCreator->description = 'App Creators';
    $auth->add($appCreator);
    // inherit permissions
    $auth->addChild($appCreator, $appAdmin);

моя таблица group_access имеет ту же схему, что и таблица auth_assignment, за исключением того, что она имеет столбец group_id, а столбец user_id не уникален.

пользователь будет иметь только одно назначение относительно глобальной области, но может иметь различные назначения в области группы, как они могут иметь admin privelidges в группе a, но только пользователь privielidges в группе b.

мой дБ-это:

  1. пользователи (status_id, имя пользователя, auth_key, password_hash, электронная почта и т. д.)

  2. группы (Поле status_id которых содержит значение, название, описание и т. д.)

  3. Group_Access (group_id, user_id, item_name) каждый пользователь получает одно назначение для каждой группы, к которой у них есть доступ.

    sample_group_access_records [ [ 'id' = > 1, 'ид_пользователя' => 35, 'и group_id' => 17, 'item_name' => 'group_admin' ], [ 'id' = > 2, 'ид_пользователя' => 35, 'и group_id' => 356, 'item_name' => 'group_user' ], [ 'id' = > 3, 'ид_пользователя' => 35, 'и group_id' => 211, 'item_name' => 'group_creator' ], ];

функция checkAccess может квалифицировать userID, и я могу даже использовать более короткую версию " can который отлично работает для зарегистрированного пользователя, но мне нужно проверить доступ на основе опции пользователя, как показано ниже:

Option::getOption('user', 'active_group_id')

это пользовательская функция, которая извлекает идентификатор активной группы из таблицы параметров пользователя. Если пользователь переключает группы, это будет изменено. Моя модель опций имеет три типа "приложение", "пользователь", "группа".

было бы неплохо, если бы я мог выяснить функцию, которая работает так же, как родной checkAccess, но называется checkGroupAccess и автоматически получает active_group_id и извлеките назначения пользователей из таблицы group_access и выполните проверку разрешений.

Я надеюсь, что это имеет смысл.

Спасибо за ваше время.

Майк

** обновлено **

Итак, у меня есть решение, которое использует пользовательские функции checkAccess для проверки правильных разрешений на группы или глобальные области.

у меня есть две таблицы (user_access, group_access), которые имеют аналогичную схему по умолчанию {{auth_assignment}} таблица, из которой я сейчас не использую. Я использую таблицы {{auth_item}}, {{auth_item_child}} и {{auth_rule}}.

у меня есть две модели, по одной для каждой из таблиц доступа GroupAccess => group_access и UserAccess => user_access.

у меня также есть модель для функций доступа и сопоставили ее с конфигурацией компонентов.

вот моя модель доступа:

<?php

namespace appmodels;

use Yii;

class Access
{

public function canUser($type, $permissionName, $params = [])
{

    switch ($type) {

        case 'group':

        $userID = Yii::$app->user->identity->id;
        $groupID = Yii::$app->options->getOption('user', 'active_group_id');

        $queryAll = GroupAccess::find()
        ->where('user_id = :user_id and group_id = :group_id', [':user_id' => $userID, ':group_id' => $groupID])
        ->asArray()
        ->all();

        $assignments = [];
        foreach ($queryAll as $queryItem) {
            $assignments[$queryItem['item_name']] = [
            'userId' => $queryItem['user_id'],
            'roleName' => $queryItem['item_name'],
            'createdAt' => $queryItem['created_date'],
            ];
        }

        $result = self::checkAccess($userID, $permissionName, $assignments, $params);

        return $result;

        break;

        case 'user':

        $userID = Yii::$app->user->identity->id;

        $queryAll = UserAccess::find()
        ->where(['user_id' => $userID])
        ->asArray()
        ->all();

        $assignments = [];
        foreach ($queryAll as $queryItem) {
            $assignments[$queryItem['item_name']] = [
            'userId' => $queryItem['user_id'],
            'roleName' => $queryItem['item_name'],
            'createdAt' => $queryItem['created_date'],
            ];
        }

        $result = self::checkAccess($userID, $permissionName, $assignments, $params);

        return $result;

        break;

    }

}

public function checkAccess($userID, $permissionName, $assignments, $params = [])
{

    $auth = Yii::$app->authManager;

    $auth->loadFromCache();

    if ($auth->items !== null) {
        return $auth->checkAccessFromCache($userID, $permissionName, $params, $assignments);
    } else {
        return $auth->checkAccessRecursive($userID, $permissionName, $params, $assignments);
    }
}

public function assign($type, $role, $userID = null, $groupID = null)
{

    switch ($type) {

        case 'group':

        // clear existing assigments
        self::revoke('group', $userID, $groupID);

        $groupAccess = new GroupAccess();
        $groupAccess->group_id = $groupID;
        $groupAccess->user_id = $userID;
        $groupAccess->item_name = $role;
        $groupAccess->created_date = time();

        return $groupAccess->save();

        break;

        case 'user':

        // clear existing assignments
        self::revoke('user', $userID);

        $userAccess = new UserAccess();
        $userAccess->user_id = $userID;
        $userAccess->item_name = $role;
        $userAccess->created_date = time();

        return $userAccess->save();

        break;

    }

}

public function revoke($type, $userID, $groupID = null)
{

    switch ($type) {

        case 'group':

        GroupAccess::deleteAll('user_id = :user_id and group_id = :group_id', [':user_id' => $userID, ':group_id' => $groupID]);

        break;

        case 'user':

        UserAccess::deleteAll('user_id = :user_id', [':user_id' => $userID]);

        break;

    }

}

}

и вот некоторые примеры использования для доступа к функции:

// get the user option
echo Yii::$app->options->getOption('user', 'active_group_id');

// assign group role
Yii::$app->access->assign('group', 'group_creator', 22, 18);
// assign user role
Yii::$app->access->assign('user', 'app_user', 22);

// revoke group access
Yii::$app->access->revoke('group', 22, 18);
// revoke user access
Yii::$app->access->revoke('user', 22);

// test user permission
var_dump(Yii::$app->access->canUser('user', 'manage_app_settings'));
// test the group permission
var_dump(Yii::$app->access->canUser('group', 'manage_group_settings'));

по сути, я скопировал функцию checkAccess из DbManager и немного переработал ее, чтобы проверить доступ пользователей на основе группы.

единственная проблема заключается в том, что мне пришлось внести изменения в фактический исходный класс DbManager, чтобы сделать $items (свойство), checkAccessFromCache (функция) и checkAccessRecursive (функция) общедоступными, чтобы к ним можно было получить доступ за пределами класса. Основным недостатком является возможность обновления...

любой путь вокруг это?

спасибо.

1 ответов


вот рабочее окончательное решение.

Итак, еще один день, больше рефакторинга.

мое окончательное решение использует функцию checkAccess в исходных файлах DbManager/ManagerInterface, но я добавил параметр $assignments для передачи. Основная проблема заключается в том, что мне пришлось создать свой собственный список заданий для проверки. Убедитесь, что вы прокомментировали строки, в которых установлена переменная $assignments.

вот моя новая модель доступа:

<?php

namespace app\models;

use Yii;

class Access
{

public function canUser($type, $permissionName, $params = [])
{

    $auth = Yii::$app->authManager;

    switch ($type) {

        case 'group':

        $userID = Yii::$app->user->identity->id;
        $groupID = Yii::$app->options->getOption('user', 'active_group_id');

        $queryAll = GroupAccess::find()
        ->where('user_id = :user_id and group_id = :group_id', [':user_id' => $userID, ':group_id' => $groupID])
        ->asArray()
        ->all();

        $assignments = [];
        foreach ($queryAll as $queryItem) {
            $assignments[$queryItem['item_name']] = [
            'userId' => $queryItem['user_id'],
            'roleName' => $queryItem['item_name'],
            'createdAt' => $queryItem['created_date'],
            ];
        }

        $result = $auth->checkAccess($userID, $permissionName, $assignments, $params);

        return $result;

        break;

        case 'user':

        $userID = Yii::$app->user->identity->id;

        $queryAll = UserAccess::find()
        ->where('user_id = :user_id', [':user_id' => $userID])
        ->asArray()
        ->all();

        $assignments = [];
        foreach ($queryAll as $queryItem) {
            $assignments[$queryItem['item_name']] = [
            'userId' => $queryItem['user_id'],
            'roleName' => $queryItem['item_name'],
            'createdAt' => $queryItem['created_date'],
            ];
        }

        $result = $auth->checkAccess($userID, $permissionName, $assignments, $params);

        return $result;

        break;

    }

}

public function assign($type, $role, $userID = null, $groupID = null)
{

    switch ($type) {

        case 'group':

        // clear existing assigments
        self::revoke('group', $userID, $groupID);

        $groupAccess = new GroupAccess();
        $groupAccess->group_id = $groupID;
        $groupAccess->user_id = $userID;
        $groupAccess->item_name = $role;
        $groupAccess->created_date = time();

        return $groupAccess->save();

        break;

        case 'user':

        // clear existing assignments
        self::revoke('user', $userID);

        $userAccess = new UserAccess();
        $userAccess->user_id = $userID;
        $userAccess->item_name = $role;
        $userAccess->created_date = time();

        return $userAccess->save();

        break;

    }

}

public function revoke($type, $userID, $groupID = null)
{

    switch ($type) {

        case 'group':

        GroupAccess::deleteAll('user_id = :user_id and group_id = :group_id', [':user_id' => $userID, ':group_id' => $groupID]);

        break;

        case 'user':

        UserAccess::deleteAll('user_id = :user_id', [':user_id' => $userID]);

        break;

    }

}

}

и здесь является ли измененная функция checkAccess в DbManager:

public function checkAccess($userId, $permissionName, $assignments, $params = [])
{
    //$assignments = $this->getAssignments($userId);
    $this->loadFromCache();
    if ($this->items !== null) {
        return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);
    } else {
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
    }
}

и вот модифицированная функция checkAccess в ManagerInterface.на PHP:

public function checkAccess($userId, $permissionName, $assignments, $params = []);

я не менял функции $items, checkAccessFromCache и checkAccessRecursive на public из protected.

и вот моя модель UserAccess:

<?php

namespace app\models;

use Yii;
use yii\db\ActiveRecord;

 /**
 * This is the model class for table "app_user_access".
 *
 * @property integer $id
 * @property integer $user_id
 * @property string $item_name
 * @property integer $created_date
 *
 * @property AppAuthItem $itemName
 * @property AppUsers $user
 */
class UserAccess extends ActiveRecord
{
/**
 * @inheritdoc
 */
public static function tableName()
{
    return 'app_user_access';
}

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        [['user_id', 'item_name', 'created_date'], 'required'],
        [['user_id', 'created_date'], 'integer'],
        [['item_name'], 'string', 'max' => 64]
    ];
}

/**
 * @inheritdoc
 */
public function attributeLabels()
{
    return [
        'id' => 'ID',
        'user_id' => 'User ID',
        'item_name' => 'Item Name',
        'created_date' => 'Created Date',
    ];
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getItemName()
{
    return $this->hasOne(AppAuthItem::className(), ['name' => 'item_name']);
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getUser()
{
    return $this->hasOne(AppUsers::className(), ['id' => 'user_id']);
}
}

и вот модель GroupAccess:

<?php

namespace app\models;

use Yii;
use yii\db\ActiveRecord;

 /**
 * This is the model class for table "app_group_access".
 *
 * @property integer $id
 * @property integer $group_id
 * @property integer $user_id
 * @property string $item_name
 * @property integer $created_date
 *
 * @property AppUsers $user
 * @property AppAuthItem $itemName
 * @property AppGroups $group
 */
class GroupAccess extends ActiveRecord
{
/**
 * @inheritdoc
 */
public static function tableName()
{
    return 'app_group_access';
}

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        [['group_id', 'user_id', 'item_name', 'created_date'], 'required'],
        [['group_id', 'user_id', 'created_date'], 'integer'],
        [['item_name'], 'string', 'max' => 64]
    ];
}

/**
 * @inheritdoc
 */
public function attributeLabels()
{
    return [
        'id' => 'ID',
        'group_id' => 'Group ID',
        'user_id' => 'User ID',
        'item_name' => 'Item Name',
        'created_date' => 'Created Date',
    ];
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getUser()
{
    return $this->hasOne(AppUsers::className(), ['id' => 'user_id']);
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getItemName()
{
    return $this->hasOne(AppAuthItem::className(), ['name' => 'item_name']);
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getGroup()
{
    return $this->hasOne(AppGroups::className(), ['id' => 'group_id']);
}
}

и еще раз, некоторые полезные образцы:

// assign group role
Yii::$app->access->assign('group', 'group_creator', 24, 20);
// assign user role
Yii::$app->access->assign('user', 'app_user', 24);

// revoke group
Yii::$app->access->revoke('group', 22, 18);
// revoke user
Yii::$app->access->revoke('user', 22);

// test user permission
var_dump(Yii::$app->access->canUser('user', 'manage_app_settings'));
// test the group permission
var_dump(Yii::$app->access->canUser('group', 'manage_group_settings'));