Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yii2 - Override checkAccess in rest ActiveController

Tags:

php

yii

yii2

Product.supplierID = Supplier.supplierID
---------         ----------
|Product|---------|Supplier|
---------         ----------
                       |
                       |  Supplier.supplierID = User.supplierID
                       |
                   ---------
                   |  User |
                   ---------

Using the above table structure, the application uses sub-classes of ActiveController, with overridden prepareDataProvider to limit the index list of each Product a logged in User can see to those with matching supplierID values. Something like this in the actions() method of ProductController.

$actions['index']['prepareDataProvider'] = function($action)
{
    $query = Product::find();
    if (Yii::$app->user->can('supplier') &&
        Yii::$app->user->identity->supplierID) {
        $query->andWhere(['supplierID' => Yii::$app->user->identity->supplierID]);
    }
    return new ActiveDataProvider(['query' => $query]);
};

This works fine, however I'm looking to use checkAccess() to limit actionView() for a single Product.

At the moment, a logged in User can access a Product by changing the productID in the URL, whether or not the have the appropriate supplierID.

It looks like I can't access the particular instance of Product, to check the supplierID, until actionView() has returned which is when I want the check to happen.

Can I override checkAccess() to restrict access and throw the appropriate ForbiddenHttpException?

like image 242
Pedro del Sol Avatar asked Oct 16 '15 10:10

Pedro del Sol


1 Answers

What about a function that checks if a model exist :

protected function modelExist($id)
{
    return Product::find()
    ->where([ 'productID' => $id ])
    ->andWhere(['supplierID' => Yii::$app->user->identity->supplierID ])
    ->exists(); 
}

If productID is your Product Primary Key, then a request to /products/1 will be translated by yii\rest\UrlRule to /products?productID=1.

In that case, when productID is provided as a param, you can use beforeAction to make a quick check if such model exist & let the action be executed or throw an error if it doesn't :

// this array will hold actions to which you want to perform a check
public $checkAccessToActions   = ['view','update','delete'];

public function beforeAction($action) {
    if (!parent::beforeAction($action)) return false;

        $params = Yii::$app->request->queryParams;

        if (isset($params['productID']) {
           foreach ($this->checkAccessToActions as $action) {
              if ($this->action->id === $action) {
                  if ($this->modelExist($params['productID']) === false)
                       throw new NotFoundHttpException("Object not found");
              }
           }
    }
    return true;
}

update

As the question is about Overriding the checkAccess method in rest ActiveController I thought it would be useful to leave an example.

In the way how Yii2 REST was designed, all of delete, update and view actions will invoke the checkAccess method once the model instance is loaded:

// code snippet from yii\rest\ViewAction
$model = $this->findModel($id);
if ($this->checkAccess) {
    call_user_func($this->checkAccess, $this->id, $model);
}

The same is true for the create and index actions except that they won't pass any model instance to it: call_user_func($this->checkAccess, $this->id).

So what you are trying to do (throwing a ForbiddenHttpException when a user is trying to view, update or delete a product he is not its supplier) may also be achieved this way:

public function checkAccess($action, $model = null, $params = [])
{
    if ($action === 'view' or $action === 'update' or $action === 'delete') 
    {
        if ( Yii::$app->user->can('supplier') === false
             or Yii::$app->user->identity->supplierID === null
             or $model->supplierID !== \Yii::$app->user->identity->supplierID ) 
        {
             throw new \yii\web\ForbiddenHttpException('You can\'t '.$action.' this product.');
        }

    }
}
like image 155
Salem Ouerdani Avatar answered Nov 19 '22 16:11

Salem Ouerdani