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
?
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;
}
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.');
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With