Skip to content

Commit

Permalink
Merge pull request #7375 from yguedidi/use-isgranted-in-entrycontroller
Browse files Browse the repository at this point in the history
Use IsGranted in EntryController
  • Loading branch information
yguedidi committed Apr 4, 2024
2 parents 92786c6 + e66ea21 commit 96cb024
Show file tree
Hide file tree
Showing 16 changed files with 575 additions and 151 deletions.
58 changes: 30 additions & 28 deletions src/Controller/EntryController.php
Expand Up @@ -8,12 +8,14 @@
use Pagerfanta\Doctrine\ORM\QueryAdapter as DoctrineORMAdapter;
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Spiriit\Bundle\FormFilterBundle\Filter\FilterBuilderUpdaterInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
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;
Expand All @@ -38,8 +40,9 @@ class EntryController extends AbstractController
private PreparePagerForEntries $preparePagerForEntriesHelper;
private FilterBuilderUpdaterInterface $filterBuilderUpdater;
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->eventDispatcher = $eventDispatcher;
Expand All @@ -48,10 +51,12 @@ public function __construct(EntityManagerInterface $entityManager, EventDispatch
$this->preparePagerForEntriesHelper = $preparePagerForEntriesHelper;
$this->filterBuilderUpdater = $filterBuilderUpdater;
$this->contentProxy = $contentProxy;
$this->security = $security;
}

