Symfony Voters

Kaan Onur
3 min readApr 7, 2023

--

Voters, kullanıcıların belirli bir işlemi gerçekleştirme izni olup olmadığını belirlemek için kullanılır. Örneğin, bir kullanıcının bir makaleyi düzenleme izni olup olmadığını belirlemek için kullanılabilir.

Symfony’de genellikle kullanıcıların belirli bir işlemi gerçekleştirme izni olup olmadığını belirlemek için kullanılır. Bununla birlikte Voters, farklı senaryolarda da kullanılabilir. Örnek olarak:

  1. Kullanıcı rol yetkilendirmesi: Kullanıcının sahip olduğu rol, uygulamanın belirli bir işlemini gerçekleştirme yetkisini belirleyebilir. Örneğin, bir yönetici kullanıcısının belirli bir sayfaya erişim izni olabilir, ancak bir standart kullanıcı erişemez. Bu senaryoda Voters, kullanıcının rolüne göre işlem yapar.
  2. Özelleştirilmiş yetkilendirme işlemleri: Uygulamanın belirli bir işlemi gerçekleştirme yetkisi, farklı kriterler göz önünde bulundurularak belirlenebilir. Örneğin, bir kullanıcının bir şirketin yöneticisi olup olmadığına veya bir ürünü satın alabilmesi için yeterli bakiyesi olup olmadığına bağlı olarak. Bu senaryoda Voters, belirli kriterlere göre işlem yapar.
  3. Ülke veya bölgeye göre yetkilendirme işlemleri: Uygulamanın belirli bir işlemi gerçekleştirme yetkisi, kullanıcının bulunduğu ülke veya bölgeye göre belirlenebilir. Örneğin, bir ülke veya bölgede yasaklı bir kelimeyi kullanmak veya bir ürünü satmak yasak olabilir. Bu durumda, Voters, kullanıcının bulunduğu ülke veya bölgeye göre işlem yapar.

Voters kullanmadan bir kullanıcının kendi içeriğine erişmesinin kontrolünü aşağıdaki şekilde yapabilirdik

// src/Controller/PostController.php
// ...

// inside your controller action
if ($post->getOwner() !== $this->getUser()) {
throw $this->createAccessDeniedException();
}

Voters kullanarak ise controller içinde denyAccessUnlessGranted() fonksiyonunu çağırarak veya isGranted() methodu ile bu kontrolü yapabiliriz. Controller kullanımına örnek olarak:

     /**
* @Route("/posts/{id}", name="post_show")
*/
public function show($id)
{
// Post objesinin çekildiğini varsayalım
$post = ...;

$this->denyAccessUnlessGranted('view', $post);
}

Örnekte görüldüğü gibi denyAccessUnlessGranted fonksiyonunu çağırarak tek satırda aynı kontrolü yapabildik. Controllerda herhangi bir if kontrolü yapmamıza gerek kalmadı. Kontrollerimizi tanımlayacağımız voter class’ı içinde yapabiliriz.

Voter Class’ı Oluşturmak

// src/Security/PostVoter.php
namespace App\Security;

use App\Entity\Post;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

class PostVoter extends Voter
{
// these strings are just invented: you can use anything
const VIEW = 'view';
const EDIT = 'edit';

protected function supports(string $attribute, mixed $subject): bool
{
// if the attribute isn't one we support, return false
if (!in_array($attribute, [self::VIEW, self::EDIT])) {
return false;
}

// only vote on `Post` objects
if (!$subject instanceof Post) {
return false;
}

return true;
}

protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();

if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}

// you know $subject is a Post object, thanks to `supports()`
/** @var Post $post */
$post = $subject;

return match($attribute) {
self::VIEW => $this->canView($post, $user),
self::EDIT => $this->canEdit($post, $user),
default => throw new \LogicException('This code should not be reached!')
};
}

private function canView(Post $post, User $user): bool
{
// if they can edit, they can view
if ($this->canEdit($post, $user)) {
return true;
}

// the Post object could have, for example, a method `isPrivate()`
return !$post->isPrivate();
}

private function canEdit(Post $post, User $user): bool
{
// this assumes that the Post object has a `getOwner()` method
return $user === $post->getOwner();
}
}

Burada iki protected fonksiyonumuz mevcut. İlk olarak supports fonksiyonu çalışır. Supports methodundan true dönerse voteInAttribute methodu çalışarak kontrol gerçekleştirilir. Supports methodu voter’ın hangi işlemleri desteklediğini belirler. VoteOnAttribute ise belirli bir işlemi gerçekleştirme yetkisi olup olmadığını belirler

Voter ile Rol Kontrolü

// src/Security/PostVoter.php

// ...
use Symfony\Bundle\SecurityBundle\Security;

class PostVoter extends Voter
{
// ...

public function __construct(private Security $security) {
}

protected function voteOnAttribute($attribute, mixed $subject, TokenInterface $token): bool
{
// ...

// ROLE_SUPER_ADMIN can do anything! The power!
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
return true;
}

// ... all the normal voter logic
}
}

Access Desicion Strategy Çeşitleri

Symfony Voters 4 farklı stratejiye bağlı olarak çalışır. Varsayılan olarak en az bir voter’ın voteOnAttribute methodundan true dönmesi yeterlidir fakat bunu istersek tanımlı olan 4 stratejiye göre değiştirebiliriz.

Affirmative (Varsayılan)

En az bir voter’dan true değeri yeterlidir.

Consensus

Voter’lardan dönen cevaplarda true değer sayısının false değer sayısından fazla olması gerekir.

Unanimous

Voter’larda voteOnAttribute methodunda hiç bir false değeri dönmemişse bütün voter’ların çekimser kaldığı durumda allow_if_all_abstain değerine göre işlem yapılır. Bu değer varsayılan olarak false'dur.

Priority

Bu strateji Symfony v5.1 ile gelmiştir. Version 5.1 ve üzerinde kullanılabilir. Priority stratejisi kullanıldığında voteOnAttribute methodundan true ya da false return eden ilk voter’ın değeri kullanılır.

Varsayılan stratejiyi değiştirmek için securtiy.yaml dosyasında kullanmak istediğiniz stratejiyi aşağıdaki örnekteki gibi tanımlamanız gerekmektedir.

# config/packages/security.yaml
security:
access_decision_manager:
strategy: unanimous
allow_if_all_abstain: false

--

--