Videa Blog

Arachne/Security - Simplified Authorizator and Fixed ACL Callbacks

Jáchym Toušek  

Authorization in Nette/Security has some long-known drawbacks as demonstrated in my 4 years old RFC (Czech only). This article will show you how you can solve these problems using the enhanced API provided by Arachne/Security.

Simplified Authorizator

The problems in Nette/Security are that authorization is tied too tightly to Nette\Security\User and lacks proper API for ownership-based ACL permissions. The RFC was not accepted in the end but in the comments David Grudl suggested a new API for Authorizator. It was never implemented in Nette itself but Arachne/Security is an implementation of that proposal.

Arachne/Security introduces a new interface Arachne\Security\Authorization\AuthorizatorInterface. The isAllowed() method is simplified to just resource and privilege. Implementations of this interface should use Arachne\Security\Authentication\FirewallInterface (Arachne equivalent of Nette\Security\User - read my previous article for details) to get the user's identity. This way you can have multiple different authorizators for each firewall.

Here is an example of how to register and implement the authorizator.

services:
    admin.authorizator.factory: App\Module\AdminModule\Security\AuthorizatorFactory
    admin.authorizator:
        factory: @admin.authorizator.factory::create
        tags:
            arachne.security.authorizator: admin
namespace App\Module\AdminModule\Security;

use App\Module\AdminModule\Security\AdminFirewall;
use Arachne\Security\Authentication\FirewallInterface;
use Arachne\Security\Authorization\AuthorizatorInterface;
use Arachne\Security\Authorization\Permission;
use Arachne\Security\Authorization\PermissionAuthorizator;

class AuthorizatorFactory
{
    /**
     * @var AdminFirewall
     */
    private $firewall;

    public function __construct(AdminFirewall $firewall)
    {
        $this->firewall = $firewall;
    }

    public function create(): AuthorizatorInterface
    {
        $permission = new Permission();

        // Setup $permission using addRole, addResource and allow methods.

        return new PermissionAuthorizator($this->firewall, $permission);
    }
}

Fixed permission callbacks in ACL

In the example above you might have noticed that I used Arachne\Security\Authorization\Permission instead of Nette\Security\Permission. This adresses the core issue from the RFC (Czech only) mentioned above. It changes the parameters which will be received by callbacks passed to allow() and deny() methods. You can finally implement all permissions that require an identity check the right way without ugly hacks.

namespace App\Module\AdminModule\Security;

use App\Module\AdminModule\Module\ArticleModule\Entity\Article;
use App\Module\AdminModule\Security\AdminFirewall;
use Arachne\Security\Authentication\FirewallInterface;
use Arachne\Security\Authorization\AuthorizatorInterface;
use Arachne\Security\Authorization\Permission;
use Arachne\Security\Authorization\PermissionAuthorizator;
use Nette\Security\IResource;

class AuthorizatorFactory
{
    // ...

    public function create(): AuthorizatorInterface
    {
        $permission = new Permission();

        // Setup $permission using addRole, addResource and allow methods.

        // Redactor can only edit his own articles.
        $permission->allow(
            'Redactor',
            Article::class,
            'edit',
            function (IIdentity $identity, IResource $article) {
                return $identity->getId() === $article->getAuthor()->getId();
            }
        );

        return new PermissionAuthorizator($this->firewall, $permission);
    }
}

Wait, there is more!

This is actually just a small portion of how Arachne can help you implement the security layer of your application. There is another package called Arachne/SecurityVerification which integrates Arachne/Security with Arachne/Verifier to greatly simplify your security layer with annotations and latte macros. How to do this will be described in the next article.