RBAC not working properly in Yii2

Hi,
I am using Yii2 basic. I two tables admin and employee. Both tables have id which is primary key, username, password. I have implemented the scenario where user can login from two different tables. Now I have RBAC implementation. Here I have created four tables which are required for RBAC. Now I have also created rules, permissions and roles in RBAC init(). I have assigned permissions to roles and roles to users.

I have a MultiUser.php model where it checks the instance of user whether it is of admin or employee and on that basis the user logs in.

Following is the MultiUser.php model

<?php

namespace app\models;

use app\models\Employee;
use app\models\Admin;

class MultiUser extends \yii\base\BaseObject implements \yii\web\IdentityInterface
{
    /**
     * @var \yii\db\BaseActiveRecord
     */
    private $_user;

    /**
     * {@inheritdoc}
     */
    public static function findIdentity($id)
    {
        // $id will be something like "admin-1" or "employee-1"
        list($type, $pk) = explode('-', $id);

        switch ($type) {
            case 'admin':
                $model = Admin::findOne($pk);
                break;
            case 'employee':
                $model = Employee::findOne($pk);
                break;
        }

        if (isset($model)) {
            return new static(['user' => $model]);
        }
    }

    /**
     * {@inheritdoc}
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
        throw new \yii\base\NotSupportedException();
    }

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        $prefix = $this->_user instanceof Admin ? 'admin' : 'employee';
        return $prefix . '-' . $this->_user->getPrimaryKey();
    }
	public function getOnlyid()
    {
        //$prefix = $this->_user instanceof Admin ? 'admin' : 'employee';

        return  $this->_user->getPrimaryKey();
    }

    /**
     * {@inheritdoc}
     */
    public function getAuthKey()
    {
        $secret = \Yii::$app->request->cookieValidationKey;

        return sha1($secret . get_class($this->_user) . $this->_user->getPrimaryKey());
    }

     public function validateAuthKey($authKey)
    {
        $secret = Yii::$app->request->cookieValidationKey;
        $computedKey = sha1($secret . get_class($this->_user) . $this->_user->getPrimaryKey());

        return $authKey === $computedKey;
    }

    public function setUser(\yii\db\BaseActiveRecord $user)
    {
        $this->_user = $user;
    }

    public function getUsername()
    {
        return $this->_user->username;
    }
}

I have a table called groupdetails which has GroupId. employee table has a relation with grouodetails table. meaning One employee has many groupdetails. I created the CRUD for groupdetails. I have also checked for authorization in controller

Following is the Groupdetails controller.

<?php

namespace app\controllers;

use Yii;
use app\models\Groupdetails;
use app\models\GroupdetailsSearch;
use yii\web\UploadedFile;
use yii\web\Controller;

use yii\filters\VerbFilter;
use app\models\FieldofficerRule;
use yii\filters\AccessControl;
use yii\web\NotFoundHttpException;
use yii\web\ForbiddenHttpException;
/**
 * GroupdetailsController implements the CRUD actions for Groupdetails model.
 */
