From 459b3317b3c08089a559d1dfac58ca934f4012c5 Mon Sep 17 00:00:00 2001 From: Elorfin Date: Tue, 15 Nov 2022 09:30:32 +0100 Subject: [PATCH] [Forum] fixes rights checks --- .../Command/CreateEntriesFromCsvCommand.php | 106 -------------- .../claco-form/Manager/ClacoFormManager.php | 134 ------------------ .../claco-form/Resources/config/services.yml | 1 - .../Resources/config/services/command.yml | 10 -- .../Controller/API/SubjectController.php | 53 ++++--- .../Resources/config/services/controller.yml | 5 +- src/plugin/forum/Security/SubjectVoter.php | 61 ++++++-- 7 files changed, 85 insertions(+), 285 deletions(-) delete mode 100644 src/plugin/claco-form/Command/CreateEntriesFromCsvCommand.php delete mode 100644 src/plugin/claco-form/Resources/config/services/command.yml diff --git a/src/plugin/claco-form/Command/CreateEntriesFromCsvCommand.php b/src/plugin/claco-form/Command/CreateEntriesFromCsvCommand.php deleted file mode 100644 index f6f9feb8af6..00000000000 --- a/src/plugin/claco-form/Command/CreateEntriesFromCsvCommand.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Claroline\ClacoFormBundle\Command; - -use Claroline\AppBundle\Persistence\ObjectManager; -use Claroline\ClacoFormBundle\Entity\ClacoForm; -use Claroline\ClacoFormBundle\Manager\ClacoFormManager; -use Claroline\CoreBundle\Entity\Resource\ResourceNode; -use Claroline\CoreBundle\Entity\User; -use Claroline\CoreBundle\Manager\ResourceManager; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; - -class CreateEntriesFromCsvCommand extends Command -{ - private $om; - private $resourceManager; - private $formManager; - - public function __construct(ObjectManager $om, ResourceManager $resourceManager, ClacoFormManager $formManager) - { - $this->om = $om; - $this->resourceManager = $resourceManager; - $this->formManager = $formManager; - - parent::__construct(); - } - - protected function configure() - { - $this->setDescription('Create entries for a ClacoForm from a csv file'); - $this->setDefinition([ - new InputArgument('username', InputArgument::REQUIRED, 'The username of the creator'), - new InputArgument('clacoform_node_id', InputArgument::REQUIRED, 'The uuid of the ClacoForm resource node'), - new InputArgument('csv_path', InputArgument::REQUIRED, 'The absolute path to the csv file'), - ]); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $userRepo = $this->om->getRepository(User::class); - $resourceNodeRepo = $this->om->getRepository(ResourceNode::class); - - $file = $input->getArgument('csv_path'); - $content = str_replace(PHP_EOL, '
', file_get_contents($file)); - $lines = explode(';""
', $content); - - $username = $input->getArgument('username'); - $user = $userRepo->findOneBy(['username' => $username]); - - $nodeId = $input->getArgument('clacoform_node_id'); - $node = $resourceNodeRepo->findOneBy(['uuid' => $nodeId]); - /** @var ClacoForm $clacoForm */ - $clacoForm = $node ? $this->resourceManager->getResourceFromNode($node) : null; - - if (!$user) { - $output->writeln("Coudn't find user."); - - return 1; - } - if (!$clacoForm) { - $output->writeln("Coudn't find ClacoForm resource."); - - return 1; - } - if (1 < count($lines)) { - $data = []; - $keys = str_getcsv($lines[0], ';'); - - foreach ($lines as $index => $line) { - if ($index > 0) { - $lineNum = $index + 1; - $lineData = []; - $lineArray = str_getcsv($line, ';'); - - if (count($lineArray) > count($keys)) { - throw new \Exception("Line {$lineNum} has too many args."); - } - - foreach ($lineArray as $key => $value) { - $lineData[$keys[$key]] = $value; - } - $data[] = $lineData; - } - } - - $this->formManager->importEntryFromCsv($clacoForm, $user, $data); - - return 0; - } - $output->writeln('CSV file must contain more the 1 line.'); - - return 1; - } -} diff --git a/src/plugin/claco-form/Manager/ClacoFormManager.php b/src/plugin/claco-form/Manager/ClacoFormManager.php index d2ea5286dce..9f8346f19a8 100644 --- a/src/plugin/claco-form/Manager/ClacoFormManager.php +++ b/src/plugin/claco-form/Manager/ClacoFormManager.php @@ -45,7 +45,6 @@ use Claroline\CoreBundle\Security\Collection\ResourceCollection; use Doctrine\Common\Collections\ArrayCollection; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LogLevel; use Ramsey\Uuid\Uuid; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -1062,137 +1061,4 @@ public function registerFile(ClacoForm $clacoForm, UploadedFile $file) 'url' => '../files/clacoform'.$ds.$clacoForm->getUuid().$ds.$fileName, ]; } - - /** - * Creates an entries from data from a csv. - * - * @return int - */ - public function importEntryFromCsv(ClacoForm $clacoForm, User $user, array $data) - { - $fieldsMapping = []; - $categoriesMapping = []; - $keywordsMapping = []; - $fields = $clacoForm->getFields(); - $categories = $clacoForm->getCategories(); - $keywords = $clacoForm->getKeywords(); - - foreach ($fields as $field) { - $fieldsMapping[$field->getName()] = $field; - } - foreach ($categories as $category) { - $categoriesMapping[$category->getName()] = $category; - } - foreach ($keywords as $keyword) { - $keywordsMapping[$keyword->getName()] = $keyword; - } - if (0 < count($data)) { - $this->om->startFlushSuite(); - $now = new \DateTime(); - - foreach ($data as $index => $entryData) { - $existingEntries = isset($entryData['title']) ? - $this->entryRepo->findBy(['clacoForm' => $clacoForm, 'title' => $entryData['title']]) : - null; - $lineNum = $index + 1; - - if (is_null($existingEntries)) { - $this->log("Entry from line {$lineNum} has no title or it is simply an empty line at the end of the file.", LogLevel::WARNING); - } elseif (0 === count($existingEntries)) { - $this->log("Importing entry from line {$lineNum}..."); - $entry = new Entry(); - $entry->setUser($user); - $entry->setClacoForm($clacoForm); - $entry->setStatus(Entry::PUBLISHED); - $entry->setCreationDate($now); - $entry->setPublicationDate($now); - - foreach ($entryData as $key => $value) { - switch ($key) { - case 'title': - $entry->setTitle($value); - break; - case 'status': - $entry->setStatus(intval($value)); - break; - case 'categories': - $categoriesNames = explode(',', $value); - - foreach ($categoriesNames as $categoryName) { - if (isset($categoriesMapping[$categoryName])) { - $entry->addCategory($categoriesMapping[$categoryName]); - } - } - break; - case 'keywords': - $keywordsNames = explode(',', $value); - - foreach ($keywordsNames as $keywordName) { - if (isset($keywordsMapping[$keywordName])) { - $entry->addKeyword($keywordsMapping[$keywordName]); - } - } - break; - case 'comments': - $contents = explode('|', $value); - - foreach ($contents as $content) { - $comment = new Comment(); - $comment->setEntry($entry); - $comment->setUser($user); - $comment->setContent($content); - $comment->setCreationDate($now); - $comment->setStatus(Comment::VALIDATED); - $this->om->persist($comment); - } - break; - default: - if (isset($fieldsMapping[$key])) { - $field = $fieldsMapping[$key]; - $fieldFacet = $field->getFieldFacet(); - $fieldValue = new FieldValue(); - $fieldValue->setEntry($entry); - $fieldValue->setField($field); - - $fielFacetValue = new FieldFacetValue(); - $fielFacetValue->setUser($user); - $fielFacetValue->setFieldFacet($fieldFacet); - - $formattedValue = $value; - - switch ($fieldFacet->getType()) { - case FieldFacet::NUMBER_TYPE: - $formattedValue = floatval($value); - break; - case FieldFacet::CASCADE_TYPE: - case FieldFacet::FILE_TYPE: - $formattedValue = explode(',', $value); - break; - case FieldFacet::BOOLEAN_TYPE: - $formattedValue = empty($value) || 'false' === $value ? false : true; - break; - case FieldFacet::CHOICE_TYPE: - $options = $fieldFacet->getOptions(); - - if (isset($options['multiple']) && $options['multiple']) { - $formattedValue = explode(',', $value); - } - break; - } - $fielFacetValue->setValue($formattedValue); - $this->om->persist($fielFacetValue); - - $fieldValue->setFieldFacetValue($fielFacetValue); - $this->om->persist($fieldValue); - } - } - } - $this->om->persist($entry); - } else { - $this->log("Entry from line {$lineNum} already existed.", LogLevel::ERROR); - } - } - $this->om->endFlushSuite(); - } - } } diff --git a/src/plugin/claco-form/Resources/config/services.yml b/src/plugin/claco-form/Resources/config/services.yml index 81e90099275..90f63ea5b15 100644 --- a/src/plugin/claco-form/Resources/config/services.yml +++ b/src/plugin/claco-form/Resources/config/services.yml @@ -2,7 +2,6 @@ imports: - { resource: services/finder.yml } - { resource: services/serializer.yml } - { resource: services/controller.yml } - - { resource: services/command.yml } - { resource: services/listener.yml } - { resource: services/manager.yml } - { resource: services/messenger.yml } diff --git a/src/plugin/claco-form/Resources/config/services/command.yml b/src/plugin/claco-form/Resources/config/services/command.yml deleted file mode 100644 index ba6661b0193..00000000000 --- a/src/plugin/claco-form/Resources/config/services/command.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - - Claroline\ClacoFormBundle\Command\CreateEntriesFromCsvCommand: - arguments: - - '@Claroline\AppBundle\Persistence\ObjectManager' - - '@Claroline\CoreBundle\Manager\ResourceManager' - - '@Claroline\ClacoFormBundle\Manager\ClacoFormManager' - tags: - - { name: 'console.command', command: 'claroline:clacoform:csv' } - diff --git a/src/plugin/forum/Controller/API/SubjectController.php b/src/plugin/forum/Controller/API/SubjectController.php index f82719014b5..eaad71e9fb7 100644 --- a/src/plugin/forum/Controller/API/SubjectController.php +++ b/src/plugin/forum/Controller/API/SubjectController.php @@ -4,6 +4,7 @@ use Claroline\AppBundle\Annotations\ApiDoc; use Claroline\AppBundle\Controller\AbstractCrudController; +use Claroline\CoreBundle\Security\PermissionCheckerTrait; use Claroline\ForumBundle\Entity\Forum; use Claroline\ForumBundle\Entity\Message; use Claroline\ForumBundle\Entity\Subject; @@ -11,17 +12,30 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; /** * @Route("/forum_subject") */ class SubjectController extends AbstractCrudController { + use PermissionCheckerTrait; + + public function __construct(AuthorizationCheckerInterface $authorization) + { + $this->authorization = $authorization; + } + public function getName(): string { return 'forum_subject'; } + public function getClass(): string + { + return Subject::class; + } + /** * @Route("/{id}/messages", methods={"GET"}) * @Route("/forum/{forumId}/subjects/{id}/messages", name="apiv2_forum_subject_get_message", methods={"GET"}) @@ -42,15 +56,15 @@ public function getName(): string * ) * * @param string $id - * - * @return JsonResponse */ - public function getMessagesAction(Subject $subject, Forum $forum = null, Request $request) + public function getMessagesAction(Request $request, Subject $subject, ?Forum $forum = null): JsonResponse { if ($forum && ($forum->getId() !== $subject->getForum()->getId())) { throw new \Exception('This subject was not created in the forum.'); } + $this->checkPermission('OPEN', $subject, [], true); + return new JsonResponse( $this->finder->search(Message::class, array_merge( $request->query->all(), @@ -69,11 +83,11 @@ public function getMessagesAction(Subject $subject, Forum $forum = null, Request * {"name": "id", "type": {"string", "integer"}, "description": "The subject id or uuid"} * } * ) - * - * @return JsonResponse */ - public function createMessage(Subject $subject, Request $request) + public function createMessage(Subject $subject, Request $request): JsonResponse { + $this->checkPermission('OPEN', $subject, [], true); + $subject = $this->serializer->serialize($subject); $data = $this->decodeRequest($request); $data['subject'] = $subject; @@ -105,38 +119,38 @@ public function createMessage(Subject $subject, Request $request) * {"name": "id", "type": {"string", "integer"}, "description": "The subject id or uuid"} * } * ) - * - * @return JsonResponse */ - public function updateMessageAction(Subject $subject, Message $message, Request $request) + public function updateMessageAction(Subject $subject, Message $message, Request $request): JsonResponse { + $this->checkPermission('OPEN', $subject, [], true); + return parent::updateAction($message->getUuid(), $request, Message::class); } /** * @Route("/forum/{forum}/subjects/list/flagged", name="apiv2_forum_subject_flagged_list", methods={"GET"}) * @EXT\ParamConverter("forum", class = "Claroline\ForumBundle\Entity\Forum", options={"mapping": {"forum": "uuid"}}) - * - * @return JsonResponse */ - public function getFlaggedSubjectsAction(Forum $forum, Request $request) + public function getFlaggedSubjectsAction(Forum $forum, Request $request): JsonResponse { + $this->checkPermission('OPEN', $forum->getResourceNode(), [], true); + return new JsonResponse( - $this->finder->search($this->getClass(), array_merge( + $this->finder->search($this->getClass(), array_merge( $request->query->all(), ['hiddenFilters' => ['flagged' => true, 'forum' => $forum->getUuid()]] )) - ); + ); } /** * @Route("/forum/{forum}/subjects/list/blocked", name="apiv2_forum_subject_blocked_list", methods={"GET"}) * @EXT\ParamConverter("forum", class = "Claroline\ForumBundle\Entity\Forum", options={"mapping": {"forum": "uuid"}}) - * - * @return JsonResponse */ - public function getBlockedSubjectsAction(Forum $forum, Request $request) + public function getBlockedSubjectsAction(Forum $forum, Request $request): JsonResponse { + $this->checkPermission('OPEN', $forum->getResourceNode(), [], true); + return new JsonResponse( $this->finder->search($this->getClass(), array_merge( $request->query->all(), @@ -144,9 +158,4 @@ public function getBlockedSubjectsAction(Forum $forum, Request $request) )) ); } - - public function getClass(): string - { - return Subject::class; - } } diff --git a/src/plugin/forum/Resources/config/services/controller.yml b/src/plugin/forum/Resources/config/services/controller.yml index bf008990b4c..7902345e7b7 100644 --- a/src/plugin/forum/Resources/config/services/controller.yml +++ b/src/plugin/forum/Resources/config/services/controller.yml @@ -1,7 +1,4 @@ services: - _defaults: - public: true #because controller - Claroline\ForumBundle\Controller\API\ForumController: parent: Claroline\AppBundle\Controller\AbstractCrudController public: true @@ -15,3 +12,5 @@ services: Claroline\ForumBundle\Controller\API\SubjectController: parent: Claroline\AppBundle\Controller\AbstractCrudController public: true + arguments: + - '@security.authorization_checker' diff --git a/src/plugin/forum/Security/SubjectVoter.php b/src/plugin/forum/Security/SubjectVoter.php index cab2cb5e0f5..cc6264cd7c3 100644 --- a/src/plugin/forum/Security/SubjectVoter.php +++ b/src/plugin/forum/Security/SubjectVoter.php @@ -13,30 +13,73 @@ use Claroline\CoreBundle\Entity\User; use Claroline\CoreBundle\Security\Voter\AbstractVoter; +use Claroline\ForumBundle\Entity\Subject; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class SubjectVoter extends AbstractVoter { - public function checkPermission(TokenInterface $token, $object, array $attributes, array $options) + public function checkPermission(TokenInterface $token, $object, array $attributes, array $options): int { switch ($attributes[0]) { - case self::CREATE: return $this->checkCreate($token); - } + case self::OPEN: + return $this->checkOpen($object); + case self::CREATE: + return $this->checkCreate($object, $token); + case self::EDIT: + case self::DELETE: + return $this->checkEdit($object, $token); + } + + return VoterInterface::ACCESS_ABSTAIN; + } + + private function checkOpen(Subject $subject): int + { + $forum = $subject->getForum(); + + if ($this->isGranted('OPEN', $forum->getResourceNode())) { + return VoterInterface::ACCESS_GRANTED; + } + + return VoterInterface::ACCESS_DENIED; + } + + private function checkCreate(Subject $subject, TokenInterface $token): int + { + $forum = $subject->getForum(); + + if ($this->isGranted('OPEN', $forum->getResourceNode())) { + if ($token->getUser() instanceof User) { + return VoterInterface::ACCESS_GRANTED; + } + } + + return VoterInterface::ACCESS_DENIED; } - public function checkCreate($token) + private function checkEdit(Subject $subject, TokenInterface $token): int { - return $token->getUser() instanceof User ? VoterInterface::ACCESS_GRANTED : VoterInterface::ACCESS_DENIED; + $forum = $subject->getForum(); + + if ($this->isGranted('EDIT', $forum->getResourceNode())) { + return VoterInterface::ACCESS_GRANTED; + } + + if ($token->getUser() instanceof User && $subject->getCreator() && $subject->getCreator()->getId() === $token->getUser()->getId()) { + return VoterInterface::ACCESS_GRANTED; + } + + return VoterInterface::ACCESS_DENIED; } - public function getClass() + public function getClass(): string { - return 'Claroline\ForumBundle\Entity\Subject'; + return Subject::class; } - public function getSupportedActions() + public function getSupportedActions(): array { - return [self::CREATE, self::EDIT, self::DELETE, self::PATCH]; + return [self::CREATE, self::EDIT, self::DELETE]; } }