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\Entity\Snip;
|
||||||
use App\Form\SnipType;
|
use App\Form\SnipType;
|
||||||
use App\Repository\SnipRepository;
|
use App\Repository\SnipRepository;
|
||||||
|
use App\Security\Voter\SnipVoter;
|
||||||
use App\Service\SnipServiceFactory;
|
use App\Service\SnipServiceFactory;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
@ -33,6 +34,8 @@ class SnipController extends AbstractController
|
|||||||
#[Route('/single/{snip}', name: '_single')]
|
#[Route('/single/{snip}', name: '_single')]
|
||||||
public function single(Snip $snip): Response
|
public function single(Snip $snip): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||||
|
|
||||||
return $this->render('snip/single.html.twig', [
|
return $this->render('snip/single.html.twig', [
|
||||||
'snip' => $snip,
|
'snip' => $snip,
|
||||||
'content' => $this->snipServiceFactory->create($snip)->get(),
|
'content' => $this->snipServiceFactory->create($snip)->get(),
|
||||||
@ -42,6 +45,8 @@ class SnipController extends AbstractController
|
|||||||
#[Route('/raw/{snip}', name: '_raw')]
|
#[Route('/raw/{snip}', name: '_raw')]
|
||||||
public function raw(Snip $snip, Request $request): Response
|
public function raw(Snip $snip, Request $request): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||||
|
|
||||||
$response = new Response(
|
$response = new Response(
|
||||||
$this->snipServiceFactory->create($snip)->get(),
|
$this->snipServiceFactory->create($snip)->get(),
|
||||||
Response::HTTP_OK,
|
Response::HTTP_OK,
|
||||||
@ -67,6 +72,8 @@ class SnipController extends AbstractController
|
|||||||
#[Route('/edit/{snip}', name: '_edit')]
|
#[Route('/edit/{snip}', name: '_edit')]
|
||||||
public function edit(Snip $snip, Request $request): Response
|
public function edit(Snip $snip, Request $request): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||||
|
|
||||||
$form = $this->createForm(SnipType::class, $snip);
|
$form = $this->createForm(SnipType::class, $snip);
|
||||||
$form->add('Save', SubmitType::class);
|
$form->add('Save', SubmitType::class);
|
||||||
if ($snip->getId()) {
|
if ($snip->getId()) {
|
||||||
|
@ -19,6 +19,9 @@ class Snip
|
|||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $name = null;
|
private ?string $name = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?bool $public = null;
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->name ?? '';
|
return $this->name ?? '';
|
||||||
@ -40,4 +43,16 @@ class Snip
|
|||||||
|
|
||||||
return $this;
|
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],
|
'attr' => ['rows' => 20],
|
||||||
'mapped' => false,
|
'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">
|
<div class="list-group">
|
||||||
{% for snip in snips %}
|
{% for snip in snips %}
|
||||||
<a class="list-group-item" href="{{ path('snip_single', {snip: snip.id}) }}">
|
<a class="list-group-item" href="{{ path('snip_single', {snip: snip.id}) }}">
|
||||||
{{ snip }}
|
{{ include('snip/badge.html.twig', {snip: snip}) }} {{ snip }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="card" style="width: 100%;">
|
<div class="card" style="width: 100%;">
|
||||||
<h4 class="card-header">
|
<h4 class="card-header">
|
||||||
<a class="btn btn-sm btn-outline-info" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
{% if is_granted('edit', snip) %}
|
||||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
<a class="btn btn-sm btn-outline-info" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
||||||
</a>
|
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||||
{{ snip }}
|
{{ snip }}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
Loading…
Reference in New Issue
Block a user