<?php
declare(strict_types=1);
namespace Pec\Bundle\PlatformBundle\Security;
use Pec\Bundle\PlatformBundle\Entity\Interfaces\Identifiable;
use Psr\Cache\InvalidArgumentException;
use StingerSoft\PhpCommons\Builder\HashCodeBuilder;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
class CachedAccessDecisionManager implements AccessDecisionManagerInterface {
/**
* @var int
*/
protected const DEFAULT_TTL = 60;
protected const CACHE_PREFIX = 'CachedAccessDecisionManager';
/**
* @var int
*/
protected $ttl;
/**
* @var AccessDecisionManagerInterface
*/
protected $decorated;
/**
* @var AdapterInterface
*/
protected $cache;
/**
* CachedAccessDecisionManager constructor.
* @param AccessDecisionManagerInterface $decorated
* @param AdapterInterface $cache
*/
public function __construct(AccessDecisionManagerInterface $decorated, AdapterInterface $cache) {
$this->decorated = $decorated;
$this->cache = $cache;
}
/**
* Decides whether the access is possible or not.
*
* @param TokenInterface $token
* @param array $attributes An array of attributes associated with the method being invoked
* @param object $object The object to secure
*
* @return bool true if the access is granted, false otherwise
*/
public function decide(TokenInterface $token, array $attributes, $object = null): bool {
$username = $token->getUsername();
if(!$username) {
return $this->decorated->decide($token, $attributes, $object);
}
if($object !== null && !($object instanceof Identifiable)) {
return $this->decorated->decide($token, $attributes, $object);
}
try {
$hashBuilder = new HashCodeBuilder();
$hashBuilder->append($username);
$hashBuilder->append($attributes);
if($object !== null) {
$className = get_class($object);
if (class_exists('Symfony\Component\Security\Acl\Util\ClassUtils')) {
$className = \Symfony\Component\Security\Acl\Util\ClassUtils::getRealClass($className);
}else if (class_exists('Doctrine\Common\Util\ClassUtils')) {
$className = \Doctrine\Common\Util\ClassUtils::getRealClass($className);
}
$hashBuilder->append($className . '#' . $object->getId());
}
$cacheKey = self::CACHE_PREFIX . '_' . $hashBuilder->toHashCode();
$cacheItem = $this->cache->getItem($cacheKey);
if(!$cacheItem->isHit()) {
$cacheItem->set($this->decorated->decide($token, $attributes, $object));
$cacheItem->expiresAfter($this->getTtl());
$this->cache->save($cacheItem);
}
return (bool)$cacheItem->get();
} catch(\ReflectionException|InvalidArgumentException $re) {
return $this->decorated->decide($token, $attributes, $object);
}
}
/**
* @return int
*/
protected function getTtl(): int {
return $this->ttl ?? self::DEFAULT_TTL;
}
public function setTtl(int $ttl): void {
$this->ttl = $ttl;
}
}