@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix two very, very minor correctness issues in Slowvote

Summary:
See <https://hackerone.com/reports/492525> and <https://hackerone.com/reports/489531>. I previously awarded a bounty for <https://hackerone.com/reports/434116> so Slowvote is getting "researched" a lot.

- Prevent users from undoing their vote by submitting the form with nothing selected.
- Prevent users from racing between the `delete()` and `save()` to vote for multiple options in a plurality poll.

Test Plan:
- Clicked the vote button with nothing selected in plurality and approval polls, got an error now.
- Added a `sleep(5)` between `delete()` and `save()`. Submitted different plurality votes in different windows. Before: votes raced, invalid end state. After: votes waited on the lock, arrived in a valid end state.

Reviewers: amckinley

Reviewed By: amckinley

Differential Revision: https://secure.phabricator.com/D20125

+41 -12
+41 -12
src/applications/slowvote/controller/PhabricatorSlowvoteVoteController.php
··· 37 37 $method = $poll->getMethod(); 38 38 $is_plurality = ($method == PhabricatorSlowvotePoll::METHOD_PLURALITY); 39 39 40 + if (!$votes) { 41 + if ($is_plurality) { 42 + $message = pht('You must vote for something.'); 43 + } else { 44 + $message = pht('You must vote for at least one option.'); 45 + } 46 + 47 + return $this->newDialog() 48 + ->setTitle(pht('Stand For Something')) 49 + ->appendParagraph($message) 50 + ->addCancelButton($poll->getURI()); 51 + } 52 + 40 53 if ($is_plurality && count($votes) > 1) { 41 54 throw new Exception( 42 55 pht('In this poll, you may only vote for one option.')); ··· 52 65 } 53 66 } 54 67 55 - foreach ($old_votes as $old_vote) { 56 - if (!idx($votes, $old_vote->getOptionID(), false)) { 68 + $poll->openTransaction(); 69 + $poll->beginReadLocking(); 70 + 71 + $poll->reload(); 72 + 73 + $old_votes = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 74 + 'pollID = %d AND authorPHID = %s', 75 + $poll->getID(), 76 + $viewer->getPHID()); 77 + $old_votes = mpull($old_votes, null, 'getOptionID'); 78 + 79 + foreach ($old_votes as $old_vote) { 80 + if (idx($votes, $old_vote->getOptionID())) { 81 + continue; 82 + } 83 + 57 84 $old_vote->delete(); 58 85 } 59 - } 86 + 87 + foreach ($votes as $vote) { 88 + if (idx($old_votes, $vote)) { 89 + continue; 90 + } 60 91 61 - foreach ($votes as $vote) { 62 - if (idx($old_votes, $vote, false)) { 63 - continue; 92 + id(new PhabricatorSlowvoteChoice()) 93 + ->setAuthorPHID($viewer->getPHID()) 94 + ->setPollID($poll->getID()) 95 + ->setOptionID($vote) 96 + ->save(); 64 97 } 65 98 66 - id(new PhabricatorSlowvoteChoice()) 67 - ->setAuthorPHID($viewer->getPHID()) 68 - ->setPollID($poll->getID()) 69 - ->setOptionID($vote) 70 - ->save(); 71 - } 99 + $poll->endReadLocking(); 100 + $poll->saveTransaction(); 72 101 73 102 return id(new AphrontRedirectResponse()) 74 103 ->setURI($poll->getURI());