How do Yii 3 controllers deal with permission access?

We have a custom permission system that needs to be hooked into controller methods. Does Yii 3 supply anything for that? PHP attributes perhaps?

Have you looked into Yii’s RBAC?

Thanks. Looks like they just inject the permission manager

public function actionCreate(\Yiisoft\Access\AccessCheckerInterface $accessChecker): ResponseInterface
{
    $userId = getUserId();

    if ($accessChecker->userHasPermission($userId, 'createPost')) {
        // author has permission to create post
    }
}

Which might get awkward when done en masse.

You have probably notices in Yii3 you inject almost everything to controller actions
But this is cleaner than putting full class


public function actionCreate(AccessCheckerInterface $accessChecker): ResponseInterface
{
    $userId = getUserId();

    if ($accessChecker->userHasPermission($userId, 'createPost')) {
        // author has permission to create post
    }
}

Yeah, but to inject the same thing in every controller in the entire code-base sounds like repetition…

You can use DI container to get the thing. I have not jumped the Yii3 ship yet but this or something similar should work:

public function actionCreate(Yiisoft\Di\Container $container): ResponseInterface
{
    $accessChecker = $container->get(AccessCheckerInterface::class);
    $userId = getUserId();

    if ($accessChecker->userHasPermission($userId, 'createPost')) {
        // author has permission to create post
    }
}
1 Like

True! Still, would be nice to see a concept using the new PHP attributes ^^ Maybe not too hard to make.

I saw them being heavily used. I’m just not yet conversant with Yii3 and am exploring it.
May someone knowledgeable like @samdark or @Bizley or @sammousa can explain it better than I can!

What about using the filters? I’m not sure about Yii 3 because I’m not working on it but with Yii 2 it’s doable with auth filter.

Just came to my mind. What about injecting it in the ctor of the class and then access it like a property?

<?php

class YourController
{
    private AccessCheckerInterface $accessChecker;

    public function __construct(AccessCheckerInterface $accessChecker)
    {
        $this->accessChecker = $accessChecker;
    }

    public function actionCreate(): ResponseInterface
    {
        $userId = getUserId();

        if ($this->accessChecker->userHasPermission($userId, 'createPost')) {
            // author has permission to create post
        }
    }
}

Or even better make a trait with that ctor and just use it in multiple classes?

Yeah, trait would look a bit like our current code, I guess, where we access static function on a permission class.

No filters in Yii 3, afaik.

In Yii3 these are represented by middleware.

1 Like

Since many of us might get back to this when we jump into Yii3, can you explain a little bit may be with pseudo example on how this should be done?

Something like this:

final class CheckAcess
{
    public function __construct(
        private CurrentUser $currentUser,
        private ResponseFactoryInterface $responseFactory
    )
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
    {
        if (!$this->currentUser->can('my_permission')) {
             return $this->responseFactory->createResponse(403);
        }

       return $next->handle($request);
    }   
}
Route::get('/account')->([AccountController::class, 'account'])
        ->name('account/view')
        ->addMiddleware(CheckAccess::class)
3 Likes

Or it could be done in the action itself:

final class AccountController
{
    public function __construct(
        private CurrentUser $currentUser,
        private ResponseFactoryInterface $responseFactory
    )
    {
    }

    public function account()
    {
        if (!$this->currentUser->can('my_permission')) {
             return $this->responseFactory->createResponse(403);
        }

        // ...
    }
}
1 Like

I assume $this->currentUser->can('my_permission') is the same as Yii2 Yii::$app->user->can('my_permission')
I also assume you need RBAC for it to work correctly

Are my assumptions correct?

Yes, you are correct.

1 Like

Yes, here’s where attributes could be useful, perhaps?

    #[UserHasPermission("edit")]
    public function account(int $domainId)
    {
    }

Or maybe not, since we often want to check permission vis-à-vis a domain entity ID. Hm.