É comum que em diversos portes de aplicações precisemos separar os usuários com níveis de acesso a fim de prover mais segurança e confiabilidade. O meio mais seguro de limitar acessos à áreas específicas é por meio de ACL (Access Control List). ACL consiste em criar um conjunto de regras de acesso que serão validadas a cada requisição, tornando pontos sensíveis da aplicação mais seguros. Este artigo mostrará os conceitos básicos da utilização do ACL no Zend Framework 3, no entanto os exemplos são funcionais para o Zend 2 também. A ideia neste primeiro artigo é focar nos pontos simples, em um artigo futuro, escreverei sobre o uso mais complexo.
Recurso: post Privilégios: add, edit, deleteDesta forma é possível criar o seguinte relacionamento: Role → Resouce → Privileges. Tudo isso será descrito no decorrer deste artigo. Pra simplificar o trabalho dos desenvolvedores, o ZF já possui implementação de ACL. No entanto este recurso não vem por padrão no Zend Skeleton Application, tampouco vem configurado. Então vamos o instalar e configurar.
composer require zendframework/zend-permissions-aclNão é necessário registrar no arquivo de módulos, estando instalado é o que basta.
<?php # module/Application/src/Acl/Resources.php namespace Application\Acl; use Application\Controller\PostController; use Zend\Permissions\Acl\Acl; use Zend\Permissions\Acl\Resource\GenericResource; /** * Class Resources * @package Application\Acl */ class Resources { public function __construct(Acl $acl) { $acl->addResource(new GenericResource(PostController::class)); } }É necessário utilizar a classe GenericResource para registrar um resource? Não. É apenas uma forma de padronizar. Você pode adcionar uma string que funcionará da mesma forma. Ex: $acl->addResource(PostController::class); ou ainda $acl->addResource('post');
<?php # module/Application/src/Acl/Roles.php namespace Application\Acl; use Zend\Permissions\Acl\Acl; use Zend\Permissions\Acl\Role\GenericRole; /** * Class Roles * @package Application\Acl */ class Roles { public function __construct(Acl $acl) { $acl->addRole(new GenericRole('admin')); $acl->addRole(new GenericRole('editor')); $acl->addRole(new GenericRole('guest')); } }Da mesma forma que em addResource, em addRole não é necessário utilizar a classe GenericRole, pode ser uma string mesmo. Ex: $acl->addRole('admin');
<?php # module/Application/src/Acl/Roles.php namespace Application\Acl; use Application\Controller\PostController; use Zend\Permissions\Acl\Acl; use Zend\Permissions\Acl\Role\GenericRole; /** * Class Roles * @package Application\Acl */ class Roles { public function __construct() { $acl = new Acl(); $acl->addRole(new GenericRole('admin')); $acl->addRole(new GenericRole('editor')); $acl->addRole(new GenericRole('guest')); // role resource privileges $acl->allow('admin', PostController::class, ['add', 'edit', 'delete', 'index', 'view']); $acl->allow('editor', PostController::class, ['add', 'edit', 'index', 'view']); $acl->allow('guest', PostController::class, ['index', 'view']); } }Agora sim, já temos nosso ACL minimamente definido e funcional. Só isso basta para que consigamos bloquear ou liberar acessos baseados no perfil do usuário. Como mencionado, existe o método deny(), que serve para bloquear privilégios. Fica a seu critério o workflow a seguir:
// Workflow 1: Com tudo bloqueado, abre as permissões aos poucos $acl->allow('guest', PostController::class, ['index', 'view']); // Workflow 2: // Libera tudo $acl->allow('guest'); // Bloqueia aos poucos $acl->deny('guest', PostController::class, ['add', 'edit', 'delete']);
<?php # module/Application/src/Controller/PostController.php namespace Application\Controller; use Application\Acl\Resources; use Application\Acl\Roles; use Zend\Mvc\Controller\AbstractActionController; use Zend\Permissions\Acl\Acl; use Zend\Permissions\Acl\Role\GenericRole; use Zend\View\Model\ViewModel; class PostController extends AbstractActionController { public function indexAction() { // o privilégio do recurso PostController (index) $privilege = str_replace('Action', '',__FUNCTION__); // Chamando nosso Acl $acl = new Acl(); /* * Carregando as configurações de resources, roles e privileges. * * A ordem tem que ser esta, recursos primeiro e roles depois senão, no * momento de registrar os privilégios um erro será lançado informando que * o recurso não existe. */ (new Resources($acl)); (new Roles($acl)); // para fins de exemplo, definindo todos os roles. $admin = new GenericRole('admin'); $editor = new GenericRole('editor'); $guest = new GenericRole('guest'); // e verificando um a um se possui acesso ao recurso com o devido privilégio $messageAdmin = $privilege . ': Acesso negado para admin'; if ($acl->isAllowed($admin, __CLASS__, $privilege)) { $messageAdmin = $privilege . ': Acesso garantido para admin'; } var_dump($messageAdmin); $messageEditor = $privilege . ': Acesso negado para editor'; if ($acl->isAllowed($editor, __CLASS__, $privilege)) { $messageEditor = $privilege . ': Acesso garantido para editor'; } var_dump($messageEditor); $messageGuest = $privilege . ': Acesso negado para guest'; if ($acl->isAllowed($guest, __CLASS__, $privilege)) { $messageGuest = $privilege . ': Acesso garantido para guest'; } var_dump($messageGuest); return new ViewModel(); } }Com o controller criado, vamos registrar rota do mesmo para poder testar no navegador.
# module/Application/config/module.config.php return [ 'router' => [ 'routes' => [ // ... 'post' => [ 'type' => Segment::class, 'options' => [ 'route' => '/post[/:action][/:id]', 'defaults' => [ 'controller' => Controller\PostController::class, 'action' => 'index', ], ], ] ], ], 'controllers' => [ 'factories' => [ Controller\IndexController::class => InvokableFactory::class, Controller\PostController::class => InvokableFactory::class, ], ], // ... ];E agora vamos rodar nosso Skeleton Application através do comando composer serve.
$ composer serve
# module/Application/src/Controller/PostController.php if (!$acl->isAllowed($guest, __CLASS__, $privilege)) { // Caso não tenha permissão, adiciono uma mensagem sobre o ocorrido $this->flashMessenger()->setNamespace('error') ->addMessage('Você não possui autorização para o recurso solicitado.'); // e redireciono o usuário para uma rota padrão pra evitar Too Many Redirects return $this->redirect()->toRoute('home'); }O resultado é este: