Last active
September 30, 2022 09:26
-
-
Save Nemo64/7a8fbcc6727c502b8cd8cbd7ef36d454 to your computer and use it in GitHub Desktop.
centralized symfony access control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Security; | |
use Doctrine\ORM\QueryBuilder; | |
use Symfony\Component\Security\Core\Exception\AccessDeniedException; | |
use Symfony\Component\Security\Core\Security; | |
/** | |
* Allows access control to be defined within an entity. | |
*/ | |
interface EntityRestrictionInterface | |
{ | |
/** | |
* Adds _read_ checks to a query builder. | |
* | |
* @param Security $security | |
* @param QueryBuilder $queryBuilder | |
* @param string $alias The root alias that represents $this. | |
* @throws AccessDeniedException if you shouldn't be able to query this entity in the first place | |
*/ | |
public static function applyQueryRestrictions(Security $security, QueryBuilder $queryBuilder, string $alias): void; | |
/** | |
* Executes _write_ and _read_ checks. | |
* | |
* This is usually very similar to {@see applyQueryRestrictions}, just implemented in php. | |
* | |
* @param Security $security | |
* @param string $attribute Can be anything that is thrown into {@see Security::isGranted()} | |
* @return bool {@see \Symfony\Component\Security\Core\Authorization\Voter\Voter::voteOnAttribute} | |
*/ | |
public function isGranted(Security $security, string $attribute): bool; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Security; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Authorization\Voter\Voter; | |
use Symfony\Component\Security\Core\Security; | |
/** | |
* This is the voter for {@see EntityRestrictionInterface} that uses the | |
* {@see EntityRestrictionInterface::isGranted} method to vote. | |
*/ | |
class EntityRestrictionVoter extends Voter | |
{ | |
private Security $security; | |
public function __construct(Security $security) { | |
$this->security = $security; | |
} | |
protected function supports($attribute, $subject) | |
{ | |
return $subject instanceof EntityRestrictionInterface; | |
} | |
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) | |
{ | |
/** @var EntityRestrictionInterface $subject */ | |
return $subject->isGranted($this->security, $attribute); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Entity; | |
use App\Security\EntityRestrictionInterface; | |
use App\Security\Security; | |
use Doctrine\ORM\Mapping as ORM; | |
use Doctrine\ORM\QueryBuilder; | |
/** @ORM\Entity() */ | |
class Product implements EntityRestrictionInterface | |
{ | |
/** @ORM\Column(type="string") */ | |
public string $name; | |
/** @ORM\Column(type="boolean", options={"default": 0}) */ | |
public bool $public = false; | |
public static function applyQueryRestrictions(Security $security, QueryBuilder $queryBuilder, string $alias): void | |
{ | |
// check admin | |
if ($security->isGranted('ROLE_ADMIN')) { | |
return; | |
} | |
// check public | |
$queryBuilder->andWhere("$alias.public = 1"); | |
} | |
/** @noinspection InArrayMissUseInspection, NotOptimalIfConditionsInspection */ | |
public function isGranted(Security $security, string $attribute): bool | |
{ | |
// check admin | |
if (in_array($attribute, ['show', 'edit']) && $security->isGranted('ROLE_ADMIN')) { | |
return true; | |
} | |
// check public | |
if (in_array($attribute, ['show']) && $this->public) { | |
return true; | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Controller; | |
use App\Entity\Product; | |
use App\Security\Security; | |
use Doctrine\ORM\EntityManagerInterface; | |
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | |
class ProductController extends AbstractController | |
{ | |
public function list(EntityManagerInterface $em, Security $security) | |
{ | |
$qb = $em->createQueryBuilder() | |
->select("product") | |
->from(Product::class, "product"); | |
Product::applyQueryRestrictions($security, $qb, "product"); | |
return $this->json($qb->getQuery()->getResult()); | |
} | |
public function show(Product $product) | |
{ | |
$this->denyAccessUnlessGranted('show', $product); | |
return $this->json($product); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Entity; | |
use App\Security\EntityRestrictionInterface; | |
use App\Security\Security; | |
use Doctrine\ORM\Mapping as ORM; | |
use Doctrine\ORM\QueryBuilder; | |
/** @ORM\Entity() */ | |
class ProductVariant implements EntityRestrictionInterface | |
{ | |
/** @ORM\ManyToOne(targetEntity=Product::class) */ | |
public Product $product; | |
/** @ORM\Column(type="string") */ | |
public string $sku; | |
public static function applyQueryRestrictions(Security $security, QueryBuilder $queryBuilder, string $alias): void | |
{ | |
// delegate access check | |
$queryBuilder->join("$alias.product", "{$alias}_product"); | |
Product::applyQueryRestrictions($security, $queryBuilder, "{$alias}_product"); | |
} | |
public function isGranted(Security $security, string $attribute): bool | |
{ | |
// delegate access check | |
return $security->isGranted($attribute, $this->product); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment