<?php
namespace App\Security;
use App\Entity\User;
use App\Service\Security\PermissionChecker;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/**
* ModuleAccessVoter — @IsGranted ile route'lara deklaratif modul/section kapisi.
*
* Attribute grameri (FE getPermission / getFeatureAccess ile birebir):
* - "MODULE:<moduleKey>:<r|c|u|d>" ornek: MODULE:billing:u
* - "SECTION:<moduleKey>:<sectionKey>:<r|u>" ornek: SECTION:project:payment_report:r
*
* Report-only modu ($enforce=false): gercek karari LOGLAR ama bloklamadan gecirir.
* Boylece eksik grant / yanlis key'ler canlida kesfedilir, sonra enforce'a gecilir.
*/
class ModuleAccessVoter extends Voter
{
private $permissionChecker;
private $logger;
private $enforce;
public function __construct(PermissionChecker $permissionChecker, LoggerInterface $logger, bool $permissionEnforce)
{
$this->permissionChecker = $permissionChecker;
$this->logger = $logger;
$this->enforce = $permissionEnforce;
}
protected function supports($attribute, $subject)
{
return is_string($attribute)
&& (strpos($attribute, 'MODULE:') === 0 || strpos($attribute, 'SECTION:') === 0);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
return false; // kimlik yok -> deny (auth katmani zaten kapi)
}
$allowed = $this->resolve($user, $attribute);
// Report-only: bloklama, sadece logla -> eksikleri kesfet.
if (!$this->enforce) {
if (!$allowed) {
$this->logger->warning('[permission report-only] would-deny', [
'user' => method_exists($user, 'getEmail') ? $user->getEmail() : null,
'attribute' => $attribute
]);
}
return true;
}
return $allowed;
}
private function resolve(User $user, string $attribute): bool
{
$parts = explode(':', $attribute);
$type = $parts[0];
if ($type === 'MODULE' && count($parts) === 3) {
return $this->permissionChecker->moduleCan($user, $parts[1], $parts[2]);
}
if ($type === 'SECTION' && count($parts) === 4) {
return $this->permissionChecker->sectionCan($user, $parts[1], $parts[2], $parts[3]);
}
// Tanimsiz format -> guvenli taraf: enforce'ta deny.
$this->logger->error('[permission] malformed attribute', ['attribute' => $attribute]);
return false;
}
}