Reference

Dynamic Assertion

In some cases you will need to authorize a role based on a specific HTTP request. For instance, imagine that you have an "editor" role that can add/update/delete a page in a Content Management System (CMS). We want to prevent an "editor" from modifying pages they have not created.

These types of authorization are called dynamic assertions and are implemented via the Laminas\Permissions\Rbac\AssertionInterface of laminas-permissions-rbac.

In order to use it, this package provides LaminasRbacAssertionInterface, which extends Laminas\Permissions\Rbac\AssertionInterface:

namespace Mezzio\Authorization\Rbac;

use Psr\Http\Message\ServerRequestInterface;
use Laminas\Permissions\Rbac\AssertionInterface;

interface LaminasRbacAssertionInterface extends AssertionInterface
{
    public function setRequest(ServerRequestInterface $request) : void;
}

The Laminas\Permissions\Rbac\AssertionInterface defines the following:

namespace Laminas\Permissions\Rbac;

interface AssertionInterface
{
    public function assert(Rbac $rbac, RoleInterface $role, string $permission) : bool;
}

Going back to our use case, we can build a class to manage the "editor" authorization requirements, as follows:

use Mezzio\Authorization\Rbac\LaminasRbacAssertionInterface;
use App\Service\Article;

class EditorAuth implements LaminasRbacAssertionInterface
{
    public function __construct(Article $article)
    {
        $this->article = $article;
    }

    public function setRequest(ServerRequestInterface $request)
    {
        $this->request = $request;
    }

    public function assert(Rbac $rbac, RoleInterface $role, string $permission)
    {
        $user = $this->request->getAttribute(UserInterface::class, false);
        return $this->article->isUserOwner($user->getIdentity(), $this->request);
    }
}

Where Article is a class that checks if the identified user is the owner of the article referenced in the HTTP request.

If you manage articles using a SQL database, the implementation of isUserOwner() might look like the following:

public function isUserOwner(string $identity, ServerRequestInterface $request): bool
{
    // get the article {article_id} attribute specified in the route
    $url = $request->getAttribute('article_id', false);
    if (! $url) {
        return false;
    }
    $sth = $this->pdo->prepare(
        'SELECT * FROM article WHERE url = :url AND owner = :identity'
    );
    $sth->bindParam(':url', $url);
    $sth->bindParam(':identity', $identity);
    if (! $sth->execute()) {
        return false;
    }
    $row = $sth->fetch();
    return ! empty($row);
}

To pass the Article dependency to your assertion, you can use a Factory class that generates the EditorAuth class instance, as follows:

use App\Service\Article;

class EditorAuthFactory
{
    public function __invoke(ContainerInterface $container) : EditorAuth
    {
        return new EditorAuth(
            $container->get(Article::class)
        );
    }
}

And configure the service container to use EditorAuthFactory to point to EditorAuth, using the following configuration:

return [    
    'dependencies' => [
        'factories' => [
            // ...
            EditorAuth::class => EditorAuthFactory::class
        ]
    ]
];

Found a mistake or want to contribute to the documentation? Edit this page on GitHub!