Yii2 RBAC várias atribuições para cada usuário com base em grupos

Meu aplicativo tecnicamente possui duas áreas, uma área global (feedback, perfil do usuário, configurações do usuário, etc.) e uma área do grupo (contatos, projetos, perfil do grupo, configurações do grupo etc.).

Estou usando o RBAC DBManager para a área global e funciona muito bem, mas estou tendo problemas para implementar um mecanismo de autorização para a área do grupo.

O motivo é que os grupos podem ser compartilhados entre os usuários, e um usuário pode ter várias atribuições na tabela group_access (id, group_id, user_id, item_name), pois podem ser membros de vários grupos e podem ter diferentes níveis de permissão para esses grupos.

Aqui está minha configuração de autenticação:

$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);

Minha tabela group_access tem o mesmo esquema que a tabela auth_assignment, com a exceção de que possui uma coluna group_id e a coluna user_id NÃO é exclusiva.

O usuário terá apenas uma atribuição referente à área global, mas poderá ter muitas atribuições diferentes na área do grupo, pois poderá ter privilégios administrativos no grupo a, mas apenas privilégios do usuário no grupo b.

Meu banco de dados está configurado como:

Usuários (status_id, nome de usuário, auth_key, password_hash, email, etc)

Grupos (status_id, nome, descrição etc.)

Group_Access (group_id, user_id, item_name) Cada usuário recebe uma atribuição para cada grupo ao qual tem acesso.

sample_group_access_records [['id' => 1, 'user_id' => 35, 'group_id' => 17, 'item_name' => 'group_admin'], ['id' => 2, 'user_id' => 35, ' group_id '=> 356,' item_name '=>' group_user '], [' id '=> 3,' user_id '=> 35,' group_id '=> 211,' item_name '=>' group_creator '],];

A função checkAccess pode qualificar o ID do usuário e posso até usar a versão mais curta "can", que funciona muito bem para o usuário conectado, mas preciso verificar o acesso com base em uma opção de usuário como abaixo:

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

Essa é uma função personalizada que extrai o ID do grupo ativo de uma tabela de opções do usuário. Se um usuário alternar grupos, isso será alterado. Meu modelo de opções possui três tipos 'app', 'user', 'group'.

Seria bom se eu descobrisse que uma função que funciona da mesma forma que o checkAccess nativo, mas se chama checkGroupAccess, obtém automaticamente o active_group_id e puxa as atribuições do usuário da tabela group_access e executa a verificação de permissão.

Espero que isto faça sentido.

Obrigado pelo seu tempo.

Mike

** ATUALIZADA **

Portanto, eu tenho uma solução que usa funções personalizadas checkAccess para verificar as permissões apropriadas nas áreas globais ou de grupo.

Eu tenho duas tabelas (user_access, group_access) que possuem um esquema semelhante à tabela padrão {{auth_assignment}}, das quais não estou usando agora. Estou usando as tabelas {{auth_item}}, {{auth_item_child}} e {{auth_rule}}.

Eu tenho dois modelos, um para cada uma das tabelas de acesso GroupAccess => group_access e UserAccess => user_access.

Eu também tenho um modelo para as funções de acesso e o mapeei para a configuração dos componentes.

Aqui está o meu modelo de acesso:

<?php

namespace app\models;

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;

    }

}

}

E aqui estão alguns exemplos de usos para acessar as funções:

// 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'));

Em essência, copiei a função checkAccess do DbManager e a refiz um pouco para verificar o acesso do usuário com base no grupo.

O único problema é que tive que fazer uma alteração na classe DbManager de origem real para tornar os $ items (property), checkAccessFromCache (function) e checkAccessRecursive (function) todos públicos, para que possam ser acessados fora da classe. A principal desvantagem é a atualidade ...

Alguma maneira de contornar isso?

Obrigado.

questionAnswers(1)

yourAnswerToTheQuestion