From 6d12db099f3c11fe86dcc1f8d0a163453d6d94e4 Mon Sep 17 00:00:00 2001 From: hamza Date: Mon, 28 Sep 2026 12:00:00 +0200 Subject: [PATCH] fix(polls): require edit permission for shifting date options Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: hamza --- lib/Service/OptionService.php | 3 ++- tests/Unit/Service/OptionServiceTest.php | 34 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/Service/OptionService.php b/lib/Service/OptionService.php index 869b44ad0d..9fe6374943 100644 --- a/lib/Service/OptionService.php +++ b/lib/Service/OptionService.php @@ -334,7 +334,8 @@ private function countProposals(array $options): int { * @psalm-return array */ public function shift(int $pollId, int $step, string $unit): array { - $this->getPoll($pollId); + $this->getPoll($pollId) + ->request(Poll::PERMISSION_POLL_EDIT); if ($this->poll->getType() !== Poll::TYPE_DATE) { throw new InvalidPollTypeException('Shifting is only available in date polls'); diff --git a/tests/Unit/Service/OptionServiceTest.php b/tests/Unit/Service/OptionServiceTest.php index 9e1f574cab..ff1680735b 100644 --- a/tests/Unit/Service/OptionServiceTest.php +++ b/tests/Unit/Service/OptionServiceTest.php @@ -11,6 +11,7 @@ use OCA\Polls\Db\Option; use OCA\Polls\Db\Poll; use OCA\Polls\Db\PollMapper; +use OCA\Polls\Exceptions\ForbiddenException; use OCA\Polls\Exceptions\InsufficientAttributesException; use OCA\Polls\Model\SimpleOption; use OCA\Polls\Service\OptionService; @@ -26,6 +27,7 @@ class OptionServiceTest extends UnitTestCase { private Poll $textPoll; private Poll $datePoll; + private Poll $foreignDatePoll; private Option $textOption; protected function setUp(): void { @@ -52,6 +54,14 @@ protected function setUp(): void { $datePoll->setExpire(0); $this->datePoll = $this->pollMapper->insert($datePoll); + // Date poll owned by another user, private, open, not shared with admin + $foreignDatePoll = $this->fm->instance('OCA\\Polls\\Db\\Poll'); + $foreignDatePoll->setOwner('foreignUser'); + $foreignDatePoll->setType(Poll::TYPE_DATE); + $foreignDatePoll->setAccess(Poll::ACCESS_PRIVATE); + $foreignDatePoll->setExpire(0); + $this->foreignDatePoll = $this->pollMapper->insert($foreignDatePoll); + // Pre-existing text option for update/delete/confirm/reorder tests $this->textOption = $this->optionService->add( $this->textPoll->getId(), @@ -70,6 +80,10 @@ protected function tearDown(): void { $this->pollMapper->delete($this->datePoll); } catch (\Exception) { } + try { + $this->pollMapper->delete($this->foreignDatePoll); + } catch (\Exception) { + } } // --- add --- @@ -210,4 +224,24 @@ public function testReorderChangesOptionOrder(): void { $this->assertSame(1, $orderById[$second->getId()]); $this->assertSame(2, $orderById[$this->textOption->getId()]); } + + // --- shift --- + + public function testShiftMovesDateOptions(): void { + $timestamp = strtotime('2027-06-15 12:00:00 UTC'); + $this->optionService->add( + $this->datePoll->getId(), + (new SimpleOption())->setDateTime($timestamp) + ); + + $shifted = $this->optionService->shift($this->datePoll->getId(), 1, 'day'); + + $this->assertCount(1, $shifted); + $this->assertSame(strtotime('+1 day', $timestamp), reset($shifted)->getTimestamp()); + } + + public function testShiftDeniedWithoutEditPermission(): void { + $this->expectException(ForbiddenException::class); + $this->optionService->shift($this->foreignDatePoll->getId(), 1, 'day'); + } }