Messing with voters

This commit is contained in:
Nicolò P 2024-10-30 17:44:47 +01:00
parent b991ae8d00
commit b7381eafa3
8 changed files with 104 additions and 30 deletions

View File

@ -47,17 +47,21 @@ if (! location.pathname.includes('login')) {
const userMenu = document.querySelector('.dropdown-trigger');
const userCaret = document.querySelector('#user-caret');
document.querySelector('#for-vocabs').addEventListener('click', function () {
vocabs.classList.toggle('is-hidden');
const forVocabs = document.querySelector('#for-vocabs')
if (forVocabs) {
forVocabs.addEventListener('click', function () {
vocabs.classList.toggle('is-hidden');
if (this.firstElementChild.classList.contains('fa-angle-right')) {
this.firstElementChild.classList.remove('fa-angle-right');
this.firstElementChild.classList.add('fa-angle-down');
} else {
this.firstElementChild.classList.remove('fa-angle-down');
this.firstElementChild.classList.add('fa-angle-right');
}
});
if (this.firstElementChild.classList.contains('fa-angle-right')) {
this.firstElementChild.classList.remove('fa-angle-right');
this.firstElementChild.classList.add('fa-angle-down');
} else {
this.firstElementChild.classList.remove('fa-angle-down');
this.firstElementChild.classList.add('fa-angle-right');
}
});
}
userMenu.addEventListener('click', function () {
document.querySelector('.dropdown').classList.toggle('is-active');

View File

@ -3,12 +3,14 @@
namespace App\Controller;
use App\Entity\VocabFuncContext;
use App\Repository\VocabFuncContextRepository;
use App\Security\Voter\VocabVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class VocabFuncContextController extends AbstractController
{
@ -17,10 +19,8 @@ class VocabFuncContextController extends AbstractController
{
$roles = $this->getUser()->getRoles();
if (! in_array('ROLE_REVISOR', $roles)
&& ! in_array('ROLE_ADMIN', $roles)
) {
$this->addFlash('warning', 'Only revisors and administrators can edit vocabularies');
if (in_array('ROLE_READER', $roles)) {
$this->addFlash('warning', 'Only editors, revisors and administrators can view vocabularies');
return $this->redirectToRoute('app_home');
}
@ -35,9 +35,16 @@ class VocabFuncContextController extends AbstractController
#[Route('/vocabs/functional_context/add', name: 'app_vocab_func_context_add')]
public function addTerm(Request $request, EntityManagerInterface $em): Response
{
$term = $request->getPayload()->get('_term');
$vocab = new VocabFuncContext;
try {
$this->denyAccessUnlessGranted(VocabVoter::EDIT, $vocab);
}
catch (AccessDeniedException) {
$this->addFlash('warning', 'Only revisors and administrators can edit vocabularies');
return $this->redirectToRoute('app_home');
}
$term = $request->getPayload()->get('_term');
$vocab->setTerm($term);
$em->persist($vocab);
$em->flush();

View File

@ -7,7 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity()]
#[ORM\Table(name: 'lis_contesto_funz')]
class VocabFuncContext
class VocabFuncContext implements \App\VocabInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]

View File

@ -0,0 +1,49 @@
<?php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
final class VocabVoter extends Voter
{
public const EDIT = 'VOCAB_EDIT';
public const DELETE = 'VOCAB_DELETE';
public const VIEW = 'VOCAB_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, self::DELETE])
&& $subject instanceof \App\VocabInterface;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
$roles = $user->getRoles();
// TODO: Better way to check roles?
switch ($attribute) {
case self::EDIT:
case self::DELETE:
return in_array('ROLE_ADMIN', $roles)
|| in_array('ROLE_REVISOR', $roles);
break;
case self::VIEW:
return ! in_array('ROLE_READER', $roles);
break;
}
return false;
}
}

7
src/VocabInterface.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace App;
interface VocabInterface
{
}

View File

@ -33,7 +33,7 @@
</span>
<span class="pl-2">Profile</span>
</a>
{% if 'ROLE_ADMIN' in app.user.roles %}
{% if is_granted('ROLE_ADMIN') %}
<a href="/admin" class="dropdown-item">
<span class="icon is-small">
<i class="fa fa-cogs"></i>
@ -57,6 +57,7 @@
<div class="columns mb-0">
<div class="column is-one-fifth arcoa-menu mb-0">
<aside class="menu pl-4">
{% if 'ROLE_READER' not in app.user.roles %}
<p class="menu-label has-text-white mt-3 pt-5 pl-5 is-size-6">
Vocabularies
<span class="icon is-clickable pl-4" id="for-vocabs">
@ -201,6 +202,7 @@
</a>
</li>
</ul>
{% endif %}
<p class="menu-label has-text-white mt-3 pt-5 pl-5 is-size-6">
Records
<span class="icon is-clickable pl-4" id="for-records">

View File

@ -3,6 +3,16 @@
{% block title %}Home | ArCOA{% endblock %}
{% block rightpanel %}
{% for message in app.flashes('warning') %}
<article class="message is-warning mb-6 mt-3 ml-auto mr-auto" style="max-width: 35vw">
<div class="message-header">
<p>Warning</p>
<button class="delete" aria-label="delete"></button>
</div>
<div class="message-body">{{ message }}</div>
</article>
{% endfor %}
<div class="has-text-centered">
<img width="200px" src="{{ asset('img/Logo-ArCOA-def.png') }}" />
</div>
@ -11,15 +21,6 @@
<h2 class="is-size-3 mb-3 has-text-centered">Archivi e Collezioni dell'Oriente Antico</h2>
<h2 class="is-size-3 mb-6 has-text-centered">Archives and Collections of the Ancient Near East</h2>
{% for message in app.flashes('warning') %}
<article class="message is-warning" style="max-width: 35vw; margin: 0 auto;">
<div class="message-header">
<p>Warning</p>
<button class="delete" aria-label="delete"></button>
</div>
<div class="message-body">{{ message }}</div>
</article>
{% endfor %}
<div class="columns" style="max-width: 35vw; margin: 0 auto;">
<div class="column mt-6 mb-5">

View File

@ -4,7 +4,7 @@
{% block rightpanel %}
<div class="container" style="max-width: 50vw">
<h1 class="is-size-1 mt-0 has-text-centered">Edit vocabulary</h1>
<h1 class="is-size-1 mt-0 has-text-centered">Vocabulary</h1>
<h2 class="is-size-3 mt-4 has-text-centered">Functional context</h2>
<div class="container mt-6">
@ -18,6 +18,7 @@
{{ message }}
</div>
{% endfor %}
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<form method="post" action="{{ path('app_vocab_func_context_add') }}">
<label class="label">Add new term</label>
<div class="field has-addons">
@ -34,15 +35,17 @@
</div>
</div>
</form>
{% endif %}
<h3 class="mt-6 mb-5 is-size-5"><strong>Terms in vocabulary</strong></h3>
<table class="table is-fullwidth" id="terms">
<tr><th>Term</th><th>Actions</th></tr>
<tr><th>Term</th>{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}<th>Actions</th>{% endif %}</tr>
{% for term in terms %}
<tr data-row-id="{{ term.id }}">
<td>
<input class="input" type="text" value="{{ term.term }}" disabled data-term-id="{{ term.id }}" />
</td>
{% if is_granted('ROLE_ADMIN') or is_granted('ROLE_REVISOR') %}
<td>
<div class="buttons">
<button class="button is-link" data-id-edit="{{ term.id }}">
@ -59,6 +62,7 @@
</button>
</div>
</td>
{% endif %}
</tr>
{% endfor %}
</table>