Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github
Em aplicativos complexos, comumente existem o problema que as decisões de permitir
ou negar acesso não podem ser tomadas somente baseada no usuário (Token
)
solicitando acesso, mas também deve levar em consideração o objeto de domínio
que está tendo o acesso solicitado. É aí que o sistema ACL entra em ação.
Imagine que está projetando um sistema de blog onde seus usuário podem comentar
os textos (posts) publicados. Agora, você deseja que um usuário possa editar
seus próprios comentários, mas não os comentários dos outros usuários. Além
disso, você como administrador deseja pode editar todos os comentários. Neste
cenário, Comment
seria seu objeto de domínio ao qual você quer restringir
acesso. Você poderia usar várias abordagens para conseguir o mesmo resultado.
Duas dessas seriam:
Comment
de todos os usuários que têm acesso
e depois comparar com o usuário Token
solicitando acesso.Comment
, isto é, ROLE_COMMENT_1
, ROLE_COMMENT_2
, etc.Ambas abordagens são perfeitamnete válidas. Elas, porém, amarram sua lógica de autorização de acesso com seu código, deixando-o mais difícil de reusar em outros contextos. Também aumenta a dificuldade de criar testes unitários. Além disso, pode-se ter problemas de performance caso muitos usuários tenham acesso a um único objeto de domínio.
Felizmente, há uma maneira melhor que veremos a seguir.
Agora, antes de realmente começarmos, precisamos fazer algumas configurações. Primeiramente, precisamos configurar a conexão de banco de dados queo sistema ACL utilizará.
1 2 3 4 | # app/config/security.yml
security:
acl:
connection: default
|
1 2 3 4 | <!-- app/config/security.xml -->
<acl>
<connection>default</connection>
</acl>
|
1 2 3 4 | // app/config/security.php
$container->loadFromExtension('security', 'acl', array(
'connection' => 'default',
));
|
Note
O sistema ACL requer que ao menos uma conexão Doctrine DBAL esteja configurada. Isto, porém, não significa que você tem que utilizar o Doctrine para mapear seus objetos de domínio. Você pode utilizar qualquer mapeamento que quiser para seus objetos, seja ele Doctrine ORM, Mongo ODM, Propel, ou SQL puro. A escolha é sua.
Depois de configurar a conexão, temos que importar a estrutura do banco de dados. Felizmente, temos um comando para isto. Rode o seguinte comando.
1 | php app/console init:acl
|
Voltando ao nosso pequeno exemplo do início, vamos implementar o sistema ACL dele.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
// ...
// BlogController.php
public function addCommentAction(Post $post)
{
$comment = new Comment();
// setup $form, and bind data
// ...
if ($form->isValid()) {
$entityManager = $this->get('doctrine.orm.default_entity_manager');
$entityManager->persist($comment);
$entityManager->flush();
// creating the ACL
$aclProvider = $this->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
$acl = $aclProvider->createAcl($objectIdentity);
// retrieving the security identity of the currently logged-in user
$securityContext = $this->get('security.context');
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// grant owner access
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
}
}
|
Há algumas importantes decisões de implementação neste trecho de código. Por enquanto, gostaria de destacar duas.
Primeiro, note que o método ->createAcl()
não aceita objetos de domínio
diretamente, mas somente implementações de ObjectIdentityInterface
. Este
passo adicional permite que trabalhe com ACLs mesmo quando não tiver uma instância
do objeto de domínio disponível. Isto será extremamente útil se você quiser
verificar permissões para um grande número de objetos sem realmente criar os
objetos.
Outra parte interessante é a chamada ->insertObjectAce()
. Em nosso exemplo,
estamos concedendo ao usuário que está autenticado permissão de proprietário
do objeto Comment. MaskBuilder::MASK_OWNER
é uma máscara (integer bitmask)
pré-definida. Não se preocupe que MaskBuilder abstrai a maior parte dos detalhes
técnicos, mas saiba que utilizando esta técnica é possível armazenar muitas
permissões diferentes em apenas uma linha do banco de dados, o que significa
uma considerável melhora na performance.
Tip
A ordem em que as entradas de controle (ACE) são checadas é importante. Como regra geral, você deve colocar as entradas mais específicas no início.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // BlogController.php
public function editCommentAction(Comment $comment)
{
$securityContext = $this->get('security.context');
// check for edit access
if (false === $securityContext->isGranted('EDIT', $comment))
{
throw new AccessDeniedException();
}
// retrieve actual comment object, and do your editing here
// ...
}
|
Neste exemplo, verificamos se o usuário tem permissão de edição (EDIT
).
Internamente, Symfony2 mapea a permissão para várias máscaras (integer bitmasks)
e verifica se o usuário tem alguma delas.
Note
Você pode definir até 32 permissões base (dependendo do seu SO, pode variar entre 30 e 32). Você ainda pode definir permisões cumulativas.
No nosso primeiro exemplo acima, nós concedemos somente a permissão base OWNER
.
Apesar disso significar que o usuário pode executar qualquer operação no
objeto de domínio tais como exibir, editar, etc, em alguns casos você pode
querer conceder essas permissões explicitamente.
O MaskBuilder
pode ser usado para criar máscaras (bit masks) facilmente através
da combinação de várias permissões base.
1 2 3 4 5 6 7 8 | $builder = new MaskBuilder();
$builder
->add('view')
->add('edit')
->add('delete')
->add('undelete')
;
$mask = $builder->get(); // int(15)
|
Este inteiro (integer bitmask) pode então ser usado para conceder a um usuário todas as permissões base que você adicionou acima.
1 | $acl->insertObjectAce(new UserSecurityIdentity('johannes'), $mask);
|
O usuário agora poderá exibir, editar, deletar e desfazer a deleção dos objetos.