class GroupdetailsController extends Controller
{
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
        ];
    }

    /**
     * Lists all Groupdetails models.
     * @return mixed
     */
    public function actionIndex()
    {

       $searchModel = new GroupdetailsSearch();

        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

    /**
     * Displays a single Groupdetails model.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionView($id)
    {
		$model=$this->findModel($id);

		$Id=\Yii::$app->user->id;



 if(\Yii::$app->user->can('viewGroup',['model'=>$model]))
		{
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
		}
		else
		{
		   throw new ForbiddenHttpException("You can't view other groups details");
		}
    }

    /**
     * Creates a new Groupdetails model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {



		 if(\Yii::$app->user->can('createGroup'))
		{
		//$this->layout = 'layoutfile';
        $model = new Groupdetails();




       if ($model->load(Yii::$app->request->post()) ) {

            $model->id=\Yii::$app->user->identity->getOnlyid();
			$model->save(false);




            return $this->redirect(['view', 'id' => $model->GroupId]);
        } else {
            return $this->render('create', [
                'model' => $model,

            ]);
        }
		}
		else
		{

		   throw new ForbiddenHttpException("You have no access to create groups");
		}




    }

    /**
     * Updates an existing Groupdetails model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionUpdate($id)
    {
		$model = $this->findModel($id);

		if(\Yii::$app->user->can('updateGroup', ['model' => $model]))
        {



        if ($model->load(Yii::$app->request->post())) {


				$model->id=\Yii::$app->user->identity->getOnlyid();
					$model->save();



            return $this->redirect(['view', 'id' => $model->GroupId]);
        }
		else {
            return $this->render('update', [
                'model' => $model,

            ]);
        }
		}



		else
		{
		     throw new ForbiddenHttpException("You can't update other groups details");
		}

    }

    /**
     * Deletes an existing Groupdetails model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionDelete($id)
    {
		$model = $this->findModel($id);
		if(\Yii::$app->user->can('deleteGroup', ['model' => $model]))
		{









		$model->delete();

        return $this->redirect(['index']);
		}
		else
		{
		  throw new ForbiddenHttpException("You can't delete other groups details");
		}

    }

    /**
     * Finds the Groupdetails model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Groupdetails the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Groupdetails::findOne($id)) !== null) {
            return $model;
        }

        throw new NotFoundHttpException('The requested page does not exist.');
    }
}

In auth_assignment table I have the following,

image

Now when the user logs in from admin table and acccess groupdetails he can have access to create, view, update and delete actions.

But now when the user logs in from employee table he can create a record for groupdetails but he cant view, update, delete his groupdetails.

Below is my rule for RBAC

<?php

namespace app\rbac;

use yii\rbac\Rule;
use app\models\Groupdetails;

/**
 * Checks if authorID matches user passed via params
 */
class FieldofficerRule extends Rule
{
    public $name = 'isfieldofficer';

    /**
     * @param string|int $user the user ID.
     * @param Item $item the role or permission that this rule is associated with
     * @param array $params parameters passed to ManagerInterface::checkAccess().
     * @return bool a value indicating whether the rule permits the role or permission it is associated with.
     */
    public function execute($user, $item, $params)
    {
        return isset($params['model']) ? $params['model']->id == $user : false;
    }
}

How to resolve?

So, your issue is the your employee role doesn’t have the view permission?

That would be assigned in the auth_item_child table
employee would be the parent and viewGroup would be the child
then add new rows with updateGroup and deleteGroup as children as well

(I would not assign any of the children of employee directly to admin, instead make employee a child of admin so it inherits all the children of employee - so whenever you add a new role make admin the parent if it can do all that the child does.)

I’m not sure if it’s a sql view, but your user_id in the auth_assignment table looks wrong.
It should only be a number. So for admin it should just be 1
that number must match the id set in the user table.
the id should be an incrementing integer, and it must be unique per user.

No I have only two roles i.e admin and Field Officer. The user_id in auth_assignment has to be admin-1 or employee-1 because i am using two tables to login, if the id of employee is 1 and the id of admin is 1 then how will it check the identity. so for distinguishing between them I have used MultiUser model which is above to identify whether the user is logged in from admin or employee table. If identity is admin-1 it means logged in from admin table where id is 1. Id identity is employee-1 then logged in from employee table whose id is 1. So I have following in RBAC controller.

Yes Following is RBAC controller

<?php
namespace app\commands;

use Yii;
use yii\console\Controller;

class RbacController extends Controller
{
    public function actionInit()
    {
        $auth = Yii::$app->authManager;
     // $auth->removeAll(); //remove previous rbac.php files under console/data

		$rule = new \app\rbac\FieldofficerRule;
		$auth->add($rule);



		$indexAll = $auth->createPermission('indexAll');
        $indexAll->description = 'Index Page';
        $auth->add($indexAll);


		$createGroup = $auth->createPermission('createGroup');
        $createGroup->description = 'Create a Group';
        $auth->add($createGroup);


        $updateGroup = $auth->createPermission('updateGroup');
        $updateGroup->description = 'Update Group';
        $auth->add($updateGroup);


		$viewGroup = $auth->createPermission('viewGroup');
        $viewGroup->description = 'View Group';
        $auth->add($viewGroup);

		$deleteGroup = $auth->createPermission('deleteGroup');
        $deleteGroup->description = 'Delete Group';
        $auth->add($deleteGroup);







        $fieldofficer = $auth->createRole('Field Officer');
        $auth->add($fieldofficer);
        $auth->addChild($fieldofficer, $createGroup);


        $admin = $auth->createRole('Admin');
        $auth->add($admin);



        $auth->addChild($admin, $fieldofficer);
		$auth->addChild($admin, $updateGroup);
		$auth->addChild($admin, $viewGroup);
		$auth->addChild($admin, $deleteGroup);
		$auth->addChild($admin, $indexAll);




        $updateOwnGroup = $auth->createPermission('updateOwnGroup');
		$updateOwnGroup->description = 'Update own Group';
		$updateOwnGroup->ruleName = $rule->name;
		$auth->add($updateOwnGroup);

		$viewOwnGroup = $auth->createPermission('viewOwnGroup');
		$viewOwnGroup->description = 'View own Group';
		$viewOwnGroup->ruleName = $rule->name;
		$auth->add($viewOwnGroup);


		$deleteOwnGroup = $auth->createPermission('deleteOwnGroup');
		$deleteOwnGroup->description = 'Delete own Group';
		$deleteOwnGroup->ruleName = $rule->name;
		$auth->add($deleteOwnGroup);




		$auth->addChild($viewOwnGroup, $viewGroup);
		$auth->addChild($updateOwnGroup, $updateGroup);
		$auth->addChild($deleteOwnGroup, $deleteGroup);



		$auth->addChild($fieldofficer, $updateOwnGroup);
		$auth->addChild($fieldofficer, $viewOwnGroup);


		$auth->addChild($fieldofficer, $deleteOwnGroup);








    }
}
?>

Sorry I stated the user as the role

I’m trying to figure out what your asking, as it’s not really clear. It seems you want to allow the Field Officer role to view/update/delete. Is that correct?

If it is, your issue is what I said. You didn’t assign the child values for view/update/delete to the Field Officer

It should read like this

    $fieldofficer = $auth->createRole('Field Officer');
    	$auth->add($fieldofficer);

    $auth->addChild($fieldofficer, $createGroup);
    $auth->addChild($admin, $updateGroup);
    $auth->addChild($admin, $viewGroup);
    $auth->addChild($admin, $deleteGroup);

    $admin = $auth->createRole('Admin');
   		$auth->add($admin);

    $auth->addChild($admin, $fieldofficer);
	$auth->addChild($admin, $indexAll);

I mentioned the user_id because that could also be the issue.
If the user isn’t properly linked - from your description the Field officer assignment seems wrong to me. If it is user 1 in the employee table shouldn’t it be employee-1?
Or its right if you are logging in as user 2 in the employee table to test.

Best of Luck

Yes, what you are saying is correct. But the Field Officer cannot view, update, delete his own groups details. I have already mentioned three permissions created in Rbac controller such as

  1. updateOwnGroup
  2. viewOwnGroup
  3. deleteOwnGroup.

I have applied rules to these three permissions. It checks whether the particular Field Officer has creaeted the group then only he can view, update and delete his own groups.

But in this case he is not able to view,delete neither update his own groups.

Gotcha - so you’re trying to get the *OwnGroup to work.

does the groups model have a created_by property?
(or createdBy - however you named it)

If that’s not it could you share the rule Class that was produced from that command?

Are you getting any errors or details in the logs that show what is happening?
You should also see if the FieldofficerRule is being called at all

  • Yes I am trying that each Field Officer should access their own created groups.

  • In groupdetails table I have a field id where it stores the id of the employee(meaning this employee has created the group). It is the foreign key in this table

  • In log messages there is one error which is as:

yii\web\ForbiddenHttpException: You can't view other groups details in F:\xampp\htdocs\newbasic\controllers\GroupdetailsController.php:75
Stack trace:
#0 [internal function]: app\controllers\GroupdetailsController->actionView('25')
#1 F:\xampp\htdocs\newbasic\vendor\yiisoft\yii2\base\InlineAction.php(57): call_user_func_array(Array, Array)
#2 F:\xampp\htdocs\newbasic\vendor\yiisoft\yii2\base\Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#3 F:\xampp\htdocs\newbasic\vendor\yiisoft\yii2\base\Module.php(528): yii\base\Controller->runAction('view', Array)
#4 F:\xampp\htdocs\newbasic\vendor\yiisoft\yii2\web\Application.php(103): yii\base\Module->runAction('groupdetails/vi...', Array)
#5 F:\xampp\htdocs\newbasic\vendor\yiisoft\yii2\base\Application.php(386): yii\web\Application->handleRequest(Object(yii\web\Request))
#6 F:\xampp\htdocs\newbasic\web\index.php(12): yii\base\Application->run()
#7 {main}

*Edit - sorry just saw you posted the rule in the first post…

Your issue is most likely in the rule file. (or getting to this rule)
My guess is here:

public function execute($user, $item, $params)
    {
        return isset($params['model']) ? $params['model']->id == $user : false;
    }
  • check that yii is finding the rule file
    should throw a file does not exist error if not
  • check that it knows what ‘model’ is
    your issue could be here if the param is not defined
  • check that ‘id’ is the correct name
    if its trying to find the user id where the page id is it won’t work

I don’t understand your Groupdetails Structure
It seems really weird to me that the id of the content you want to access is the id of the user.

For the Logs, that is the server logs, I assumed you were running Yii in dev mode and was just asking if you saw where it was having the issue. It should show you step by step what is working.
Sorry for not being clear.

ori post
An RBAC rule must be written as a class that extends yii\rbac\Rule and stored in an ordinary php file. Then you can add it to the authManager.

You have this line:
$rule = new \app\rbac\FieldofficerRule;

You have to write “FieldofficerRule.php” before this will work.

Guide > Authorization > Using Rules

http://www.yiiframework.com/doc-2.0/guide-security-authorization.html#using-rules

public function execute($user, $item, $params)
    {
        return isset($params['model']) ? $params['model']->id == $user : false;
    }

Here the field id in groupdetails table is the foriegn key of employee id field. And the groupdetails model identity number is the field GroupId.

Where should I write FieldofficerRule.php? I checked in the guide but there not seems to be any change in my code.

I tried once more by changing the name of primary key fields of employee and admin. But still the same issue. I am not finding the issue.

How should I check the rule file? How should I check the model is defined. model is defined as shown in GroupdetailsController.

Where do you currently have your FieldofficerRule.php?
(code in first post)
Your telling Yii to look here: \app\rbac\FieldofficerRule.php

To check, use some form debug
for example to debug your params, place this before the return

// first line is text to help you find in the logs
    Yii::debug('XYZ Item');
    Yii::debug($item);

    Yii::debug('XYZ Params');
    Yii::debug($params);

if model isn’t in params than none of that code will run
(first statement is if params model isset)

If nothing shows up in the log, its not getting to the rule
you should already have “FieldofficerRule.php does not exist” in the log

The rule FieldofficerRule.php exists in newbasic/rbac/ where newbasic is the name of my project folder and rbac is the folder where I have stored the rule file.

When I add Yii::debug($model); in actionView() then in logs, I have

unserialize('O:23:"app\\models\\Groupdetails":10:{s:36:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_attributes";a:20:{s:7:"GroupId";i:26;s:21:"IdentificationDetails";i:4;s:9:"GroupName";s:11:"Raju Bachat";s:12:"TotalMembers";s:1:"3";s:12:"StageOfGroup";i:3;s:13:"ResidenceType";s:5:"Rural";s:13:"FormationType";s:4:"Open";s:13:"FormationDate";s:10:"2020-06-01";s:8:"Category";s:4:"Open";s:3:"IGA";s:3:"Yes";s:16:"RegistrationWith";s:8:"IF/Other";s:12:"RegisterUsed";s:8:"IF/Other";s:12:"PassBookUsed";s:8:"IF/Other";s:11:"MeetingDate";s:10:"2020-06-11";s:13:"MonthlySaving";d:1000;s:13:"BankAccountNo";s:11:"10212512022";s:15:"AccountOpenDate";s:10:"2020-06-01";s:8:"BankName";s:20:"Punjab National Bank";s:6:"Branch";s:10:"Ahmednagar";s:5:"Photo";s:4:"NULL";}s:39:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_oldAttributes";a:20:{s:7:"GroupId";i:26;s:21:"IdentificationDetails";i:4;s:9:"GroupName";s:11:"Raju Bachat";s:12:"TotalMembers";s:1:"3";s:12:"StageOfGroup";i:3;s:13:"ResidenceType";s:5:"Rural";s:13:"FormationType";s:4:"Open";s:13:"FormationDate";s:10:"2020-06-01";s:8:"Category";s:4:"Open";s:3:"IGA";s:3:"Yes";s:16:"RegistrationWith";s:8:"IF/Other";s:12:"RegisterUsed";s:8:"IF/Other";s:12:"PassBookUsed";s:8:"IF/Other";s:11:"MeetingDate";s:10:"2020-06-11";s:13:"MonthlySaving";d:1000;s:13:"BankAccountNo";s:11:"10212512022";s:15:"AccountOpenDate";s:10:"2020-06-01";s:8:"BankName";s:20:"Punjab National Bank";s:6:"Branch";s:10:"Ahmednagar";s:5:"Photo";s:4:"NULL";}s:33:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_related";a:0:{}s:47:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_relationsDependencies";a:0:{}s:23:"' . "\0" . 'yii\\base\\Model' . "\0" . '_errors";N;s:27:"' . "\0" . 'yii\\base\\Model' . "\0" . '_validators";N;s:25:"' . "\0" . 'yii\\base\\Model' . "\0" . '_scenario";s:7:"default";s:27:"' . "\0" . 'yii\\base\\Component' . "\0" . '_events";a:0:{}s:35:"' . "\0" . 'yii\\base\\Component' . "\0" . '_eventWildcards";a:0:{}s:30:"' . "\0" . 'yii\\base\\Component' . "\0" . '_behaviors";a:0:{}}')

When I place Yii::debug($params) in rule as follows

public function execute($user, $item, $params)
    {
		Yii::debug($params);
        return isset($params['model']) ? $params['model']->id == $user : false;

    }

Then in debug log, I have below

[
    'model' => unserialize('O:23:"app\\models\\Groupdetails":10:{s:36:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_attributes";a:20:{s:7:"GroupId";i:26;s:21:"IdentificationDetails";i:4;s:9:"GroupName";s:11:"Raju Bachat";s:12:"TotalMembers";s:1:"3";s:12:"StageOfGroup";i:3;s:13:"ResidenceType";s:5:"Rural";s:13:"FormationType";s:4:"Open";s:13:"FormationDate";s:10:"2020-06-01";s:8:"Category";s:4:"Open";s:3:"IGA";s:3:"Yes";s:16:"RegistrationWith";s:8:"IF/Other";s:12:"RegisterUsed";s:8:"IF/Other";s:12:"PassBookUsed";s:8:"IF/Other";s:11:"MeetingDate";s:10:"2020-06-11";s:13:"MonthlySaving";d:1000;s:13:"BankAccountNo";s:11:"10212512022";s:15:"AccountOpenDate";s:10:"2020-06-01";s:8:"BankName";s:20:"Punjab National Bank";s:6:"Branch";s:10:"Ahmednagar";s:5:"Photo";s:4:"NULL";}s:39:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_oldAttributes";a:20:{s:7:"GroupId";i:26;s:21:"IdentificationDetails";i:4;s:9:"GroupName";s:11:"Raju Bachat";s:12:"TotalMembers";s:1:"3";s:12:"StageOfGroup";i:3;s:13:"ResidenceType";s:5:"Rural";s:13:"FormationType";s:4:"Open";s:13:"FormationDate";s:10:"2020-06-01";s:8:"Category";s:4:"Open";s:3:"IGA";s:3:"Yes";s:16:"RegistrationWith";s:8:"IF/Other";s:12:"RegisterUsed";s:8:"IF/Other";s:12:"PassBookUsed";s:8:"IF/Other";s:11:"MeetingDate";s:10:"2020-06-11";s:13:"MonthlySaving";d:1000;s:13:"BankAccountNo";s:11:"10212512022";s:15:"AccountOpenDate";s:10:"2020-06-01";s:8:"BankName";s:20:"Punjab National Bank";s:6:"Branch";s:10:"Ahmednagar";s:5:"Photo";s:4:"NULL";}s:33:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_related";a:0:{}s:47:"' . "\0" . 'yii\\db\\BaseActiveRecord' . "\0" . '_relationsDependencies";a:0:{}s:23:"' . "\0" . 'yii\\base\\Model' . "\0" . '_errors";N;s:27:"' . "\0" . 'yii\\base\\Model' . "\0" . '_validators";N;s:25:"' . "\0" . 'yii\\base\\Model' . "\0" . '_scenario";s:7:"default";s:27:"' . "\0" . 'yii\\base\\Component' . "\0" . '_events";a:0:{}s:35:"' . "\0" . 'yii\\base\\Component' . "\0" . '_eventWildcards";a:0:{}s:30:"' . "\0" . 'yii\\base\\Component' . "\0" . '_behaviors";a:0:{}}'),
]

So its getting to the rule (the params log ran) :+1:
And you are getting the output of model :+1:

In the params log it is returning the ‘model’

However there is no id field
you are asking it to compare

$params['model']->id == $user

:id: doesn’t exist in your model

Looks like this is a group model - so if you had a field ( created_by ) in the group model that is normally what you would query here for own group.

Or if one of the fields ( GroupId ? ) you see in the log is the user_id of the creator, use that instead of id.