Add propper access control for snips with public option
This commit is contained in:
parent
607435bff0
commit
693f83ca4a
31
migrations/Version20230404215108.php
Normal file
31
migrations/Version20230404215108.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20230404215108 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE snip ADD public TINYINT(1) NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE snip DROP public');
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
||||
use App\Entity\Snip;
|
||||
use App\Form\SnipType;
|
||||
use App\Repository\SnipRepository;
|
||||
use App\Security\Voter\SnipVoter;
|
||||
use App\Service\SnipServiceFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
@ -33,6 +34,8 @@ class SnipController extends AbstractController
|
||||
#[Route('/single/{snip}', name: '_single')]
|
||||
public function single(Snip $snip): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||
|
||||
return $this->render('snip/single.html.twig', [
|
||||
'snip' => $snip,
|
||||
'content' => $this->snipServiceFactory->create($snip)->get(),
|
||||
@ -42,6 +45,8 @@ class SnipController extends AbstractController
|
||||
#[Route('/raw/{snip}', name: '_raw')]
|
||||
public function raw(Snip $snip, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||
|
||||
$response = new Response(
|
||||
$this->snipServiceFactory->create($snip)->get(),
|
||||
Response::HTTP_OK,
|
||||
@ -67,6 +72,8 @@ class SnipController extends AbstractController
|
||||
#[Route('/edit/{snip}', name: '_edit')]
|
||||
public function edit(Snip $snip, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||
|
||||
$form = $this->createForm(SnipType::class, $snip);
|
||||
$form->add('Save', SubmitType::class);
|
||||
if ($snip->getId()) {
|
||||
|
@ -19,6 +19,9 @@ class Snip
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $name = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private ?bool $public = null;
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name ?? '';
|
||||
@ -40,4 +43,16 @@ class Snip
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isPublic(): ?bool
|
||||
{
|
||||
return $this->public;
|
||||
}
|
||||
|
||||
public function setPublic(bool $public): self
|
||||
{
|
||||
$this->public = $public;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class SnipType extends AbstractType
|
||||
'attr' => ['rows' => 20],
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('public')
|
||||
;
|
||||
}
|
||||
|
||||
|
47
src/Security/Voter/SnipVoter.php
Normal file
47
src/Security/Voter/SnipVoter.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Snip;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class SnipVoter extends Voter
|
||||
{
|
||||
public const EDIT = 'edit';
|
||||
public const VIEW = 'view';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
// replace with your own logic
|
||||
// https://symfony.com/doc/current/security/voters.html
|
||||
return in_array($attribute, [self::EDIT, self::VIEW])
|
||||
&& $subject instanceof Snip;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
/** @var Snip $subject */
|
||||
$user = $token->getUser();
|
||||
// if the user is anonymous, do not grant access
|
||||
if (!$user instanceof UserInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... (check conditions and return true to grant permission) ...
|
||||
switch ($attribute) {
|
||||
case self::VIEW:
|
||||
if ($subject->isPublic()) {
|
||||
return true;
|
||||
}
|
||||
case self::EDIT:
|
||||
if ($subject->getCreatedBy() === $user) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
9
templates/snip/badge.html.twig
Normal file
9
templates/snip/badge.html.twig
Normal file
@ -0,0 +1,9 @@
|
||||
{% if snip.public %}
|
||||
<span class="badge bg-info">
|
||||
<i class="fa fa-lock-open"></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">
|
||||
<i class="fa fa-lock"></i>
|
||||
</span>
|
||||
{% endif %}
|
@ -6,7 +6,7 @@
|
||||
<div class="list-group">
|
||||
{% for snip in snips %}
|
||||
<a class="list-group-item" href="{{ path('snip_single', {snip: snip.id}) }}">
|
||||
{{ snip }}
|
||||
{{ include('snip/badge.html.twig', {snip: snip}) }} {{ snip }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -3,9 +3,12 @@
|
||||
{% block body %}
|
||||
<div class="card" style="width: 100%;">
|
||||
<h4 class="card-header">
|
||||
<a class="btn btn-sm btn-outline-info" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% if is_granted('edit', snip) %}
|
||||
<a class="btn btn-sm btn-outline-info" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||
{{ snip }}
|
||||
</h4>
|
||||
<div class="card-body">
|
||||
|
Loading…
Reference in New Issue
Block a user