/**
* @Route("/mass", name="mass_action")
* @IsGranted("EDIT_ENTRIES")
*
* @return Response
*/
Expand Down Expand Up @@ -104,7 +109,9 @@ function ($v) {
/** @var Entry * */
$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) {
$entry->toggleArchive();
Expand Down Expand Up @@ -135,6 +142,7 @@ function ($v) {
* @param int $page
*
* @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)
* because this controller is also called inside the layout template without any page as argument
Expand Down Expand Up @@ -164,6 +172,7 @@ public function searchFormAction(Request $request, $page = 1, $currentRoute = nu

/**
* @Route("/new-entry", name="new_entry")
* @IsGranted("CREATE_ENTRIES")
*
* @return Response
*/
Expand Down Expand Up @@ -205,6 +214,7 @@ public function addEntryFormAction(Request $request, TranslatorInterface $transl

/**
* @Route("/bookmarklet", name="bookmarklet")
* @IsGranted("CREATE_ENTRIES")
*
* @return Response
*/
Expand All @@ -228,6 +238,7 @@ public function addEntryViaBookmarkletAction(Request $request)

/**
* @Route("/new", name="new")
* @IsGranted("CREATE_ENTRIES")
*
* @return Response
*/
Expand All @@ -240,13 +251,12 @@ public function addEntryAction()
* Edit an entry content.
*
* @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
* @IsGranted("EDIT", subject="entry")
*
* @return Response
*/
public function editEntryAction(Request $request, Entry $entry)
{
$this->checkUserAction($entry);

$form = $this->createForm(EditEntryType::class, $entry);

$form->handleRequest($request);
Expand Down Expand Up @@ -274,6 +284,7 @@ public function editEntryAction(Request $request, Entry $entry)
* @param int $page
*
* @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -288,6 +299,7 @@ public function showAllAction(Request $request, $page)
* @param int $page
*
* @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -307,6 +319,7 @@ public function showUnreadAction(Request $request, $page)
* @param int $page
*
* @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -321,6 +334,7 @@ public function showArchiveAction(Request $request, $page)
* @param int $page
*
* @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -335,6 +349,7 @@ public function showStarredAction(Request $request, $page)
* @param int $page
*
* @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -349,6 +364,7 @@ public function showUntaggedEntriesAction(Request $request, $page)
* @param int $page
*
* @Route("/annotated/list/{page}", name="annotated", defaults={"page" = "1"})
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand All @@ -361,6 +377,7 @@ public function showWithAnnotationsEntriesAction(Request $request, $page)
* Shows random entry depending on the given type.
*
* @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|annotated|all"})
* @IsGranted("LIST_ENTRIES")
*
* @return RedirectResponse
*/
Expand All @@ -382,13 +399,12 @@ public function redirectRandomEntryAction(string $type = 'all')
* Shows entry content.
*
* @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
* @IsGranted("VIEW", subject="entry")
*
* @return Response
*/
public function viewAction(Entry $entry)
{
$this->checkUserAction($entry);

return $this->render(
'Entry/entry.html.twig',
['entry' => $entry]
Expand All @@ -400,13 +416,12 @@ public function viewAction(Entry $entry)
* Refetch content from the website and make it readable again.
*
* @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry")
* @IsGranted("RELOAD", subject="entry")
*
* @return RedirectResponse
*/
public function reloadAction(Entry $entry)
{
$this->checkUserAction($entry);

$this->updateEntry($entry, 'entry_reloaded');

// if refreshing entry failed, don't save it
Expand All @@ -429,13 +444,12 @@ public function reloadAction(Entry $entry)
* Changes read status for an entry.
*
* @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
* @IsGranted("ARCHIVE", subject="entry")
*
* @return RedirectResponse
*/
public function toggleArchiveAction(Request $request, Entry $entry)
{
$this->checkUserAction($entry);

$entry->toggleArchive();
$this->entityManager->flush();

Expand All @@ -458,13 +472,12 @@ public function toggleArchiveAction(Request $request, Entry $entry)
* Changes starred status for an entry.
*
* @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
* @IsGranted("STAR", subject="entry")
*
* @return RedirectResponse
*/
public function toggleStarAction(Request $request, Entry $entry)
{
$this->checkUserAction($entry);

$entry->toggleStar();
$entry->updateStar($entry->isStarred());
$this->entityManager->flush();
Expand All @@ -488,13 +501,12 @@ public function toggleStarAction(Request $request, Entry $entry)
* Deletes entry and redirect to the homepage or the last viewed page.
*
* @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
* @IsGranted("DELETE", subject="entry")
*
* @return RedirectResponse
*/
public function deleteEntryAction(Request $request, Entry $entry)
{
$this->checkUserAction($entry);

// generates the view url for this entry to check for redirection later
// to avoid redirecting to the deleted entry. Ugh.
$url = $this->generateUrl(
Expand Down Expand Up @@ -526,13 +538,12 @@ public function deleteEntryAction(Request $request, Entry $entry)
* Get public URL for entry (and generate it if necessary).
*
* @Route("/share/{id}", requirements={"id" = "\d+"}, name="share")
* @IsGranted("SHARE", subject="entry")
*
* @return Response
*/
public function shareAction(Entry $entry)
{
$this->checkUserAction($entry);

if (null === $entry->getUid()) {
$entry->generateUid();

Expand All @@ -549,13 +560,12 @@ public function shareAction(Entry $entry)
* Disable public sharing for an entry.
*
* @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share")
* @IsGranted("UNSHARE", subject="entry")
*
* @return Response
*/
public function deleteShareAction(Entry $entry)
{
$this->checkUserAction($entry);

$entry->cleanUid();

$this->entityManager->persist($entry);
Expand All @@ -571,6 +581,7 @@ public function deleteShareAction(Entry $entry)
*
* @Route("/share/{uid}", requirements={"uid" = ".+"}, name="share_entry")
* @Cache(maxage="25200", smaxage="25200", public=true)
* @IsGranted("PUBLIC_ACCESS")
*
* @return Response
*/
Expand All @@ -592,6 +603,7 @@ public function shareEntryAction(Entry $entry, Config $craueConfig)
* @param int $page
*
* @Route("/domain/{id}/{page}", requirements={"id" = ".+"}, defaults={"page" = 1}, name="same_domain")
* @IsGranted("LIST_ENTRIES")
*
* @return Response
*/
Expand Down Expand Up @@ -713,16 +725,6 @@ private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
$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.
*
Expand Down
58 changes: 58 additions & 0 deletions src/Security/Voter/EntryVoter.php
@@ -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 changes: 46 additions & 0 deletions src/Security/Voter/MainVoter.php
@@ -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;
}
}

0 comments on commit 96cb024

Please sign in to comment.