Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting custom denyCallback even when returning false from matchCallback with Yii2 behaviours

I'm using Yii2 and utilising their behaviors within my controllers.

I am building my own permissions system and because the permissions are rather complex I need to make use of a matchCallback.

Here is an example:

public function behaviors() {
    return [
        'access' => [
            'class' => AccessControl::className(),
            'only' => ['view'],
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['view'],
                    'matchCallback' => function ($rule, $action) {
                        return Yii::$app->authManager->can($rule, $action);
                    }
                ],      
                // everything else is denied
            ],
        ],
    ];
}   

Now, unfortunately the way the matchCallback works is by returning true or false on if it should continue to execute the rule, rather than being able to return true or false of they are allowed or not.

So if I return false that it shouldn't continue (and hence disallow them) then I am unable to customise the denyCallback as it quits executing the rule.

Is there anyway I can customise the denyCallback even if I return false from the matchCallback - or should I be handling my situation in a different way?

like image 565
Brett Avatar asked Jul 06 '15 16:07

Brett


1 Answers

You can define denyCallback as property of AccessControl instead of defining it in AccessRule. It will get called if allow after rule check returns null. It has the same signature as denyCallback in AccessRule:

public function behaviors() {
    return [
        'access' => [
            'class' => AccessControl::className(),
            'only' => ['view'],
            'rules' => [
                [
                    'allow' => true,
                    'actions' => ['view'],
                    'matchCallback' => function ($rule, $action) {
                        return Yii::$app->authManager->can($rule, $action);
                    }
                ],
            'denyCallback' => function ($rule, $action){...}
            // everything else is denied
            ],
        ],
    ];
}  

As another option you can extend AccessRule class and override allows() method to return false instead of null when match check fails, and your rule's denyCallback will be called then:

class MyAccessRule extends AccessRule
{
    public function allows($action, $user, $request)
    {
        $allows = parent::allows($action, $user, $request);
        if ($allows === null) {
            return false;
        } else {
            return $allows;
        }

    }
}

matchCallback only determines should rule be applied or not, and if matchCallback returns true and other parameters are match(e.g. roles, verbs etc.), call to allows() will return rule's allow parameter true or false as you set it in configuration. And if matchCallback returns false - allow will be null and rule's denyCallback will not be called, but AccessControl denyCallback will be called instead if it is set in configuration.

As you mentioned in the comments third option is to make allows() return result of callback.

class MyAccessRule extends AccessRule
{
    public $allowCallback;
    public function allows($action, $user, $request)
    {
        if(!empty($this->allowCallback) {
            return call_user_func($this->allowCallback);
        }
        $allows = parent::allows($action, $user, $request);
        if ($allows === null) {
            return false;
        } else {
            return $allows;
        }

    }
}
like image 163
Tony Avatar answered Oct 18 '22 10:10

Tony