mirror of
https://github.com/wallabag/wallabag.git
synced 2025-04-10 03:54:07 +00:00
Add IsGranted to TagController
This commit is contained in:
parent
4a1598165f
commit
943bfd9162
13 changed files with 310 additions and 41 deletions
|
@ -6,10 +6,12 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||
use Doctrine\ORM\QueryBuilder;
|
||||
use Pagerfanta\Adapter\ArrayAdapter;
|
||||
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Wallabag\Entity\Tag;
|
||||
|
@ -26,16 +28,19 @@ class TagController extends AbstractController
|
|||
private EntityManagerInterface $entityManager;
|
||||
private TagsAssigner $tagsAssigner;
|
||||
private Redirect $redirectHelper;
|
||||
private Security $security;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, TagsAssigner $tagsAssigner, Redirect $redirectHelper)
|
||||
public function __construct(EntityManagerInterface $entityManager, TagsAssigner $tagsAssigner, Redirect $redirectHelper, Security $security)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->tagsAssigner = $tagsAssigner;
|
||||
$this->redirectHelper = $redirectHelper;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new-tag/{entry}", name="new_tag", methods={"POST"}, requirements={"entry" = "\d+"})
|
||||
* @IsGranted("TAG", subject="entry")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
|
@ -59,8 +64,6 @@ class TagController extends AbstractController
|
|||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$this->tagsAssigner->assignTagsToEntry(
|
||||
$entry,
|
||||
$form->get('label')->getData()
|
||||
|
@ -87,18 +90,17 @@ class TagController extends AbstractController
|
|||
* Removes tag from entry.
|
||||
*
|
||||
* @Route("/remove-tag/{entry}/{tag}", name="remove_tag", methods={"GET"}, requirements={"entry" = "\d+", "tag" = "\d+"})
|
||||
* @IsGranted("UNTAG", subject="entry")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function removeTagFromEntry(Request $request, Entry $entry, Tag $tag)
|
||||
{
|
||||
$this->checkUserAction($entry);
|
||||
|
||||
$entry->removeTag($tag);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// remove orphan tag in case no entries are associated to it
|
||||
if (0 === \count($tag->getEntries())) {
|
||||
if (0 === \count($tag->getEntries()) && $this->security->isGranted('DELETE', $tag)) {
|
||||
$this->entityManager->remove($tag);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
@ -112,21 +114,22 @@ class TagController extends AbstractController
|
|||
* Shows tags for current user.
|
||||
*
|
||||
* @Route("/tag/list", name="tag", methods={"GET"})
|
||||
* @IsGranted("LIST_TAGS")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function showTagAction(TagRepository $tagRepository, EntryRepository $entryRepository)
|
||||
{
|
||||
$tags = $tagRepository->findAllFlatTagsWithNbEntries($this->getUser()->getId());
|
||||
$allTagsWithNbEntries = $tagRepository->findAllTagsWithNbEntries($this->getUser()->getId());
|
||||
$nbEntriesUntagged = $entryRepository->countUntaggedEntriesByUser($this->getUser()->getId());
|
||||
|
||||
$renameForms = [];
|
||||
foreach ($tags as $tag) {
|
||||
$renameForms[$tag['id']] = $this->createForm(RenameTagType::class, new Tag())->createView();
|
||||
foreach ($allTagsWithNbEntries as $tagWithNbEntries) {
|
||||
$renameForms[$tagWithNbEntries['tag']->getId()] = $this->createForm(RenameTagType::class, new Tag())->createView();
|
||||
}
|
||||
|
||||
return $this->render('Tag/tags.html.twig', [
|
||||
'tags' => $tags,
|
||||
'allTagsWithNbEntries' => $allTagsWithNbEntries,
|
||||
'renameForms' => $renameForms,
|
||||
'nbEntriesUntagged' => $nbEntriesUntagged,
|
||||
]);
|
||||
|
@ -137,6 +140,8 @@ class TagController extends AbstractController
|
|||
*
|
||||
* @Route("/tag/list/{slug}/{page}", name="tag_entries", methods={"GET"}, defaults={"page" = "1"})
|
||||
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
|
||||
* @IsGranted("LIST_ENTRIES")
|
||||
* @IsGranted("VIEW", subject="tag")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
|
@ -176,6 +181,7 @@ class TagController extends AbstractController
|
|||
*
|
||||
* @Route("/tag/rename/{slug}", name="tag_rename", methods={"POST"})
|
||||
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
|
||||
* @IsGranted("EDIT", subject="tag")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
|
@ -228,6 +234,7 @@ class TagController extends AbstractController
|
|||
* Tag search results with the current search term.
|
||||
*
|
||||
* @Route("/tag/search/{filter}", name="tag_this_search", methods={"GET"})
|
||||
* @IsGranted("CREATE_TAGS")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
|
@ -264,6 +271,7 @@ class TagController extends AbstractController
|
|||
*
|
||||
* @Route("/tag/delete/{slug}", name="tag_delete", methods={"GET"})
|
||||
* @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
|
||||
* @IsGranted("DELETE", subject="tag")
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
|
@ -282,14 +290,4 @@ class TagController extends AbstractController
|
|||
|
||||
return $this->redirect($redirectUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the logged user can manage the given entry.
|
||||
*/
|
||||
private function checkUserAction(Entry $entry)
|
||||
{
|
||||
if (null === $this->getUser() || $this->getUser()->getId() !== $entry->getUser()->getId()) {
|
||||
throw $this->createAccessDeniedException('You can not access this entry.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Wallabag\Repository;
|
||||
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Wallabag\Entity\Tag;
|
||||
|
@ -13,8 +14,10 @@ use Wallabag\Entity\Tag;
|
|||
*/
|
||||
class TagRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
public function __construct(
|
||||
ManagerRegistry $registry,
|
||||
private string $tablePrefix,
|
||||
) {
|
||||
parent::__construct($registry, Tag::class);
|
||||
}
|
||||
|
||||
|
@ -85,6 +88,36 @@ class TagRepository extends ServiceEntityRepository
|
|||
->getArrayResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all tags per user with nb entries.
|
||||
*
|
||||
* @param int $userId
|
||||
*
|
||||
* @return array<array{tag: Tag, nbEntries: int}>
|
||||
*/
|
||||
public function findAllTagsWithNbEntries($userId)
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
|
||||
$rsm->addRootEntityFromClassMetadata(Tag::class, 't');
|
||||
$rsm->addEntityResult(Tag::class, 't', 'tag');
|
||||
$rsm->addScalarResult('nbEntries', 'nbEntries', 'integer');
|
||||
|
||||
$sql = <<<SQL
|
||||
SELECT DISTINCT {$rsm->generateSelectClause()}, count(e.id) as nbEntries
|
||||
FROM {$this->tablePrefix}tag t
|
||||
LEFT JOIN {$this->tablePrefix}entry_tag et ON et.tag_id = t.id
|
||||
JOIN {$this->tablePrefix}entry e ON e.id = et.entry_id
|
||||
WHERE e.user_id = :userId
|
||||
GROUP BY t.id
|
||||
ORDER BY t.label
|
||||
SQL;
|
||||
|
||||
$query = $this->getEntityManager()->createNativeQuery($sql, $rsm);
|
||||
$query->setParameter('userId', $userId);
|
||||
|
||||
return $query->getResult();
|
||||
}
|
||||
|
||||
public function findByLabelsAndUser($labels, $userId)
|
||||
{
|
||||
$qb = $this->getQueryBuilderByUser($userId)
|
||||
|
|
|
@ -20,6 +20,8 @@ class EntryVoter extends Voter
|
|||
public const DELETE = 'DELETE';
|
||||
public const LIST_ANNOTATIONS = 'LIST_ANNOTATIONS';
|
||||
public const CREATE_ANNOTATIONS = 'CREATE_ANNOTATIONS';
|
||||
public const TAG = 'TAG';
|
||||
public const UNTAG = 'UNTAG';
|
||||
|
||||
protected function supports(string $attribute, $subject): bool
|
||||
{
|
||||
|
@ -27,7 +29,7 @@ class EntryVoter extends Voter
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!\in_array($attribute, [self::VIEW, self::EDIT, self::RELOAD, self::STAR, self::ARCHIVE, self::SHARE, self::UNSHARE, self::EXPORT, self::DELETE, self::LIST_ANNOTATIONS, self::CREATE_ANNOTATIONS], true)) {
|
||||
if (!\in_array($attribute, [self::VIEW, self::EDIT, self::RELOAD, self::STAR, self::ARCHIVE, self::SHARE, self::UNSHARE, self::EXPORT, self::DELETE, self::LIST_ANNOTATIONS, self::CREATE_ANNOTATIONS, self::TAG, self::UNTAG], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -56,6 +58,8 @@ class EntryVoter extends Voter
|
|||
case self::DELETE:
|
||||
case self::LIST_ANNOTATIONS:
|
||||
case self::CREATE_ANNOTATIONS:
|
||||
case self::TAG:
|
||||
case self::UNTAG:
|
||||
return $user === $subject->getUser();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ class MainVoter extends Voter
|
|||
public const EDIT_ENTRIES = 'EDIT_ENTRIES';
|
||||
public const EXPORT_ENTRIES = 'EXPORT_ENTRIES';
|
||||
public const IMPORT_ENTRIES = 'IMPORT_ENTRIES';
|
||||
public const LIST_TAGS = 'LIST_TAGS';
|
||||
public const CREATE_TAGS = 'CREATE_TAGS';
|
||||
public const LIST_SITE_CREDENTIALS = 'LIST_SITE_CREDENTIALS';
|
||||
public const CREATE_SITE_CREDENTIALS = 'CREATE_SITE_CREDENTIALS';
|
||||
public const EDIT_CONFIG = 'EDIT_CONFIG';
|
||||
|
@ -30,7 +32,7 @@ class MainVoter extends Voter
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!\in_array($attribute, [self::LIST_ENTRIES, self::CREATE_ENTRIES, self::EDIT_ENTRIES, self::EXPORT_ENTRIES, self::IMPORT_ENTRIES, self::LIST_SITE_CREDENTIALS, self::CREATE_SITE_CREDENTIALS, self::EDIT_CONFIG], true)) {
|
||||
if (!\in_array($attribute, [self::LIST_ENTRIES, self::CREATE_ENTRIES, self::EDIT_ENTRIES, self::EXPORT_ENTRIES, self::IMPORT_ENTRIES, self::LIST_TAGS, self::CREATE_TAGS, self::LIST_SITE_CREDENTIALS, self::CREATE_SITE_CREDENTIALS, self::EDIT_CONFIG], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -45,6 +47,8 @@ class MainVoter extends Voter
|
|||
case self::EDIT_ENTRIES:
|
||||
case self::EXPORT_ENTRIES:
|
||||
case self::IMPORT_ENTRIES:
|
||||
case self::LIST_TAGS:
|
||||
case self::CREATE_TAGS:
|
||||
case self::LIST_SITE_CREDENTIALS:
|
||||
case self::CREATE_SITE_CREDENTIALS:
|
||||
case self::EDIT_CONFIG:
|
||||
|
|
54
src/Security/Voter/TagVoter.php
Normal file
54
src/Security/Voter/TagVoter.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\Security\Voter;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Wallabag\Entity\Tag;
|
||||
use Wallabag\Entity\User;
|
||||
|
||||
class TagVoter extends Voter
|
||||
{
|
||||
public const VIEW = 'VIEW';
|
||||
public const EDIT = 'EDIT';
|
||||
public const DELETE = 'DELETE';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
protected function supports(string $attribute, $subject): bool
|
||||
{
|
||||
if (!$subject instanceof Tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\in_array($attribute, [self::VIEW, self::EDIT, self::DELETE], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
\assert($subject instanceof Tag);
|
||||
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($attribute) {
|
||||
case self::VIEW:
|
||||
case self::EDIT:
|
||||
case self::DELETE:
|
||||
return $this->security->isGranted('ROLE_USER');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
{% for tag in tags %}
|
||||
<li class="chip">
|
||||
<a class="chip-label" href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a>
|
||||
{% if withRemove is defined and withRemove == true %}
|
||||
{% if withRemove is defined and withRemove == true and is_granted('DELETE', tag) %}
|
||||
{% set current_path = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
|
||||
<a class="chip-action" href="{{ path('remove_tag', {'entry': entryId, 'tag': tag.id, redirect: current_path}) }}" onclick="return confirm('{{ 'entry.confirm.delete_tag'|trans|escape('js') }}')">
|
||||
<i class="material-icons vertical-align-middle">delete</i>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
{% include "Entry/_feed_link.html.twig" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if current_route == 'search' %}<div><a href="{{ path('tag_this_search', {'filter': searchTerm, 'currentRoute': app.request.get('currentRoute'), redirect: current_path}) }}" title="{{ 'entry.list.assign_search_tag'|trans }}">{{ 'entry.list.assign_search_tag'|trans }}</a></div>{% endif %}
|
||||
{% if current_route == 'search' and is_granted('CREATE_TAGS') %}<div><a href="{{ path('tag_this_search', {'filter': searchTerm, 'currentRoute': app.request.get('currentRoute'), redirect: current_path}) }}" title="{{ 'entry.list.assign_search_tag'|trans }}">{{ 'entry.list.assign_search_tag'|trans }}</a></div>{% endif %}
|
||||
{% if entries.getNbPages > 1 %}
|
||||
{{ pagerfanta(entries, 'default_wallabag') }}
|
||||
{% endif %}
|
||||
|
|
|
@ -316,12 +316,14 @@
|
|||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% include "Entry/_tags.html.twig" with {'tags': entry.tags, 'entryId': entry.id, 'withRemove': true} only %}
|
||||
{% include "Entry/_tags.html.twig" with {'tags': entry.tags, 'entryId': entry.id, 'withRemove': is_granted('UNTAG', entry)} only %}
|
||||
</div>
|
||||
|
||||
<div class="input-field nav-panel-add-tag" style="display: none">
|
||||
{{ render(controller('Wallabag\\Controller\\TagController::addTagFormAction', {'id': entry.id})) }}
|
||||
</div>
|
||||
{% if is_granted('TAG', entry) %}
|
||||
<div class="input-field nav-panel-add-tag" style="display: none">
|
||||
{{ render(controller('Wallabag\\Controller\\TagController::addTagFormAction', {'id': entry.id})) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</aside>
|
||||
<article{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{% set current_path = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
|
||||
|
||||
<div class="results clearfix">
|
||||
{{ 'tag.list.number_on_the_page'|trans({'%count%': tags|length}) }}
|
||||
{{ 'tag.list.number_on_the_page'|trans({'%count%': allTagsWithNbEntries|length}) }}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -16,12 +16,14 @@
|
|||
<a href="{{ path('untagged') }}">{{ 'tag.list.untagged'|trans }} ({{ nbEntriesUntagged }})</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for tag in tags %}
|
||||
<li title="{{ tag.label }} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}" class="chip">
|
||||
{% for tagWithNbEntries in allTagsWithNbEntries %}
|
||||
{% set tag = tagWithNbEntries.tag %}
|
||||
{% set nbEntries = tagWithNbEntries.nbEntries %}
|
||||
<li title="{{ tag.label }} ({{ nbEntries }})" id="tag-{{ tag.id }}" class="chip">
|
||||
<a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-handle="tag-link">
|
||||
{{ tag.label }} ({{ tag.nbEntries }})
|
||||
{{ tag.label }} ({{ nbEntries }})
|
||||
</a>
|
||||
{% if renameForms is defined and renameForms[tag.id] is defined %}
|
||||
{% if renameForms is defined and renameForms[tag.id] is defined and is_granted('EDIT', tag) %}
|
||||
<form class="card-tag-form hidden" data-handle="tag-rename-form" action="{{ path('tag_rename', {'slug': tag.slug, redirect: current_path}) }}" method="POST">
|
||||
{{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label}}) }}
|
||||
{{ form_rest(renameForms[tag.id]) }}
|
||||
|
@ -30,9 +32,11 @@
|
|||
<i class="material-icons">mode_edit</i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a id="delete-{{ tag.slug }}" href="{{ path('tag_delete', {'slug': tag.slug, redirect: current_path}) }}" class="card-tag-icon card-tag-delete" onclick="return confirm('{{ 'tag.confirm.delete'|trans({'%name%': tag.label})|escape('js') }}')">
|
||||
<i class="material-icons">delete</i>
|
||||
</a>
|
||||
{% if is_granted('DELETE', tag) %}
|
||||
<a id="delete-{{ tag.slug }}" href="{{ path('tag_delete', {'slug': tag.slug, redirect: current_path}) }}" class="card-tag-icon card-tag-delete" onclick="return confirm('{{ 'tag.confirm.delete'|trans({'%name%': tag.label})|escape('js') }}')">
|
||||
<i class="material-icons">delete</i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if app.user.config.feedToken %}
|
||||
<a rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" class="card-tag-icon"><i class="material-icons">rss_feed</i></a>
|
||||
{% endif %}
|
||||
|
|
|
@ -73,9 +73,11 @@
|
|||
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="items-number grey-text">{{ count_entries('all') }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="bold {% if current_route == 'tags' %}active{% endif %}">
|
||||
<a class="waves-effect" href="{{ path('tag') }}">{{ 'menu.left.tags'|trans }} <span class="items-number grey-text">{{ count_tags() }}</span></a>
|
||||
</li>
|
||||
{% if is_granted('LIST_TAGS') %}
|
||||
<li class="bold {% if current_route == 'tags' %}active{% endif %}">
|
||||
<a class="waves-effect" href="{{ path('tag') }}">{{ 'menu.left.tags'|trans }} <span class="items-number grey-text">{{ count_tags() }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="nav-panels">
|
||||
<div class="nav-panel-actions nav-panel-item">
|
||||
|
|
|
@ -188,4 +188,32 @@ class EntryVoterTest extends TestCase
|
|||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::CREATE_ANNOTATIONS]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForNonEntryUserTag(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::TAG]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForEntryUserTag(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn($this->user);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::TAG]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForNonEntryUserUntag(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::UNTAG]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForEntryUserUntag(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn($this->user);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::UNTAG]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,34 @@ class MainVoterTest extends TestCase
|
|||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::IMPORT_ENTRIES]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForNonUserListTags(): void
|
||||
{
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::LIST_TAGS]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForUserListTags(): void
|
||||
{
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::LIST_TAGS]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForNonUserCreateTags(): void
|
||||
{
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_TAGS]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForUserCreateTags(): void
|
||||
{
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_TAGS]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForNonUserListSiteCredentials(): void
|
||||
{
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
|
112
tests/Security/Voter/TagVoterTest.php
Normal file
112
tests/Security/Voter/TagVoterTest.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Wallabag\Security\Voter;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Wallabag\Entity\Tag;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Security\Voter\TagVoter;
|
||||
|
||||
class TagVoterTest extends TestCase
|
||||
{
|
||||
private $security;
|
||||
private $token;
|
||||
private $tagVoter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->security = $this->createMock(Security::class);
|
||||
|
||||
$this->token = $this->createMock(TokenInterface::class);
|
||||
|
||||
$this->tagVoter = new TagVoter($this->security);
|
||||
}
|
||||
|
||||
public function testVoteReturnsAbstainForInvalidSubject(): void
|
||||
{
|
||||
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->tagVoter->vote($this->token, new \stdClass(), [TagVoter::EDIT]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsAbstainForInvalidAttribute(): void
|
||||
{
|
||||
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->tagVoter->vote($this->token, new Tag(), ['INVALID']));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForUnauthenticatedView(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(null);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::VIEW]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForUnauthorizedUserView(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::VIEW]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForAuthorizedUserView(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::VIEW]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForUnauthenticatedEdit(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(null);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::EDIT]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForUnauthorizedUserEdit(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::EDIT]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForAuthorizedUserEdit(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::EDIT]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsDeniedForUnauthenticatedDelete(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(null);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::DELETE]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForUnauthorizedUserDelete(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::DELETE]));
|
||||
}
|
||||
|
||||
public function testVoteReturnsGrantedForAuthorizedUserDelete(): void
|
||||
{
|
||||
$this->token->method('getUser')->willReturn(new User());
|
||||
|
||||
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||
|
||||
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->tagVoter->vote($this->token, new Tag(), [TagVoter::DELETE]));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue