This bundle helps you to create access permissions for your Symfony 4/5 application
$ composer require dmytrof/access-permissions-bundle
<?php
// config/bundles.php
return [
// ...
Dmytrof\AccessPermissionsBundle\DmytrofAccessPermissionsBundle::class => ['all' => true],
];
Read official documentation for symfony/security and install security component to your project.
// src/Security/ArticleVoter.php
use App\Model\{Article, Author};
use Dmytrof\AccessPermissionsBundle\Security\{AbstractVoter, CRUDVoterInterface, Traits\CRUDVoterTrait};
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ArticleVoter extends AbstractVoter implements CRUDVoterInterface
{
use CRUDVoterTrait;
// Put needed resources to subject (Article, Category etc.)
protected const SUBJECT = [
Article::class,
];
public const PREFIX = 'app.article.';
public const VIEW = self::PREFIX.'view';
public const CREATE = self::PREFIX.'create';
public const EDIT = self::PREFIX.'edit';
public const DELETE = self::PREFIX.'delete';
public const ATTRIBUTES = [
self::VIEW,
self::CREATE,
self::EDIT,
self::DELETE,
];
}
// src/Entity/User.php
use Symfony\Component\Security\Core\User\UserInterface;
use Dmytrof\AccessPermissionsBundle\Security\AdminInterface;
class User implements UserInterface, AdminInterface
{
//.....
/**
* Returns admin access atributes
*/
public function getAdminAccessAttributes(): array
{
// Return saved attributes from DB
return [
ArticleVoter::getViewAttribute(),
ArticleVoter::getCreateAttribute(),
ArticleVoter::getEditAttribute(),
];
}
/**
* Returns roles
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
if ($this->isAdmin()) {
$roles[] = 'ROLE_ADMIN';
}
return array_unique($roles);
}
//....
}
//src/Controller/ArticleController.php
/**
* @Route("/api/articles")
*/
class ArticleController extends AbstractController
{
/**
* @Route("/", methods={"GET"})
*/
public function getAll(Request $request)
{
$this->denyAccessUnlessGranted(ArticleVoter::getViewAttribute());
// Fetch article from DB and return response
}
}
At this moment AccessDecisionManager "asks" ArticleVoter if canRoleAdmin to view:
// Dmytrof\AccessPermissionsBundle\Security\AbstractVoter.php;
/**
* Checks if admin has access to attribute
*/
protected function canRoleAdmin(string $attribute, TokenInterface $token, $subject = null): bool
{
$admin = $token->getUser();
if (!$admin instanceof AdminInterface) {
return true;
}
return in_array($attribute, $admin->getAdminAccessAttributes());
}
You can write "can-method" for any role which is defined at security.yaml:
security:
role_hierarchy:
ROLE_AUTHOR
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
or added to RolesContainer:
/** @var \Dmytrof\AccessPermissionsBundle\Service\RolesContainer $rolesContainer */
$rolesContainer->addRole('ROLE_ANY');
Add canRoleAuthorEdit (where RoleAuthor - classified role ROLE_AUTHOR, Edit - short attribute ArticleVoter::EDIT)
// src/Security/ArticleVoter.php
/**
* Checks if ROLE_AUTHOR can EDIT the article
*/
protected function canRoleAuthorEdit(TokenInterface $token, $subject = null): bool
{
return $subject instanceof Article // Subject is Article
&& $token->getUser() instanceof Author // Authinticated user is Author
&& $subject->getAuthor() === $token->getUser(); // Authenticated Author is author of the Article
}
/**
* Checks if ROLE_AUTHOR can VIEW, CREATE, DELETE the article
*/
protected function canRoleAuthor(string $attribute, TokenInterface $token, $subject = null): bool
{
switch($attribute) {
case static::VIEW:
case static::CREATE:
return true;
default:
return false;
}
}
Important to remember:
- Voter tries to call canRoleAuthorEdit first.
- If method not exists canRoleAuthor will be called
- If method not exists can will be called
Create access_attributes.en.yaml at translations folder
// translations/access_attributes.en.yaml
app:
label: My application
attributes:
create:
label: Create
view:
label: View
edit:
label: Edit
delete:
label: Delete
subjects:
article:
label: Articles
attributes:
view:
label: View articles
description: Access to view article(s)
create:
label: Create new articles
# edit - default label app.attributes.edit.label will be used
# delete - default label app.attributes.delete.label will be used
author:
label: Authors
# attributes - default app.attributes will be used
Use form type AccessAttributesChoiceType or AccessAttributesCollectionType at your user form type to manage access attributes for user.
To get all access attributes with descriptions at your API add action to your UserController:
// src/Controller/UserController.php
public function getAccessAttributes(VotersContainer $votersContainer)
{
return [
'attributes' => $votersContainer->getAttributeDescriptionsCollection()->sort()->getAsArray()
];
}