1
0
Fork 0
mirror of https://github.com/wallabag/wallabag.git synced 2025-07-22 17:18:37 +00:00

Merge pull request #8094 from wallabag/add-isgranted-to-configcontroller

Add IsGranted to ConfigController
This commit is contained in:
Yassine Guedidi 2025-03-17 09:34:25 +01:00 committed by GitHub
commit fb11f5870e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 359 additions and 56 deletions

View file

@ -10,11 +10,6 @@ parameters:
count: 1 count: 1
path: src/Controller/AnnotationController.php path: src/Controller/AnnotationController.php
-
message: "#^Call to an undefined method Wallabag\\\\Entity\\\\RuleInterface\\:\\:getConfig\\(\\)\\.$#"
count: 1
path: src/Controller/ConfigController.php
- -
message: "#^Method FOS\\\\UserBundle\\\\Model\\\\UserManagerInterface\\:\\:updateUser\\(\\) invoked with 2 parameters, 1 required\\.$#" message: "#^Method FOS\\\\UserBundle\\\\Model\\\\UserManagerInterface\\:\\:updateUser\\(\\) invoked with 2 parameters, 1 required\\.$#"
count: 6 count: 6

View file

@ -10,6 +10,7 @@ use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder; use JMS\Serializer\SerializerBuilder;
use PragmaRX\Recovery\Recovery as BackupCodes; use PragmaRX\Recovery\Recovery as BackupCodes;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
@ -22,7 +23,6 @@ use Symfony\Component\Validator\Constraints\Locale as LocaleConstraint;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
use Wallabag\Entity\Config as ConfigEntity; use Wallabag\Entity\Config as ConfigEntity;
use Wallabag\Entity\IgnoreOriginUserRule; use Wallabag\Entity\IgnoreOriginUserRule;
use Wallabag\Entity\RuleInterface;
use Wallabag\Entity\TaggingRule; use Wallabag\Entity\TaggingRule;
use Wallabag\Event\ConfigUpdatedEvent; use Wallabag\Event\ConfigUpdatedEvent;
use Wallabag\Form\Type\ChangePasswordType; use Wallabag\Form\Type\ChangePasswordType;
@ -75,6 +75,7 @@ class ConfigController extends AbstractController
/** /**
* @Route("/config", name="config", methods={"GET", "POST"}) * @Route("/config", name="config", methods={"GET", "POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function indexAction(Request $request, Config $craueConfig, TaggingRuleRepository $taggingRuleRepository, IgnoreOriginUserRuleRepository $ignoreOriginUserRuleRepository, UserRepository $userRepository) public function indexAction(Request $request, Config $craueConfig, TaggingRuleRepository $taggingRuleRepository, IgnoreOriginUserRuleRepository $ignoreOriginUserRuleRepository, UserRepository $userRepository)
{ {
@ -265,6 +266,7 @@ class ConfigController extends AbstractController
* Disable 2FA using email. * Disable 2FA using email.
* *
* @Route("/config/otp/email/disable", name="disable_otp_email", methods={"POST"}) * @Route("/config/otp/email/disable", name="disable_otp_email", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function disableOtpEmailAction(Request $request) public function disableOtpEmailAction(Request $request)
{ {
@ -289,6 +291,7 @@ class ConfigController extends AbstractController
* Enable 2FA using email. * Enable 2FA using email.
* *
* @Route("/config/otp/email", name="config_otp_email", methods={"POST"}) * @Route("/config/otp/email", name="config_otp_email", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function otpEmailAction(Request $request) public function otpEmailAction(Request $request)
{ {
@ -316,6 +319,7 @@ class ConfigController extends AbstractController
* Disable 2FA using OTP app. * Disable 2FA using OTP app.
* *
* @Route("/config/otp/app/disable", name="disable_otp_app", methods={"POST"}) * @Route("/config/otp/app/disable", name="disable_otp_app", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function disableOtpAppAction(Request $request) public function disableOtpAppAction(Request $request)
{ {
@ -342,6 +346,7 @@ class ConfigController extends AbstractController
* Enable 2FA using OTP app, user will need to confirm the generated code from the app. * Enable 2FA using OTP app, user will need to confirm the generated code from the app.
* *
* @Route("/config/otp/app", name="config_otp_app", methods={"POST"}) * @Route("/config/otp/app", name="config_otp_app", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function otpAppAction(Request $request, GoogleAuthenticatorInterface $googleAuthenticator) public function otpAppAction(Request $request, GoogleAuthenticatorInterface $googleAuthenticator)
{ {
@ -383,6 +388,7 @@ class ConfigController extends AbstractController
* Cancelling 2FA using OTP app. * Cancelling 2FA using OTP app.
* *
* @Route("/config/otp/app/cancel", name="config_otp_app_cancel") * @Route("/config/otp/app/cancel", name="config_otp_app_cancel")
* @IsGranted("EDIT_CONFIG")
* *
* XXX: commented until we rewrite 2fa with a real two-steps activation * XXX: commented until we rewrite 2fa with a real two-steps activation
*/ */
@ -401,6 +407,7 @@ class ConfigController extends AbstractController
* Validate OTP code. * Validate OTP code.
* *
* @Route("/config/otp/app/check", name="config_otp_app_check", methods={"POST"}) * @Route("/config/otp/app/check", name="config_otp_app_check", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
*/ */
public function otpAppCheckAction(Request $request, GoogleAuthenticatorInterface $googleAuthenticator) public function otpAppCheckAction(Request $request, GoogleAuthenticatorInterface $googleAuthenticator)
{ {
@ -437,6 +444,7 @@ class ConfigController extends AbstractController
/** /**
* @Route("/generate-token", name="generate_token", methods={"GET"}) * @Route("/generate-token", name="generate_token", methods={"GET"})
* @IsGranted("EDIT_CONFIG")
* *
* @return RedirectResponse|JsonResponse * @return RedirectResponse|JsonResponse
*/ */
@ -462,6 +470,7 @@ class ConfigController extends AbstractController
/** /**
* @Route("/revoke-token", name="revoke_token", methods={"GET"}) * @Route("/revoke-token", name="revoke_token", methods={"GET"})
* @IsGranted("EDIT_CONFIG")
* *
* @return RedirectResponse|JsonResponse * @return RedirectResponse|JsonResponse
*/ */
@ -488,15 +497,14 @@ class ConfigController extends AbstractController
/** /**
* Deletes a tagging rule and redirect to the config homepage. * Deletes a tagging rule and redirect to the config homepage.
* *
* @Route("/tagging-rule/delete/{id}", name="delete_tagging_rule", methods={"GET"}, requirements={"id" = "\d+"}) * @Route("/tagging-rule/delete/{taggingRule}", name="delete_tagging_rule", methods={"GET"}, requirements={"taggingRule" = "\d+"})
* @IsGranted("DELETE", subject="taggingRule")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
public function deleteTaggingRuleAction(TaggingRule $rule) public function deleteTaggingRuleAction(TaggingRule $taggingRule)
{ {
$this->validateRuleAction($rule); $this->entityManager->remove($taggingRule);
$this->entityManager->remove($rule);
$this->entityManager->flush(); $this->entityManager->flush();
$this->addFlash( $this->addFlash(
@ -510,29 +518,27 @@ class ConfigController extends AbstractController
/** /**
* Edit a tagging rule. * Edit a tagging rule.
* *
* @Route("/tagging-rule/edit/{id}", name="edit_tagging_rule", methods={"GET"}, requirements={"id" = "\d+"}) * @Route("/tagging-rule/edit/{taggingRule}", name="edit_tagging_rule", methods={"GET"}, requirements={"taggingRule" = "\d+"})
* @IsGranted("EDIT", subject="taggingRule")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
public function editTaggingRuleAction(TaggingRule $rule) public function editTaggingRuleAction(TaggingRule $taggingRule)
{ {
$this->validateRuleAction($rule); return $this->redirect($this->generateUrl('config') . '?tagging-rule=' . $taggingRule->getId() . '#set5');
return $this->redirect($this->generateUrl('config') . '?tagging-rule=' . $rule->getId() . '#set5');
} }
/** /**
* Deletes an ignore origin rule and redirect to the config homepage. * Deletes an ignore origin rule and redirect to the config homepage.
* *
* @Route("/ignore-origin-user-rule/delete/{id}", name="delete_ignore_origin_rule", methods={"GET"}, requirements={"id" = "\d+"}) * @Route("/ignore-origin-user-rule/delete/{ignoreOriginUserRule}", name="delete_ignore_origin_rule", methods={"GET"}, requirements={"ignoreOriginUserRule" = "\d+"})
* @IsGranted("DELETE", subject="ignoreOriginUserRule")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
public function deleteIgnoreOriginRuleAction(IgnoreOriginUserRule $rule) public function deleteIgnoreOriginRuleAction(IgnoreOriginUserRule $ignoreOriginUserRule)
{ {
$this->validateRuleAction($rule); $this->entityManager->remove($ignoreOriginUserRule);
$this->entityManager->remove($rule);
$this->entityManager->flush(); $this->entityManager->flush();
$this->addFlash( $this->addFlash(
@ -546,21 +552,21 @@ class ConfigController extends AbstractController
/** /**
* Edit an ignore origin rule. * Edit an ignore origin rule.
* *
* @Route("/ignore-origin-user-rule/edit/{id}", name="edit_ignore_origin_rule", methods={"GET"}, requirements={"id" = "\d+"}) * @Route("/ignore-origin-user-rule/edit/{ignoreOriginUserRule}", name="edit_ignore_origin_rule", methods={"GET"}, requirements={"ignoreOriginUserRule" = "\d+"})
* @IsGranted("EDIT", subject="ignoreOriginUserRule")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
public function editIgnoreOriginRuleAction(IgnoreOriginUserRule $rule) public function editIgnoreOriginRuleAction(IgnoreOriginUserRule $ignoreOriginUserRule)
{ {
$this->validateRuleAction($rule); return $this->redirect($this->generateUrl('config') . '?ignore-origin-user-rule=' . $ignoreOriginUserRule->getId() . '#set6');
return $this->redirect($this->generateUrl('config') . '?ignore-origin-user-rule=' . $rule->getId() . '#set6');
} }
/** /**
* Remove all annotations OR tags OR entries for the current user. * Remove all annotations OR tags OR entries for the current user.
* *
* @Route("/reset/{type}", name="config_reset", methods={"POST"}, requirements={"id" = "annotations|tags|entries|tagging_rules"}) * @Route("/reset/{type}", name="config_reset", methods={"POST"}, requirements={"id" = "annotations|tags|entries|tagging_rules"})
* @IsGranted("EDIT_CONFIG")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
@ -616,6 +622,7 @@ class ConfigController extends AbstractController
* Delete account for current user. * Delete account for current user.
* *
* @Route("/account/delete", name="delete_account", methods={"POST"}) * @Route("/account/delete", name="delete_account", methods={"POST"})
* @IsGranted("EDIT_CONFIG")
* *
* @throws AccessDeniedHttpException * @throws AccessDeniedHttpException
* *
@ -648,6 +655,7 @@ class ConfigController extends AbstractController
* Switch view mode for current user. * Switch view mode for current user.
* *
* @Route("/config/view-mode", name="switch_view_mode", methods={"GET"}) * @Route("/config/view-mode", name="switch_view_mode", methods={"GET"})
* @IsGranted("EDIT_CONFIG")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
@ -670,6 +678,7 @@ class ConfigController extends AbstractController
* @param string $language * @param string $language
* *
* @Route("/locale/{language}", name="changeLocale", methods={"GET"}) * @Route("/locale/{language}", name="changeLocale", methods={"GET"})
* @IsGranted("PUBLIC_ACCESS")
* *
* @return RedirectResponse * @return RedirectResponse
*/ */
@ -688,6 +697,7 @@ class ConfigController extends AbstractController
* Export tagging rules for the logged in user. * Export tagging rules for the logged in user.
* *
* @Route("/tagging-rule/export", name="export_tagging_rule", methods={"GET"}) * @Route("/tagging-rule/export", name="export_tagging_rule", methods={"GET"})
* @IsGranted("EDIT_CONFIG")
* *
* @return Response * @return Response
*/ */
@ -768,16 +778,6 @@ class ConfigController extends AbstractController
$this->entityManager->flush(); $this->entityManager->flush();
} }
/**
* Validate that a rule can be edited/deleted by the current user.
*/
private function validateRuleAction(RuleInterface $rule)
{
if ($this->getUser()->getId() !== $rule->getConfig()->getUser()->getId()) {
throw $this->createAccessDeniedException('You can not access this rule.');
}
}
/** /**
* Retrieve config for the current user. * Retrieve config for the current user.
* If no config were found, create a new one. * If no config were found, create a new one.

View 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 Wallabag\Entity\IgnoreOriginUserRule;
use Wallabag\Entity\User;
class IgnoreOriginUserRuleVoter extends Voter
{
public const EDIT = 'EDIT';
public const DELETE = 'DELETE';
protected function supports(string $attribute, $subject): bool
{
if (!$subject instanceof IgnoreOriginUserRule) {
return false;
}
if (!\in_array($attribute, [self::EDIT, self::DELETE], true)) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
\assert($subject instanceof IgnoreOriginUserRule);
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
switch ($attribute) {
case self::EDIT:
case self::DELETE:
return $subject->getConfig()->getUser() === $user;
}
return false;
}
}

View file

@ -15,6 +15,7 @@ class MainVoter extends Voter
public const IMPORT_ENTRIES = 'IMPORT_ENTRIES'; public const IMPORT_ENTRIES = 'IMPORT_ENTRIES';
public const LIST_SITE_CREDENTIALS = 'LIST_SITE_CREDENTIALS'; public const LIST_SITE_CREDENTIALS = 'LIST_SITE_CREDENTIALS';
public const CREATE_SITE_CREDENTIALS = 'CREATE_SITE_CREDENTIALS'; public const CREATE_SITE_CREDENTIALS = 'CREATE_SITE_CREDENTIALS';
public const EDIT_CONFIG = 'EDIT_CONFIG';
private Security $security; private Security $security;
@ -29,7 +30,7 @@ class MainVoter extends Voter
return false; 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], true)) { 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)) {
return false; return false;
} }
@ -46,6 +47,7 @@ class MainVoter extends Voter
case self::IMPORT_ENTRIES: case self::IMPORT_ENTRIES:
case self::LIST_SITE_CREDENTIALS: case self::LIST_SITE_CREDENTIALS:
case self::CREATE_SITE_CREDENTIALS: case self::CREATE_SITE_CREDENTIALS:
case self::EDIT_CONFIG:
return $this->security->isGranted('ROLE_USER'); return $this->security->isGranted('ROLE_USER');
} }

View 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 Wallabag\Entity\TaggingRule;
use Wallabag\Entity\User;
class TaggingRuleVoter extends Voter
{
public const EDIT = 'EDIT';
public const DELETE = 'DELETE';
protected function supports(string $attribute, $subject): bool
{
if (!$subject instanceof TaggingRule) {
return false;
}
if (!\in_array($attribute, [self::EDIT, self::DELETE], true)) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
\assert($subject instanceof TaggingRule);
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
switch ($attribute) {
case self::EDIT:
case self::DELETE:
return $subject->getConfig()->getUser() === $user;
}
return false;
}
}

View file

@ -381,10 +381,10 @@
{{ 'config.form_rules.then_tag_as_label'|trans }} {{ 'config.form_rules.then_tag_as_label'|trans }}
« {{ tagging_rule.tags|join(', ') }} » « {{ tagging_rule.tags|join(', ') }} »
<a href="{{ path('edit_tagging_rule', {id: tagging_rule.id}) }}" title="{{ 'config.form_rules.edit_rule_label'|trans }}" class="mode_edit_tagging_rule"> <a href="{{ path('edit_tagging_rule', {taggingRule: tagging_rule.id}) }}" title="{{ 'config.form_rules.edit_rule_label'|trans }}" class="mode_edit_tagging_rule">
<i class="tool grey-text material-icons">mode_edit</i> <i class="tool grey-text material-icons">mode_edit</i>
</a> </a>
<a href="{{ path('delete_tagging_rule', {id: tagging_rule.id}) }}" title="{{ 'config.form_rules.delete_rule_label'|trans }}" class="delete_tagging_rule"> <a href="{{ path('delete_tagging_rule', {taggingRule: tagging_rule.id}) }}" title="{{ 'config.form_rules.delete_rule_label'|trans }}" class="delete_tagging_rule">
<i class="tool grey-text material-icons">delete</i> <i class="tool grey-text material-icons">delete</i>
</a> </a>
</li> </li>
@ -561,10 +561,10 @@
<li> <li>
{{ 'config.form_rules.if_label'|trans }} {{ 'config.form_rules.if_label'|trans }}
« {{ ignore_origin_rule.rule }} » « {{ ignore_origin_rule.rule }} »
<a href="{{ path('edit_ignore_origin_rule', {id: ignore_origin_rule.id}) }}" title="{{ 'config.form_rules.edit_rule_label'|trans }}" class="mode_edit"> <a href="{{ path('edit_ignore_origin_rule', {ignoreOriginUserRule: ignore_origin_rule.id}) }}" title="{{ 'config.form_rules.edit_rule_label'|trans }}" class="mode_edit">
<i class="tool grey-text material-icons">mode_edit</i> <i class="tool grey-text material-icons">mode_edit</i>
</a> </a>
<a href="{{ path('delete_ignore_origin_rule', {id: ignore_origin_rule.id}) }}" title="{{ 'config.form_rules.delete_rule_label'|trans }}" class="delete"> <a href="{{ path('delete_ignore_origin_rule', {ignoreOriginUserRule: ignore_origin_rule.id}) }}" title="{{ 'config.form_rules.delete_rule_label'|trans }}" class="delete">
<i class="tool grey-text material-icons">delete</i> <i class="tool grey-text material-icons">delete</i>
</a> </a>
</li> </li>

View file

@ -12,21 +12,23 @@
<h3>{{ 'quickstart.intro.title'|trans }}</h3> <h3>{{ 'quickstart.intro.title'|trans }}</h3>
<ul class="row data"> <ul class="row data">
<li class="col l4 m6 s12"> {% if is_granted('EDIT_CONFIG') %}
<div class="card teal darken-1"> <li class="col l4 m6 s12">
<div class="card-content white-text"> <div class="card teal darken-1">
<span class="card-title white-text">{{ 'quickstart.configure.title'|trans }}</span> <div class="card-content white-text">
<p>{{ 'quickstart.configure.description'|trans }}</p> <span class="card-title white-text">{{ 'quickstart.configure.title'|trans }}</span>
<p>{{ 'quickstart.configure.description'|trans }}</p>
</div>
<div class="card-action">
<ul>
<li><a href="{{ path('config') }}">{{ 'quickstart.configure.language'|trans }}</a></li>
<li><a href="{{ path('config') }}#set2">{{ 'quickstart.configure.feed'|trans }}</a></li>
<li><a href="{{ path('config') }}#set5">{{ 'quickstart.more'|trans }}</a></li>
</ul>
</div>
</div> </div>
<div class="card-action"> </li>
<ul> {% endif %}
<li><a href="{{ path('config') }}">{{ 'quickstart.configure.language'|trans }}</a></li>
<li><a href="{{ path('config') }}#set2">{{ 'quickstart.configure.feed'|trans }}</a></li>
<li><a href="{{ path('config') }}#set5">{{ 'quickstart.more'|trans }}</a></li>
</ul>
</div>
</div>
</li>
<li class="col l4 m6 s12"> <li class="col l4 m6 s12">
<div class="card green darken-1"> <div class="card green darken-1">

View file

@ -121,7 +121,9 @@
</li> </li>
</ul> </ul>
<ul id="dropdown-account" class="dropdown-content"> <ul id="dropdown-account" class="dropdown-content">
<li><a href="{{ path('config') }}"><i class="material-icons">settings</i> {{ 'menu.left.config'|trans }}</a></li> {% if is_granted('EDIT_CONFIG') %}
<li><a href="{{ path('config') }}"><i class="material-icons">settings</i> {{ 'menu.left.config'|trans }}</a></li>
{% endif %}
<li><a href="{{ path('developer') }}"><i class="material-icons">smartphone</i> {{ 'menu.left.developer'|trans }}</a></li> <li><a href="{{ path('developer') }}"><i class="material-icons">smartphone</i> {{ 'menu.left.developer'|trans }}</a></li>
{% if is_granted('IMPORT_ENTRIES') %} {% if is_granted('IMPORT_ENTRIES') %}
<li><a href="{{ path('import') }}"><i class="material-icons">import_export</i> {{ 'menu.left.import'|trans }}</a></li> <li><a href="{{ path('import') }}"><i class="material-icons">import_export</i> {{ 'menu.left.import'|trans }}</a></li>

View file

@ -0,0 +1,98 @@
<?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 Wallabag\Entity\Config;
use Wallabag\Entity\IgnoreOriginUserRule;
use Wallabag\Entity\User;
use Wallabag\Security\Voter\IgnoreOriginUserRuleVoter;
class IgnoreOriginUserRuleVoterTest extends TestCase
{
private $token;
private $ignoreOriginUserRuleVoter;
protected function setUp(): void
{
$this->token = $this->createMock(TokenInterface::class);
$this->ignoreOriginUserRuleVoter = new IgnoreOriginUserRuleVoter();
}
public function testVoteReturnsAbstainForInvalidSubject(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->ignoreOriginUserRuleVoter->vote($this->token, new \stdClass(), [IgnoreOriginUserRuleVoter::EDIT]));
}
public function testVoteReturnsAbstainForInvalidAttribute(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->ignoreOriginUserRuleVoter->vote($this->token, new IgnoreOriginUserRule(), ['INVALID']));
}
public function testVoteReturnsDeniedForUnauthenticatedEdit(): void
{
$this->token->method('getUser')->willReturn(null);
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->ignoreOriginUserRuleVoter->vote($this->token, new IgnoreOriginUserRule(), [IgnoreOriginUserRuleVoter::EDIT]));
}
public function testVoteReturnsDeniedForOtherUserEdit(): void
{
$currentUser = new User();
$this->token->method('getUser')->willReturn($currentUser);
$taggingRuleUser = new User();
$taggingRule = new IgnoreOriginUserRule();
$taggingRule->setConfig(new Config($taggingRuleUser));
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->ignoreOriginUserRuleVoter->vote($this->token, $taggingRule, [IgnoreOriginUserRuleVoter::EDIT]));
}
public function testVoteReturnsGrantedForIgnoreOriginUserRuleUserEdit(): void
{
$user = new User();
$this->token->method('getUser')->willReturn($user);
$taggingRule = new IgnoreOriginUserRule();
$taggingRule->setConfig(new Config($user));
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->ignoreOriginUserRuleVoter->vote($this->token, $taggingRule, [IgnoreOriginUserRuleVoter::EDIT]));
}
public function testVoteReturnsDeniedForUnauthenticatedDelete(): void
{
$this->token->method('getUser')->willReturn(null);
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->ignoreOriginUserRuleVoter->vote($this->token, new IgnoreOriginUserRule(), [IgnoreOriginUserRuleVoter::DELETE]));
}
public function testVoteReturnsDeniedForOtherUserDelete(): void
{
$currentUser = new User();
$this->token->method('getUser')->willReturn($currentUser);
$taggingRuleUser = new User();
$taggingRule = new IgnoreOriginUserRule();
$taggingRule->setConfig(new Config($taggingRuleUser));
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->ignoreOriginUserRuleVoter->vote($this->token, $taggingRule, [IgnoreOriginUserRuleVoter::DELETE]));
}
public function testVoteReturnsGrantedForIgnoreOriginUserRuleUserDelete(): void
{
$user = new User();
$this->token->method('getUser')->willReturn($user);
$taggingRule = new IgnoreOriginUserRule();
$taggingRule->setConfig(new Config($user));
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->ignoreOriginUserRuleVoter->vote($this->token, $taggingRule, [IgnoreOriginUserRuleVoter::DELETE]));
}
}

View file

@ -139,4 +139,18 @@ class MainVoterTest extends TestCase
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_SITE_CREDENTIALS])); $this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::CREATE_SITE_CREDENTIALS]));
} }
public function testVoteReturnsDeniedForNonUserEditConfig(): void
{
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(false);
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->mainVoter->vote($this->token, null, [MainVoter::EDIT_CONFIG]));
}
public function testVoteReturnsGrantedForUserEditConfig(): void
{
$this->security->method('isGranted')->with('ROLE_USER')->willReturn(true);
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->mainVoter->vote($this->token, null, [MainVoter::EDIT_CONFIG]));
}
} }

View file

@ -0,0 +1,98 @@
<?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 Wallabag\Entity\Config;
use Wallabag\Entity\TaggingRule;
use Wallabag\Entity\User;
use Wallabag\Security\Voter\TaggingRuleVoter;
class TaggingRuleVoterTest extends TestCase
{
private $token;
private $taggingRuleVoter;
protected function setUp(): void
{
$this->token = $this->createMock(TokenInterface::class);
$this->taggingRuleVoter = new TaggingRuleVoter();
}
public function testVoteReturnsAbstainForInvalidSubject(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->taggingRuleVoter->vote($this->token, new \stdClass(), [TaggingRuleVoter::EDIT]));
}
public function testVoteReturnsAbstainForInvalidAttribute(): void
{
$this->assertSame(VoterInterface::ACCESS_ABSTAIN, $this->taggingRuleVoter->vote($this->token, new TaggingRule(), ['INVALID']));
}
public function testVoteReturnsDeniedForUnauthenticatedEdit(): void
{
$this->token->method('getUser')->willReturn(null);
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->taggingRuleVoter->vote($this->token, new TaggingRule(), [TaggingRuleVoter::EDIT]));
}
public function testVoteReturnsDeniedForOtherUserEdit(): void
{
$currentUser = new User();
$this->token->method('getUser')->willReturn($currentUser);
$taggingRuleUser = new User();
$taggingRule = new TaggingRule();
$taggingRule->setConfig(new Config($taggingRuleUser));
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->taggingRuleVoter->vote($this->token, $taggingRule, [TaggingRuleVoter::EDIT]));
}
public function testVoteReturnsGrantedForTaggingRuleUserEdit(): void
{
$user = new User();
$this->token->method('getUser')->willReturn($user);
$taggingRule = new TaggingRule();
$taggingRule->setConfig(new Config($user));
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->taggingRuleVoter->vote($this->token, $taggingRule, [TaggingRuleVoter::EDIT]));
}
public function testVoteReturnsDeniedForUnauthenticatedDelete(): void
{
$this->token->method('getUser')->willReturn(null);
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->taggingRuleVoter->vote($this->token, new TaggingRule(), [TaggingRuleVoter::DELETE]));
}
public function testVoteReturnsDeniedForOtherUserDelete(): void
{
$currentUser = new User();
$this->token->method('getUser')->willReturn($currentUser);
$taggingRuleUser = new User();
$taggingRule = new TaggingRule();
$taggingRule->setConfig(new Config($taggingRuleUser));
$this->assertSame(VoterInterface::ACCESS_DENIED, $this->taggingRuleVoter->vote($this->token, $taggingRule, [TaggingRuleVoter::DELETE]));
}
public function testVoteReturnsGrantedForTaggingRuleUserDelete(): void
{
$user = new User();
$this->token->method('getUser')->willReturn($user);
$taggingRule = new TaggingRule();
$taggingRule->setConfig(new Config($user));
$this->assertSame(VoterInterface::ACCESS_GRANTED, $this->taggingRuleVoter->vote($this->token, $taggingRule, [TaggingRuleVoter::DELETE]));
}
}