Smyfony projenizde yetki kontrolü yapmak için symfony’nin voters özelliğini kullanabilirsiniz. Bu yazımda symfony voters nedir ve yetki kontrolleri voters ile nasıl yapılırı anlatacağım.
İçindekiler Tablosu
Symfony Voters Nedir?
Symfony Voters, projenizde yetki kontrollerini yönetebilmenizi ve tüm yetki kontrollerini tek bir yere toplamızı sağlayan yetki kontrol mekanizmasıdır.
Örneklemek gerekirse bir makalenin sadece makale yazarı tarafından görüntülenmesine ve düzenlenebilmesine izin vermek istersek bunu controller içerisinde ekleceğimiz bir kontrol ile gerçekleştirebiliriz ya da symfony voters özelliğini kullanabiliriz.
Bu senaryodaki kontrolü controller içinde yapmak isterseniz. Sorunu çabucak çözebilirsiniz. Ya da çözemez misiniz? Cevap: Çözemezsiniz. Çünkü buraya ekleceğiniz kontrolü büyük ihtimalle ara yüzde görüntüleme butonunu gizleme vs. gibi farklı yerlerde uygulamak isteyeceksiniz. Peki bu durumda ne olacak? Her yere aynı kontrolü yazacaksınız. Kod tekrarı yapmak zorunda kalacaksınız ve en küçük bir değişiklikte her yeri güncellemeniz gerekecektir.
Symfony bu sorunu çözmek için voters özelliğini sunuyor. Bu senaryodaki ya da farklı durumlar da ki yetki kontrolleriniz için bir voter tanımlamak ve bunu istediğiniz yerde kullanmak daha kolay ve kullanışlıdır.
Yukarıdaki senaryomuzu kod üzerinden inceleyelim. Controller’da yapılacak olan sahiplik kontrolü aşağıdaki gibi olacaktır. Örneğimizde $post
‘un owner
bilgisi ile oturumu açık olan kullanıcının bilgisi aynı değilse hata olarak AccessDeniedException
fırlatılıyor.
// src/Controller/PostController.php class PostController extends AbstractController { /** * @Route("/posts/{id}", name="post_show") */ public function show($id) { // Post objesinin çekildiğini varsayalım $post = ...; // Post objesinin owner bilgisi ile oturumu açık olan kullanıcın bilgileri aynı mı diye kontrol ediliyor. if ($post->getOwner() !== $this->getUser()) { throw $this->createAccessDeniedException(); } // ... } /** * @Route("/posts/{id}/edit", name="post_edit") */ public function edit($id) { // Post objesinin çekildiğini varsayalım $post = ...; // Post objesinin owner bilgisi ile oturumu açık olan kullanıcın bilgileri aynı mı diye kontrol ediliyor. if ($post->getOwner() !== $this->getUser()) { throw $this->createAccessDeniedException(); } // ... } }
Symfony Voter İle Yetki Kontrolü
Controller’da Yetki Kontrolü
Voter tanımlamış olsaydık ve edit
ve view
yetkilerimiz olsaydı bu kontroller nasıl olacaktı birde onu inceleyelim. Dikkat ederseniz burada herhangi bir hata fırlatma işlemi de yapmadık. Çünkü bu işlemi voter kendisi yönetiyor.
// src/Controller/PostController.php class PostController extends AbstractController { /** * @Route("/posts/{id}", name="post_show") */ public function show($id) { // Post objesinin çekildiğini varsayalım $post = ...; // Voter ile "view" yetkisi kontrolü yapılıyor $this->denyAccessUnlessGranted('view', $post); // ... } /** * @Route("/posts/{id}/edit", name="post_edit") */ public function edit($id) { // Post objesinin çekildiğini varsayalım $post = ...; // Voter ile "edit" yetkisi kontrolü yapılıyor $this->denyAccessUnlessGranted('edit', $post); // ... } }
Twig’de Yetki Kontrolü
Edit
ve view
yetkilerinin twig dosyalarımızın için de kontrolünü twig’in is_granted
fonksiyonu ile gerçekleştiriyoruz.
// templates/post_list.html.twig //... Diğer işlemler {% if (is_granted('edit', post) %} // Düzenleyebilir {% endif %} {% if (is_granted('view', post) %} // Görüntüleyebilir {% endif %} //... Diğer işlemler
Symfony Voter Nasıl Tanımlanır?
Voter’larımızı src/Security/
dizini içerisine tanımlıyoruz. Bizim voter’ımızın adı PostVoter
olacak. Voter’ımızın boş hali aşağıdaki gibi olacaktır.
// src/Security/PostVoter.php namespace App\Security; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class PostVoter extends Voter { protected function supports($attribute, $subject): bool { // } protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { // } }
Burada iki methodumuz mevcut. Bir yetki için kontrol sağladığınızda tüm voter’lardan sonuç alınır. Sadece ilgili voter’ın çalışmasını sağlamak için supports
methodu kullanılır. Bu nedenle yetki kontrolü sırasında bu voter’ın çalışıp çalışmayacağına karar veren kontroller buraya tanımlanır. supports
methodu true
değer return ederse voteOnAttribute
methodu çağırılarak yetki için kontroller gerçekleştirilir. Eğer supports
methodu false
return edersede bu voter çekimser kalmış kabul edilir.
Senaryomuz için supports methodu aşağıdaki gibi olmalı. Bu method içinde gelen yetkinin ‘edit’ ya da ‘view’ olup olmadığı kontrol ediliyor. Aynı zamanda $subject
Post objesimi diye kontrol ediliyor. Eğer bu kontrollerden true
dönerse voter voteOnAttribute
methodunu çağırarak yetki kontrolü yapacaktır. Ancak false
döner ise bu voter atlanacak ve diğer voter’lar kontrol edilecektir. Bu durumda voter yetki için çekimser kalmış oluyor.
// src/Security/PostVoter.php namespace App\Security; use App\Entity\Post; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; class PostVoter extends Voter { protected function supports($attribute, $subject): bool { // Eğer kontrol edilecek yetki 'view' ya da 'edit' değilse false return et if (!in_array($attribute, ['view', 'edit'])) { return false; } // Eğer kontrol edilen $subject Post objesi değilse false return et if (!$subject instanceof Post) { return false; } return true; } protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token) { // } }
VoteOnAttribute methodunda ihtiyacınız olduğunda TokenInterface
üzerinden oturum açmış olan kullanıcının bilgisine ulaşabilirsiniz. Ancak bu durum da tokenInterface’den gerçekten user objesi gelip gelmediğini kontrol etmekte fayda var çünkü oturum açılmamış bir ekranda yetki kontrollü yaptırırsanız burada hata meydana gelecektir.
// 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 { protected function supports($attribute, $subject): bool { // Eğer kontrol edilecek yetki 'view' ya da 'edit' değilse false return et if (!in_array($attribute, ['view', 'edit'])) { return false; } // Eğer kontrol edilen $subject Post objesi değilse false return et if (!$subject instanceof Post) { return false; } return true; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); if (!$user instanceof User) { return false; } } }
Ve geldik sahiplik kontrolüne. Post
‘un User
‘a ait olup olmadığını checkOwnership
methodunu kullanarak gerçekleştireceğiz.
// 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 { protected function supports($attribute, $subject) { if (!in_array($attribute, ['view', 'edit'])) { return false; } if (!$subject instanceof Post) { return false; } return true; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); if (!$user instanceof User) { return false; } /** @var Post $post */ $post = $subject; switch ($attribute) { case 'view': return $this->checkOwnership($post, $user); case 'edit': return $this->checkOwnership($post, $user); } throw new \LogicException('Beklenmedik hata!'); } private function checkOwnership(Post $post, User $user) { return $user === $post->getOwner(); } }
Voter içinde genel olarak izinlerin kontrolleri yapılıyor. Ama SUPER_ADMIN gibi bir rol tanımladıysanız bu role sahip tüm kullanıcılara izin vermek gerekecektir. Bunun için de voter içinde rol kontrolü ekleyebilirsiniz.
// src/Security/PostVoter.php // ... use Symfony\Component\Security\Core\Security; class PostVoter extends Voter { // ... private $security; public function __construct(Security $security) { $this->security = $security; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { // ... // ROLE_SUPER_ADMIN ise izin ver if ($this->security->isGranted('SUPER_ADMIN')) { return true; } // ... diğer kontroller } }
Access Decision Strategy Türleri
Symfony voters 4 farklı strajiye bağlı olarak işleme izin verir ya da reddeder. Varsayılan olarak en az bir voter’ın voteOnAttribute
methodundan true dönmesi symfony’inin işleme izin vermesi için yeterlidir. Ancak bu durumu değiştirmek isterseniz tanımlı olan 4 strateji şunlardır:
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 v5.1 ile kullanılmaya başlanmıştır. v5.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 ve allow_if_all_abstain değerini değiştirmek için securtiy.yaml dosyasınızda kullanmak istediğiniz değerleri tanımlamanız gerekmektedir.
# config/packages/security.yaml security: access_decision_manager: strategy: unanimous allow_if_all_abstain: false
Bu yazımda Symfony Voter’ın nasıl kullanıldığını ve ne için kullanılması gerektiğini anlattım. Symfony Voter hakkında daha ayrıntılı bilgi için smyfony voter’ın kendi dökümanını buradan inceleyebilirsiniz.
Tolga Bey,Emeğinize Sağlık.