mirror of
https://github.com/wallabag/wallabag.git
synced 2024-11-26 02:51:04 +00:00
Use IsGranted in EntryController
This commit is contained in:
parent
92786c6304
commit
b8819cc3d7
16 changed files with 575 additions and 151 deletions
|
@ -8,12 +8,14 @@ use Doctrine\ORM\NoResultException;
|
||||||
use Pagerfanta\Doctrine\ORM\QueryAdapter as DoctrineORMAdapter;
|
use Pagerfanta\Doctrine\ORM\QueryAdapter as DoctrineORMAdapter;
|
||||||
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
|
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
|
||||||
use Spiriit\Bundle\FormFilterBundle\Filter\FilterBuilderUpdaterInterface;
|
use Spiriit\Bundle\FormFilterBundle\Filter\FilterBuilderUpdaterInterface;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use Wallabag\Entity\Entry;
|
use Wallabag\Entity\Entry;
|
||||||
use Wallabag\Entity\Tag;
|
use Wallabag\Entity\Tag;
|
||||||
|
@ -38,8 +40,9 @@ class EntryController extends AbstractController
|
||||||
private PreparePagerForEntries $preparePagerForEntriesHelper;
|
private PreparePagerForEntries $preparePagerForEntriesHelper;
|
||||||
private FilterBuilderUpdaterInterface $filterBuilderUpdater;
|
private FilterBuilderUpdaterInterface $filterBuilderUpdater;
|
||||||
private ContentProxy $contentProxy;
|
private ContentProxy $contentProxy;
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher, EntryRepository $entryRepository, Redirect $redirectHelper, PreparePagerForEntries $preparePagerForEntriesHelper, FilterBuilderUpdaterInterface $filterBuilderUpdater, ContentProxy $contentProxy)
|
public function __construct(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher, EntryRepository $entryRepository, Redirect $redirectHelper, PreparePagerForEntries $preparePagerForEntriesHelper, FilterBuilderUpdaterInterface $filterBuilderUpdater, ContentProxy $contentProxy, Security $security)
|
||||||
{
|
{
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
|
@ -48,10 +51,12 @@ class EntryController extends AbstractController
|
||||||
$this->preparePagerForEntriesHelper = $preparePagerForEntriesHelper;
|
$this->preparePagerForEntriesHelper = $preparePagerForEntriesHelper;
|
||||||
$this->filterBuilderUpdater = $filterBuilderUpdater;
|
$this->filterBuilderUpdater = $filterBuilderUpdater;
|
||||||
$this->contentProxy = $contentProxy;
|
$this->contentProxy = $contentProxy;
|
||||||
|
$this->security = $security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/mass", name="mass_action")
|
* @Route("/mass", name="mass_action")
|
||||||
|
* @IsGranted("EDIT_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -104,7 +109,9 @@ class EntryController extends AbstractController
|
||||||
/** @var Entry * */
|
/** @var Entry * */
|
||||||
$entry = $this->entryRepository->findById((int) $id)[0];
|
$entry = $this->entryRepository->findById((int) $id)[0];
|
||||||
|
|
||||||
$this->checkUserAction($entry);
|
if (!$this->security->isGranted('EDIT', $entry)) {
|
||||||
|
throw $this->createAccessDeniedException('You can not access this entry.');
|
||||||
|
}
|
||||||
|
|
||||||
if ('toggle-read' === $action) {
|
if ('toggle-read' === $action) {
|
||||||
$entry->toggleArchive();
|
$entry->toggleArchive();
|
||||||
|
@ -135,6 +142,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/search/{page}", name="search", defaults={"page" = 1})
|
* @Route("/search/{page}", name="search", defaults={"page" = 1})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* Default parameter for page is hardcoded (in duplication of the defaults from the Route)
|
* Default parameter for page is hardcoded (in duplication of the defaults from the Route)
|
||||||
* because this controller is also called inside the layout template without any page as argument
|
* because this controller is also called inside the layout template without any page as argument
|
||||||
|
@ -164,6 +172,7 @@ class EntryController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/new-entry", name="new_entry")
|
* @Route("/new-entry", name="new_entry")
|
||||||
|
* @IsGranted("CREATE_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -205,6 +214,7 @@ class EntryController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/bookmarklet", name="bookmarklet")
|
* @Route("/bookmarklet", name="bookmarklet")
|
||||||
|
* @IsGranted("CREATE_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -228,6 +238,7 @@ class EntryController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/new", name="new")
|
* @Route("/new", name="new")
|
||||||
|
* @IsGranted("CREATE_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -240,13 +251,12 @@ class EntryController extends AbstractController
|
||||||
* Edit an entry content.
|
* Edit an entry content.
|
||||||
*
|
*
|
||||||
* @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
|
* @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
|
||||||
|
* @IsGranted("EDIT", subject="entry")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function editEntryAction(Request $request, Entry $entry)
|
public function editEntryAction(Request $request, Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
$form = $this->createForm(EditEntryType::class, $entry);
|
$form = $this->createForm(EditEntryType::class, $entry);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
@ -274,6 +284,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
|
* @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -288,6 +299,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
|
* @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -307,6 +319,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
|
* @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -321,6 +334,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
|
* @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -335,6 +349,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
|
* @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -349,6 +364,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/annotated/list/{page}", name="annotated", defaults={"page" = "1"})
|
* @Route("/annotated/list/{page}", name="annotated", defaults={"page" = "1"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -361,6 +377,7 @@ class EntryController extends AbstractController
|
||||||
* Shows random entry depending on the given type.
|
* Shows random entry depending on the given type.
|
||||||
*
|
*
|
||||||
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|annotated|all"})
|
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|annotated|all"})
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
|
@ -382,13 +399,12 @@ class EntryController extends AbstractController
|
||||||
* Shows entry content.
|
* Shows entry content.
|
||||||
*
|
*
|
||||||
* @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
|
* @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
|
||||||
|
* @IsGranted("VIEW", subject="entry")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function viewAction(Entry $entry)
|
public function viewAction(Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
return $this->render(
|
return $this->render(
|
||||||
'Entry/entry.html.twig',
|
'Entry/entry.html.twig',
|
||||||
['entry' => $entry]
|
['entry' => $entry]
|
||||||
|
@ -400,13 +416,12 @@ class EntryController extends AbstractController
|
||||||
* Refetch content from the website and make it readable again.
|
* Refetch content from the website and make it readable again.
|
||||||
*
|
*
|
||||||
* @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry")
|
* @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry")
|
||||||
|
* @IsGranted("RELOAD", subject="entry")
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function reloadAction(Entry $entry)
|
public function reloadAction(Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
$this->updateEntry($entry, 'entry_reloaded');
|
$this->updateEntry($entry, 'entry_reloaded');
|
||||||
|
|
||||||
// if refreshing entry failed, don't save it
|
// if refreshing entry failed, don't save it
|
||||||
|
@ -429,13 +444,12 @@ class EntryController extends AbstractController
|
||||||
* Changes read status for an entry.
|
* Changes read status for an entry.
|
||||||
*
|
*
|
||||||
* @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
|
* @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
|
||||||
|
* @IsGranted("ARCHIVE", subject="entry")
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function toggleArchiveAction(Request $request, Entry $entry)
|
public function toggleArchiveAction(Request $request, Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
$entry->toggleArchive();
|
$entry->toggleArchive();
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
@ -458,13 +472,12 @@ class EntryController extends AbstractController
|
||||||
* Changes starred status for an entry.
|
* Changes starred status for an entry.
|
||||||
*
|
*
|
||||||
* @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
|
* @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
|
||||||
|
* @IsGranted("STAR", subject="entry")
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function toggleStarAction(Request $request, Entry $entry)
|
public function toggleStarAction(Request $request, Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
$entry->toggleStar();
|
$entry->toggleStar();
|
||||||
$entry->updateStar($entry->isStarred());
|
$entry->updateStar($entry->isStarred());
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
@ -488,13 +501,12 @@ class EntryController extends AbstractController
|
||||||
* Deletes entry and redirect to the homepage or the last viewed page.
|
* Deletes entry and redirect to the homepage or the last viewed page.
|
||||||
*
|
*
|
||||||
* @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
|
* @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
|
||||||
|
* @IsGranted("DELETE", subject="entry")
|
||||||
*
|
*
|
||||||
* @return RedirectResponse
|
* @return RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function deleteEntryAction(Request $request, Entry $entry)
|
public function deleteEntryAction(Request $request, Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
// generates the view url for this entry to check for redirection later
|
// generates the view url for this entry to check for redirection later
|
||||||
// to avoid redirecting to the deleted entry. Ugh.
|
// to avoid redirecting to the deleted entry. Ugh.
|
||||||
$url = $this->generateUrl(
|
$url = $this->generateUrl(
|
||||||
|
@ -526,13 +538,12 @@ class EntryController extends AbstractController
|
||||||
* Get public URL for entry (and generate it if necessary).
|
* Get public URL for entry (and generate it if necessary).
|
||||||
*
|
*
|
||||||
* @Route("/share/{id}", requirements={"id" = "\d+"}, name="share")
|
* @Route("/share/{id}", requirements={"id" = "\d+"}, name="share")
|
||||||
|
* @IsGranted("SHARE", subject="entry")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function shareAction(Entry $entry)
|
public function shareAction(Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
if (null === $entry->getUid()) {
|
if (null === $entry->getUid()) {
|
||||||
$entry->generateUid();
|
$entry->generateUid();
|
||||||
|
|
||||||
|
@ -549,13 +560,12 @@ class EntryController extends AbstractController
|
||||||
* Disable public sharing for an entry.
|
* Disable public sharing for an entry.
|
||||||
*
|
*
|
||||||
* @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share")
|
* @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share")
|
||||||
|
* @IsGranted("UNSHARE", subject="entry")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function deleteShareAction(Entry $entry)
|
public function deleteShareAction(Entry $entry)
|
||||||
{
|
{
|
||||||
$this->checkUserAction($entry);
|
|
||||||
|
|
||||||
$entry->cleanUid();
|
$entry->cleanUid();
|
||||||
|
|
||||||
$this->entityManager->persist($entry);
|
$this->entityManager->persist($entry);
|
||||||
|
@ -571,6 +581,7 @@ class EntryController extends AbstractController
|
||||||
*
|
*
|
||||||
* @Route("/share/{uid}", requirements={"uid" = ".+"}, name="share_entry")
|
* @Route("/share/{uid}", requirements={"uid" = ".+"}, name="share_entry")
|
||||||
* @Cache(maxage="25200", smaxage="25200", public=true)
|
* @Cache(maxage="25200", smaxage="25200", public=true)
|
||||||
|
* @IsGranted("PUBLIC_ACCESS")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -592,6 +603,7 @@ class EntryController extends AbstractController
|
||||||
* @param int $page
|
* @param int $page
|
||||||
*
|
*
|
||||||
* @Route("/domain/{id}/{page}", requirements={"id" = ".+"}, defaults={"page" = 1}, name="same_domain")
|
* @Route("/domain/{id}/{page}", requirements={"id" = ".+"}, defaults={"page" = 1}, name="same_domain")
|
||||||
|
* @IsGranted("LIST_ENTRIES")
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
@ -713,16 +725,6 @@ class EntryController extends AbstractController
|
||||||
$this->addFlash('notice', $message);
|
$this->addFlash('notice', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for existing entry, if it exists, redirect to it with a message.
|
* Check for existing entry, if it exists, redirect to it with a message.
|
||||||
*
|
*
|
||||||
|
|
58
src/Security/Voter/EntryVoter.php
Normal file
58
src/Security/Voter/EntryVoter.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Wallabag\Security\Voter;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Wallabag\Entity\Entry;
|
||||||
|
use Wallabag\Entity\User;
|
||||||
|
|
||||||
|
class EntryVoter extends Voter
|
||||||
|
{
|
||||||
|
public const VIEW = 'VIEW';
|
||||||
|
public const EDIT = 'EDIT';
|
||||||
|
public const RELOAD = 'RELOAD';
|
||||||
|
public const STAR = 'STAR';
|
||||||
|
public const ARCHIVE = 'ARCHIVE';
|
||||||
|
public const SHARE = 'SHARE';
|
||||||
|
public const UNSHARE = 'UNSHARE';
|
||||||
|
public const DELETE = 'DELETE';
|
||||||
|
|
||||||
|
protected function supports(string $attribute, $subject): bool
|
||||||
|
{
|
||||||
|
if (!$subject instanceof Entry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\in_array($attribute, [self::VIEW, self::EDIT, self::RELOAD, self::STAR, self::ARCHIVE, self::SHARE, self::UNSHARE, self::DELETE], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
\assert($subject instanceof Entry);
|
||||||
|
|
||||||
|
$user = $token->getUser();
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($attribute) {
|
||||||
|
case self::VIEW:
|
||||||
|
case self::EDIT:
|
||||||
|
case self::RELOAD:
|
||||||
|
case self::STAR:
|
||||||
|
case self::ARCHIVE:
|
||||||
|
case self::SHARE:
|
||||||
|
case self::UNSHARE:
|
||||||
|
case self::DELETE:
|
||||||
|
return $user === $subject->getUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
46
src/Security/Voter/MainVoter.php
Normal file
46
src/Security/Voter/MainVoter.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
class MainVoter extends Voter
|
||||||
|
{
|
||||||
|
public const LIST_ENTRIES = 'LIST_ENTRIES';
|
||||||
|
public const CREATE_ENTRIES = 'CREATE_ENTRIES';
|
||||||
|
public const EDIT_ENTRIES = 'EDIT_ENTRIES';
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(Security $security)
|
||||||
|
{
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function supports(string $attribute, $subject): bool
|
||||||
|
{
|
||||||
|
if (null !== $subject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\in_array($attribute, [self::LIST_ENTRIES, self::CREATE_ENTRIES, self::EDIT_ENTRIES], true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
switch ($attribute) {
|
||||||
|
case self::LIST_ENTRIES:
|
||||||
|
case self::CREATE_ENTRIES:
|
||||||
|
case self::EDIT_ENTRIES:
|
||||||
|
return $this->security->isGranted('ROLE_USER');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,13 @@
|
||||||
{% if withPreview is defined %}
|
{% if withPreview is defined %}
|
||||||
<i class="grey-text text-darken-4 activator material-icons right">more_vert</i>
|
<i class="grey-text text-darken-4 activator material-icons right">more_vert</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if is_granted('VIEW', entry) %}
|
||||||
<a href="{{ path('view', {'id': entry.id}) }}" title="{{ entry.title|striptags|e('html_attr') }}" class="card-title dot-ellipsis dot-resize-update">
|
<a href="{{ path('view', {'id': entry.id}) }}" title="{{ entry.title|striptags|e('html_attr') }}" class="card-title dot-ellipsis dot-resize-update">
|
||||||
{{ entry.title|striptags|u.truncate(80, '…', false)|default('entry.default_title'|trans)|raw }}
|
{{ entry.title|striptags|u.truncate(80, '…', false)|default('entry.default_title'|trans)|raw }}
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ entry.title|striptags|u.truncate(80, '…', false)|default('entry.default_title'|trans)|raw }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="{{ subClass|default('original grey-text') }}">
|
<div class="{{ subClass|default('original grey-text') }}">
|
||||||
<a href="{{ entry.url|e }}" target="_blank" title="{{ entry.domainName|removeWww }}" class="tool grey-text">{{ entry.domainName|removeWww }}</a>
|
<a href="{{ entry.url|e }}" target="_blank" title="{{ entry.domainName|removeWww }}" class="tool grey-text">{{ entry.domainName|removeWww }}</a>
|
||||||
|
|
|
@ -10,17 +10,25 @@
|
||||||
{% set current_path = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
|
{% set current_path = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
|
||||||
|
|
||||||
<ul class="tools right">
|
<ul class="tools right">
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<li>
|
<li>
|
||||||
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', {'id': entry.id, redirect: current_path}) }}" data-action="same_domain" data-entry-id="{{ entry.id }}"><i class="material-icons">language</i></a>
|
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', {'id': entry.id, redirect: current_path}) }}" data-action="same_domain" data-entry-id="{{ entry.id }}"><i class="material-icons">language</i></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('ARCHIVE', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" data-action="archived" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" data-action="archived" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('STAR', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" data-action="star" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" data-action="star" data-entry-id="{{ entry.id }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('DELETE', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" data-action-confirm="{{ 'entry.confirm.delete'|trans }}" class="tool grey-text delete" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}" data-action="delete" data-entry-id="{{ entry.id }}"><i class="material-icons">delete</i></a>
|
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" data-action-confirm="{{ 'entry.confirm.delete'|trans }}" class="tool grey-text delete" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}" data-action="delete" data-entry-id="{{ entry.id }}"><i class="material-icons">delete</i></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,9 +7,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% if app.user.config.displayThumbnails %}
|
{% if app.user.config.displayThumbnails %}
|
||||||
|
{% if is_granted('VIEW', entry) %}
|
||||||
<a href="{{ path('view', {'id': entry.id}) }}">
|
<a href="{{ path('view', {'id': entry.id}) }}">
|
||||||
<span class="preview" style="background-image: url({{ entry.previewPicture }})"></span>
|
<span class="preview" style="background-image: url({{ entry.previewPicture }})"></span>
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="preview" style="background-image: url({{ entry.previewPicture }})"></span>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include "Entry/Card/_content.html.twig" with {'entry': entry} only %}
|
{% include "Entry/Card/_content.html.twig" with {'entry': entry} only %}
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
{% include "Entry/Card/_mass_checkbox.html.twig" with {'entry': entry} only %}
|
{% include "Entry/Card/_mass_checkbox.html.twig" with {'entry': entry} only %}
|
||||||
{% if app.user.config.displayThumbnails %}
|
{% if app.user.config.displayThumbnails %}
|
||||||
<div class="card-preview">
|
<div class="card-preview">
|
||||||
<a href="{{ path('view', {'id': entry.id}) }}">
|
|
||||||
{% set preview_class_modifier = entry.previewPicture ? '' : ' preview--default' %}
|
{% set preview_class_modifier = entry.previewPicture ? '' : ' preview--default' %}
|
||||||
|
{% if is_granted('VIEW', entry) %}
|
||||||
|
<a href="{{ path('view', {'id': entry.id}) }}">
|
||||||
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
|
{% include "Entry/Card/_content.html.twig" with {'entry': entry, 'withMetadata': true, 'subClass': 'metadata'} only %}
|
||||||
|
@ -14,10 +18,18 @@
|
||||||
|
|
||||||
<ul class="tools-list hide-on-small-only">
|
<ul class="tools-list hide-on-small-only">
|
||||||
<li>
|
<li>
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">language</i></a>
|
<a title="{{ 'entry.list.show_same_domain'|trans }}" class="tool grey-text" href="{{ path('same_domain', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">language</i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('ARCHIVE', entry) %}
|
||||||
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('STAR', entry) %}
|
||||||
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
<a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('DELETE', entry) %}
|
||||||
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">delete</i></a>
|
<a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">delete</i></a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,10 +8,14 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% if app.user.config.displayThumbnails %}
|
{% if app.user.config.displayThumbnails %}
|
||||||
<a href="{{ path('view', {'id': entry.id}) }}">
|
|
||||||
{% set preview_class_modifier = entry.previewPicture ? '' : ' preview--default' %}
|
{% set preview_class_modifier = entry.previewPicture ? '' : ' preview--default' %}
|
||||||
|
{% if is_granted('VIEW', entry) %}
|
||||||
|
<a href="{{ path('view', {'id': entry.id}) }}">
|
||||||
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="preview{{ preview_class_modifier }}" style="background-image: url({{ entry.previewPicture|default(asset('img/logo-square.svg')) }})"></span>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% include "Entry/Card/_content.html.twig" with {'entry': entry, 'withPreview': true} only %}
|
{% include "Entry/Card/_content.html.twig" with {'entry': entry, 'withPreview': true} only %}
|
||||||
|
@ -20,9 +24,13 @@
|
||||||
<div class="card-reveal">
|
<div class="card-reveal">
|
||||||
<i class="card-title activator grey-text text-darken-4 material-icons right">clear</i>
|
<i class="card-title activator grey-text text-darken-4 material-icons right">clear</i>
|
||||||
<span class="card-title">
|
<span class="card-title">
|
||||||
|
{% if is_granted('VIEW', entry) %}
|
||||||
<a href="{{ path('view', {'id': entry.id}) }}" title="{{ entry.title|striptags|e('html_attr') }}">
|
<a href="{{ path('view', {'id': entry.id}) }}" title="{{ entry.title|striptags|e('html_attr') }}">
|
||||||
{{ entry.title|striptags|u.truncate(80, '…', false)|raw }}
|
{{ entry.title|striptags|u.truncate(80, '…', false)|raw }}
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ entry.title|striptags|u.truncate(80, '…', false)|raw }}
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p>{{ entry.content|striptags|slice(0, 250)|raw }}…</p>
|
<p>{{ entry.content|striptags|slice(0, 250)|raw }}…</p>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
{% if entries.count > 0 %}
|
{% if entries.count > 0 %}
|
||||||
<a class="results-item" href="{{ path('switch_view_mode', {redirect: current_path}) }}"><i class="material-icons">{% if list_mode == 0 %}view_list{% else %}view_module{% endif %}</i></a>
|
<a class="results-item" href="{{ path('switch_view_mode', {redirect: current_path}) }}"><i class="material-icons">{% if list_mode == 0 %}view_list{% else %}view_module{% endif %}</i></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if entries.count > 0 %}
|
{% if entries.count > 0 and is_granted('EDIT_ENTRIES') %}
|
||||||
<label for="mass-action-inputs-displayed" class="mass-action-toggle results-item tooltipped" data-position="right" data-delay="50" data-tooltip="{{ 'entry.list.toggle_mass_action'|trans }}"><i class="material-icons">library_add_check</i></label>
|
<label for="mass-action-inputs-displayed" class="mass-action-toggle results-item tooltipped" data-position="right" data-delay="50" data-tooltip="{{ 'entry.list.toggle_mass_action'|trans }}"><i class="material-icons">library_add_check</i></label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if app.user.config.feedToken %}
|
{% if app.user.config.feedToken %}
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
{% if form is not null %}
|
{% if form is not null and is_granted('LIST_ENTRIES') %}
|
||||||
<div id="filters" class="side-nav right-aligned">
|
<div id="filters" class="side-nav right-aligned">
|
||||||
<form action="{{ path('all') }}">
|
<form action="{{ path('all') }}">
|
||||||
|
|
||||||
|
|
|
@ -25,16 +25,20 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="right">
|
<ul class="right">
|
||||||
|
{% if is_granted('ARCHIVE', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a class="waves-effect" title="{{ 'entry.view.left_menu.set_as_read'|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead">
|
<a class="waves-effect" title="{{ 'entry.view.left_menu.set_as_read'|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead">
|
||||||
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('STAR', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a class="waves-effect" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav">
|
<a class="waves-effect" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav">
|
||||||
<i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
|
<i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -55,6 +59,7 @@
|
||||||
<div class="collapsible-body"></div>
|
<div class="collapsible-body"></div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{% if is_granted('RELOAD', entry) %}
|
||||||
<li class="bold">
|
<li class="bold">
|
||||||
<a class="waves-effect collapsible-header" onclick="return confirm('{{ 'entry.confirm.reload'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.re_fetch_content'|trans }}" href="{{ path('reload_entry', {'id': entry.id}) }}" id="reload">
|
<a class="waves-effect collapsible-header" onclick="return confirm('{{ 'entry.confirm.reload'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.re_fetch_content'|trans }}" href="{{ path('reload_entry', {'id': entry.id}) }}" id="reload">
|
||||||
<i class="material-icons small">refresh</i>
|
<i class="material-icons small">refresh</i>
|
||||||
|
@ -62,12 +67,14 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="collapsible-body"></div>
|
<div class="collapsible-body"></div>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% set mark_as_read_label = 'entry.view.left_menu.set_as_unread' %}
|
{% set mark_as_read_label = 'entry.view.left_menu.set_as_unread' %}
|
||||||
{% if entry.isArchived == 0 %}
|
{% if entry.isArchived == 0 %}
|
||||||
{% set mark_as_read_label = 'entry.view.left_menu.set_as_read' %}
|
{% set mark_as_read_label = 'entry.view.left_menu.set_as_read' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('ARCHIVE', entry) %}
|
||||||
<li class="bold hide-on-med-and-down">
|
<li class="bold hide-on-med-and-down">
|
||||||
<a class="waves-effect collapsible-header markasread" title="{{ mark_as_read_label|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead">
|
<a class="waves-effect collapsible-header markasread" title="{{ mark_as_read_label|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead">
|
||||||
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||||
|
@ -75,7 +82,9 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="collapsible-body"></div>
|
<div class="collapsible-body"></div>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('STAR', entry) %}
|
||||||
<li class="bold hide-on-med-and-down">
|
<li class="bold hide-on-med-and-down">
|
||||||
<a class="waves-effect collapsible-header favorite" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav">
|
<a class="waves-effect collapsible-header favorite" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav">
|
||||||
<i class="material-icons spall">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
|
<i class="material-icons spall">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
|
||||||
|
@ -83,6 +92,9 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="collapsible-body"></div>
|
<div class="collapsible-body"></div>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('DELETE', entry) %}
|
||||||
<li class="bold border-bottom">
|
<li class="bold border-bottom">
|
||||||
<a class="waves-effect collapsible-header delete" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}">
|
<a class="waves-effect collapsible-header delete" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}">
|
||||||
<i class="material-icons small">delete</i>
|
<i class="material-icons small">delete</i>
|
||||||
|
@ -90,6 +102,7 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="collapsible-body"></div>
|
<div class="collapsible-body"></div>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li class="bold border-bottom">
|
<li class="bold border-bottom">
|
||||||
<a class="waves-effect collapsible-header" id="nav-btn-add-tag">
|
<a class="waves-effect collapsible-header" id="nav-btn-add-tag">
|
||||||
|
@ -134,17 +147,21 @@
|
||||||
<div class="collapsible-body">
|
<div class="collapsible-body">
|
||||||
<ul>
|
<ul>
|
||||||
{% if craue_setting('share_public') %}
|
{% if craue_setting('share_public') %}
|
||||||
|
{% if is_granted('SHARE', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('share', {'id': entry.id}) }}" target="_blank" title="{{ 'entry.view.left_menu.public_link'|trans }}" class="tool icon-eye">
|
<a href="{{ path('share', {'id': entry.id}) }}" target="_blank" title="{{ 'entry.view.left_menu.public_link'|trans }}" class="tool icon-eye">
|
||||||
<span>{{ 'entry.view.left_menu.public_link'|trans }}</span>
|
<span>{{ 'entry.view.left_menu.public_link'|trans }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('UNSHARE', entry) %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('delete_share', {'id': entry.id}) }}" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}" class="tool icon-no-eye">
|
<a href="{{ path('delete_share', {'id': entry.id}) }}" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}" class="tool icon-no-eye">
|
||||||
<span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span>
|
<span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% if craue_setting('share_twitter') %}
|
{% if craue_setting('share_twitter') %}
|
||||||
<li>
|
<li>
|
||||||
<a href="https://twitter.com/share?text={{ entry.title|striptags|url_encode }}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool icon-twitter" title="twitter">
|
<a href="https://twitter.com/share?text={{ entry.title|striptags|url_encode }}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" rel="noopener" class="tool icon-twitter" title="twitter">
|
||||||
|
@ -196,12 +213,14 @@
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<li class="bold border-top border-bottom">
|
<li class="bold border-top border-bottom">
|
||||||
<a class="waves-effect collapsible-header" title="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': 'all'}) }}">
|
<a class="waves-effect collapsible-header" title="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': 'all'}) }}">
|
||||||
<i class="material-icons small">casino</i>
|
<i class="material-icons small">casino</i>
|
||||||
<span>{{ 'menu.top.random_entry'|trans }}</span>
|
<span>{{ 'menu.top.random_entry'|trans }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li class="bold">
|
<li class="bold">
|
||||||
<a class="waves-effect collapsible-header">
|
<a class="waves-effect collapsible-header">
|
||||||
|
@ -235,7 +254,12 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="article" class="article">
|
<div id="article" class="article">
|
||||||
<header class="mbm">
|
<header class="mbm">
|
||||||
<h1><span{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>{{ entry.title|striptags|default('entry.default_title'|trans)|raw }}</span> <a class="title-edit" href="{{ path('edit', {'id': entry.id}) }}" title="{{ 'entry.view.edit_title'|trans }}"><i class="material-icons grey-text">create</i></a></h1>
|
<h1>
|
||||||
|
<span{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>{{ entry.title|striptags|default('entry.default_title'|trans)|raw }}</span>
|
||||||
|
{% if is_granted('EDIT', entry) %}
|
||||||
|
<a class="title-edit" href="{{ path('edit', {'id': entry.id}) }}" title="{{ 'entry.view.edit_title'|trans }}"><i class="material-icons grey-text">create</i></a>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
<aside class="entry-info">
|
<aside class="entry-info">
|
||||||
<div class="tools entry-tools grey-text" dir="auto">
|
<div class="tools entry-tools grey-text" dir="auto">
|
||||||
|
@ -299,9 +323,15 @@
|
||||||
<i class="material-icons">menu</i>
|
<i class="material-icons">menu</i>
|
||||||
</a>
|
</a>
|
||||||
<ul>
|
<ul>
|
||||||
|
{% if is_granted('ARCHIVE', entry) %}
|
||||||
<li><a class="btn-floating" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a></li>
|
<li><a class="btn-floating" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i></a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('STAR', entry) %}
|
||||||
<li><a class="btn-floating" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i></a></li>
|
<li><a class="btn-floating" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i></a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('DELETE', entry) %}
|
||||||
<li><a class="btn-floating" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')"><i class="material-icons">delete</i></a></li>
|
<li><a class="btn-floating" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')"><i class="material-icons">delete</i></a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,9 +21,11 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
|
{% if is_granted('CREATE_ENTRIES') %}
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<a href="{{ path('new') }}">{{ 'howto.form.description'|trans }}</a>
|
<a href="{{ path('new') }}">{{ 'howto.form.description'|trans }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h5>{{ 'howto.top_menu.browser_addons'|trans }}</h5>
|
<h5>{{ 'howto.top_menu.browser_addons'|trans }}</h5>
|
||||||
|
@ -43,12 +45,13 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if is_granted('CREATE_ENTRIES') %}
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h5>{{ 'howto.top_menu.bookmarklet'|trans }}</h5>
|
<h5>{{ 'howto.top_menu.bookmarklet'|trans }}</h5>
|
||||||
{{ 'howto.bookmarklet.description'|trans }}
|
{{ 'howto.bookmarklet.description'|trans }}
|
||||||
{% include 'Static/_bookmarklet.html.twig' %}
|
{% include 'Static/_bookmarklet.html.twig' %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,9 @@
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://doc.wallabag.org/en/user/articles/save.html">{{ 'quickstart.first_steps.new_article'|trans }}</a></li>
|
<li><a href="https://doc.wallabag.org/en/user/articles/save.html">{{ 'quickstart.first_steps.new_article'|trans }}</a></li>
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<li><a href="{{ path('unread') }}">{{ 'quickstart.first_steps.unread_articles'|trans }}</a></li>
|
<li><a href="{{ path('unread') }}">{{ 'quickstart.first_steps.unread_articles'|trans }}</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,9 +11,11 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ul class="card-tag-labels">
|
<ul class="card-tag-labels">
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<li class="chip">
|
<li class="chip">
|
||||||
<a href="{{ path('untagged') }}">{{ 'tag.list.untagged'|trans }} ({{ nbEntriesUntagged }})</a>
|
<a href="{{ path('untagged') }}">{{ 'tag.list.untagged'|trans }} ({{ nbEntriesUntagged }})</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li title="{{ tag.label }} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}" class="chip">
|
<li title="{{ tag.label }} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}" class="chip">
|
||||||
<a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-handle="tag-link">
|
<a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-handle="tag-link">
|
||||||
|
|
|
@ -30,9 +30,13 @@
|
||||||
<ul id="slide-out" class="left-bar side-nav fixed">
|
<ul id="slide-out" class="left-bar side-nav fixed">
|
||||||
{% block logo %}
|
{% block logo %}
|
||||||
<li class="logo border-bottom">
|
<li class="logo border-bottom">
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<a title="{{ 'menu.left.back_to_unread'|trans }}" href="{{ path('unread') }}">
|
<a title="{{ 'menu.left.back_to_unread'|trans }}" href="{{ path('unread') }}">
|
||||||
<img src="{{ asset('img/logo-square.svg') }}" alt="wallabag logo" />
|
<img src="{{ asset('img/logo-square.svg') }}" alt="wallabag logo" />
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<img src="{{ asset('img/logo-square.svg') }}" alt="wallabag logo"/>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -54,6 +58,7 @@
|
||||||
{% set active_route = 'untagged' %}
|
{% set active_route = 'untagged' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
<li class="bold {% if active_route == 'unread' %}active{% endif %}">
|
<li class="bold {% if active_route == 'unread' %}active{% endif %}">
|
||||||
<a class="waves-effect" href="{{ path('unread') }}">{{ 'menu.left.unread'|trans }} <span class="items-number grey-text">{{ count_entries('unread') }}</span></a>
|
<a class="waves-effect" href="{{ path('unread') }}">{{ 'menu.left.unread'|trans }} <span class="items-number grey-text">{{ count_entries('unread') }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -69,6 +74,7 @@
|
||||||
<li class="bold {% if active_route == 'all' %}active{% endif %}">
|
<li class="bold {% if active_route == 'all' %}active{% endif %}">
|
||||||
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="items-number grey-text">{{ count_entries('all') }}</span></a>
|
<a class="waves-effect" href="{{ path('all') }}">{{ 'menu.left.all_articles'|trans }} <span class="items-number grey-text">{{ count_entries('all') }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="bold {% if current_route == 'tags' %}active{% 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>
|
<a class="waves-effect" href="{{ path('tag') }}">{{ 'menu.left.tags'|trans }} <span class="items-number grey-text">{{ count_tags() }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -93,7 +99,7 @@
|
||||||
<i class="material-icons">search</i>
|
<i class="material-icons">search</i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if active_route %}
|
{% if active_route and is_granted('LIST_ENTRIES') %}
|
||||||
<li id="button_random">
|
<li id="button_random">
|
||||||
<a class="waves-effect tooltipped js-random-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': active_route}) }}">
|
<a class="waves-effect tooltipped js-random-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': active_route}) }}">
|
||||||
<i class="material-icons">casino</i>
|
<i class="material-icons">casino</i>
|
||||||
|
@ -165,8 +171,12 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{% if is_granted('LIST_ENTRIES') %}
|
||||||
{{ render(controller('Wallabag\\Controller\\EntryController::searchFormAction', {'currentRoute': current_route})) }}
|
{{ render(controller('Wallabag\\Controller\\EntryController::searchFormAction', {'currentRoute': current_route})) }}
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CREATE_ENTRIES') %}
|
||||||
{{ render(controller('Wallabag\\Controller\\EntryController::addEntryFormAction')) }}
|
{{ render(controller('Wallabag\\Controller\\EntryController::addEntryFormAction')) }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
149
tests/Security/Voter/EntryVoterTest.php
Normal file
149
tests/Security/Voter/EntryVoterTest.php
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Security\Voter;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
|
||||||
|
use Wallabag\Entity\Entry;
|
||||||
|
use Wallabag\Entity\User;
|
||||||
|
use Wallabag\Security\Voter\EntryVoter;
|
||||||
|
|
||||||
|
class EntryVoterTest extends TestCase
|
||||||
|
{
|
||||||
|
private $token;
|
||||||
|
private $user;
|
||||||
|
private $entry;
|
||||||
|
private $entryVoter;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->token = $this->createMock(TokenInterface::class);
|
||||||
|
$this->user = new User();
|
||||||
|
$this->entry = new Entry($this->user);
|
||||||
|
|
||||||
|
$this->entryVoter = new EntryVoter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsAbstainForInvalidSubject(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->entryVoter->vote($this->token, new \stdClass(), [EntryVoter::EDIT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsAbstainForInvalidAttribute(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->entryVoter->vote($this->token, $this->entry, ['INVALID']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserView(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::VIEW]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserView(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::VIEW]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserEdit(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::EDIT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserEdit(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::EDIT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserReload(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::RELOAD]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserReload(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::RELOAD]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserStar(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::STAR]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserStar(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::STAR]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserArchive(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::ARCHIVE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserArchive(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::ARCHIVE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserShare(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::SHARE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserShare(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::SHARE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserUnshare(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::UNSHARE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserUnshare(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::UNSHARE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonEntryUserDelete(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::DELETE]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForEntryUserDelete(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn($this->user);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->entryVoter->vote($this->token, $this->entry, [EntryVoter::DELETE]));
|
||||||
|
}
|
||||||
|
}
|
86
tests/Security/Voter/MainVoterTest.php
Normal file
86
tests/Security/Voter/MainVoterTest.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace 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\User;
|
||||||
|
use Wallabag\Security\Voter\MainVoter;
|
||||||
|
|
||||||
|
class MainVoterTest extends TestCase
|
||||||
|
{
|
||||||
|
private $security;
|
||||||
|
private $token;
|
||||||
|
private $mainVoter;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$this->token = $this->createMock(TokenInterface::class);
|
||||||
|
$this->token->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$this->mainVoter = new MainVoter($this->security);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsAbstainForInvalidAttribute(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->mainVoter->vote($this->token, null, ['INVALID']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsAbstainForInvalidSubject(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->mainVoter->vote($this->token, new \stdClass(), [MainVoter::LIST_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForInvalidUser(): void
|
||||||
|
{
|
||||||
|
$this->token->method('getUser')->willReturn(new \stdClass());
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::LIST_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonUserListEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::LIST_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForUserListEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::LIST_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonUserCreateEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForUserCreateEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsDeniedForNonUserEditEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::EDIT_ENTRIES]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVoteReturnsGrantedForUserEditEntries(): void
|
||||||
|
{
|
||||||
|
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::EDIT_ENTRIES]));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue