Yii2 RBAC Asignaciones múltiples para cada usuario basadas en grupos

Mi aplicación técnicamente tiene dos áreas, un área global (comentarios, perfil de usuario, configuración de usuario, etc.) y un área de grupo (contactos, proyectos, perfil de grupo, configuración de grupo, etc.).

Estoy usando el DBManager RBAC para el área global, y funciona bien, pero tengo problemas para implementar un mecanismo de autorización para el área grupal.

La razón es que los grupos pueden compartirse entre los usuarios, y un usuario puede tener múltiples asignaciones en la tabla group_access (id, group_id, user_id, item_name) ya que pueden ser miembros de múltiples grupos y pueden tener diferentes niveles de permiso para esos grupos

Aquí está mi configuración de autenticación:

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

Mi tabla group_access tiene el mismo esquema que la tabla auth_assignment, con la excepción de que tiene una columna group_id, y la columna user_id NO es única.

El usuario solo tendrá una tarea relacionada con el área global, pero puede tener muchas asignaciones diferentes en el área de grupo, ya que puede tener puentes administrativos en el grupo a, pero solo puentes de usuario en el grupo b.

Mi base de datos está configurada como:

Usuarios (status_id, nombre de usuario, auth_key, password_hash, correo electrónico, etc.)

Grupos (status_id, nombre, descripción, etc.)

Group_Access (group_id, user_id, item_name) Cada usuario obtiene una asignación por cada grupo al que tiene acceso.

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 '],];

La función checkAccess puede calificar el ID de usuario, e incluso puedo usar la versión "can" más corta que funciona muy bien para el usuario conectado, pero necesito verificar el acceso en función de una opción de usuario como la siguiente:

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

Esta es una función personalizada que extrae la identificación del grupo activo de una tabla de opciones del usuario. Si un usuario cambia de grupo, esto cambiará. Mi modelo de opciones tiene tres tipos 'aplicación', 'usuario', 'grupo'.

Sería bueno si pudiera descubrir que una función que funciona de la misma manera que el checkAccess nativo, pero se llama checkGroupAccess y automáticamente obtiene active_group_id y extrae las asignaciones de usuario de la tabla group_access y realiza la verificación de permisos.

Espero que esto tenga sentido.

Gracias por tu tiempo.

Miguel

** ACTUALIZADO **

Entonces, tengo una solución, que utiliza funciones personalizadas checkAccess para verificar los permisos adecuados en el grupo o áreas globales.

Tengo dos tablas (user_access, group_access) que tienen un esquema similar a la tabla predeterminada {{auth_assignment}}, de las cuales no estoy usando ahora. Estoy usando las tablas {{auth_item}}, {{auth_item_child}} y {{auth_rule}}.

Tengo dos modelos, uno para cada una de las tablas de acceso GroupAccess => group_access, y UserAccess => user_access.

También tengo un modelo para las funciones de acceso y lo he asignado a la configuración de componentes.

Aquí está mi modelo de acceso:

<?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;

    }

}

}

Y aquí hay algunos ejemplos de usos para acceder a las funciones:

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

En esencia, copié la función checkAccess del DbManager y la modifiqué un poco para verificar el acceso del usuario según el grupo.

El único problema es que tuve que hacer un cambio en la clase DbManager de origen real para que los $ items (propiedad), checkAccessFromCache (función) y checkAccessRecursive (función) sean todos públicos para que se pueda acceder a ellos fuera de la clase. El principal inconveniente es la capacidad de actualización ...

¿Alguna forma de evitar esto?

Gracias.

Respuestas a la pregunta(1)

Su respuesta a la pregunta