From baeb81eaa4ab8d576c7c3ade4a614da6caa2f5fe Mon Sep 17 00:00:00 2001 From: dartcafe Date: Tue, 19 Mar 2024 20:37:45 +0100 Subject: [PATCH 1/2] join share for user role Signed-off-by: dartcafe --- lib/Controller/PublicController.php | 19 +++++++++------ lib/Db/Poll.php | 37 ++++++++++++++++++++++------- lib/Db/PollMapper.php | 24 +++++++++++++++++-- lib/Db/UserMapper.php | 4 ++-- lib/Model/Acl.php | 8 +++---- 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/lib/Controller/PublicController.php b/lib/Controller/PublicController.php index 177345938..ee650bf9f 100644 --- a/lib/Controller/PublicController.php +++ b/lib/Controller/PublicController.php @@ -46,6 +46,10 @@ use OCP\Util; /** + * Always use parent's classe response* methods to make sure, the token gets set correctly. + * Requesting the token inside the controller is not possible, because the token is submitted + * as a paramter and not known while contruction time + * i.e. ACL requests are not valid before calling the response* method * @psalm-api */ class PublicController extends BasePublicController { @@ -89,13 +93,14 @@ public function votePage() { */ #[PublicPage] public function getPoll(string $token): JSONResponse { - $this->acl->request(Acl::PERMISSION_POLL_VIEW); - - // load poll through acl - return $this->response(fn () => [ - 'acl' => $this->acl, - 'poll' => $this->acl->getPoll(), - ], $token); + return $this->response(function () { + $this->acl->request(Acl::PERMISSION_POLL_VIEW); + // load poll through acl + return [ + 'acl' => $this->acl, + 'poll' => $this->acl->getPoll(), + ]; + }, $token); } /** diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 053eabbd6..01a9c40f8 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -137,6 +137,8 @@ class Poll extends EntityWithUser implements JsonSerializable { protected int $maxDate = 0; protected int $minDate = 0; protected int $currentUserVotes = 0; + protected string $userRole = "none"; + protected ?int $isCurrentUserLocked = 0; public function __construct() { $this->addType('created', 'int'); @@ -168,6 +170,7 @@ public function __construct() { public function jsonSerialize(): array { return [ 'id' => $this->getId(), + 'type' => $this->getType(), 'title' => $this->getTitle(), 'description' => $this->getDescription(), 'descriptionSafe' => $this->getDescriptionSafe(), @@ -185,14 +188,14 @@ public function jsonSerialize(): array { 'optionLimit' => $this->getOptionLimit(), 'proposalsExpire' => $this->getProposalsExpire(), 'showResults' => $this->getShowResults() === 'expired' ? Poll::SHOW_RESULTS_CLOSED : $this->getShowResults(), - 'type' => $this->getType(), 'useNo' => $this->getUseNo(), 'voteLimit' => $this->getVoteLimit(), 'lastInteraction' => $this->getLastInteraction(), 'summary' => [ - 'orphanedVotes' => count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())), - 'yesByCurrentUser' => count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())), + 'orphanedVotes' => $this->getCurrentUserOrphanedVotes(), + 'yesByCurrentUser' => $this->getCurrentUserYesVotes(), 'countVotes' => $this->getCurrentUserCountVotes(), + 'userRole' => $this->getUserRole(), ], ]; } @@ -231,6 +234,13 @@ public function getExpired(): bool { ); } + public function getUserRole(): string { + if ($this->userMapper->getCurrentUser()->getId() === $this->getOwner()) { + return 'owner'; + } + return $this->userRole; + } + public function getVoteUrl(): string { return $this->urlGenerator->linkToRouteAbsolute( AppConstants::APP_ID . '.page.vote', @@ -278,10 +288,6 @@ public function getProposalsExpired(): bool { ); } - public function getCurrentUserCountVotes(): int { - return $this->currentUserVotes; - } - public function getDescription(): string { return $this->description ?? ''; } @@ -337,13 +343,28 @@ public function getRelevantThresholdNet(): int { ); } + public function getCurrentUserCountVotes(): int { + return $this->currentUserVotes; + } + + public function getIsCurrentUserLocked(): bool { + return (bool) $this->isCurrentUserLocked; + } + /** * @psalm-return int<0, max> */ - public function getOrphanedVotes(): int { + public function getCurrentUserOrphanedVotes(): int { return count($this->voteMapper->findOrphanedByPollandUser($this->id, $this->userMapper->getCurrentUserCached()->getId())); } + /** + * @psalm-return int<0, max> + */ + public function getCurrentUserYesVotes(): int { + return count($this->voteMapper->getYesVotesByParticipant($this->getPollId(), $this->userMapper->getCurrentUserCached()->getId())); + } + public function getDeadline(): int { // if expiration is set return expiration date if ($this->getExpire()) { diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index de08bca37..12726301f 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -179,10 +179,32 @@ protected function buildQuery(): IQueryBuilder { ->from($this->getTableName(), self::TABLE); $this->joinOptionsForMaxDate($qb, self::TABLE); $this->joinCurrentUserVotes($qb, self::TABLE, $currentUserId); + $this->joinUserRole($qb, self::TABLE, $currentUserId); $qb->groupBy(self::TABLE . '.id'); return $qb; } + /** + * Joins options to evaluate min and max option date for date polls + * if text poll or no options are set, + * the min value is the current time, + * the max value is null + */ + protected function joinUserRole(IQueryBuilder &$qb, string $fromAlias, string $currentUserId): void { + $joinAlias = 'shares'; + $qb->addSelect($qb->createFunction('coalesce(' . $joinAlias . '.type, "") AS user_role')); + + $qb->leftJoin( + $fromAlias, + Share::TABLE, + $joinAlias, + $qb->expr()->andX( + $qb->expr()->eq($fromAlias . '.id', $joinAlias . '.poll_id'), + $qb->expr()->eq($joinAlias . '.user_id', $qb->createNamedParameter($currentUserId, IQueryBuilder::PARAM_STR)), + ) + ); + } + /** * Joins options to evaluate min and max option date for date polls * if text poll or no options are set, @@ -193,8 +215,6 @@ protected function joinOptionsForMaxDate(IQueryBuilder &$qb, string $fromAlias): $joinAlias = 'options'; $saveMin = (string) time(); - // force value into a MIN function to avoid grouping errors - // $qb->selectAlias($qb->func()->max($joinAlias . '.timestamp'), 'max_date'); $qb->addSelect($qb->createFunction('coalesce(MAX(' . $joinAlias . '.timestamp), 0) AS max_date')) ->addSelect($qb->createFunction('coalesce(MIN(' . $joinAlias . '.timestamp), ' . $saveMin . ') AS min_date')); diff --git a/lib/Db/UserMapper.php b/lib/Db/UserMapper.php index cc2cc392f..3d70f84bd 100644 --- a/lib/Db/UserMapper.php +++ b/lib/Db/UserMapper.php @@ -96,7 +96,7 @@ public function getCurrentUser(): UserBase { } else { try { - $this->currentUser = $this->getUserFromShareToken($this->getToken()); + $this->currentUser = $this->getUserFromShareToken($this->getSessionStoredShareToken()); } catch (DoesNotExistException $e) { $this->logger->debug('no user found, returned fake user'); $this->currentUser = new GenericUser('', Share::TYPE_PUBLIC); @@ -114,7 +114,7 @@ public function getCurrentUserCached(): UserBase { return $this->currentUser ?? $this->getCurrentUser(); } - private function getToken(): string { + public function getSessionStoredShareToken(): string { return (string) $this->session->get(AppConstants::SESSION_KEY_SHARE_TOKEN); } diff --git a/lib/Model/Acl.php b/lib/Model/Acl.php index 414125edc..3cbed9419 100644 --- a/lib/Model/Acl.php +++ b/lib/Model/Acl.php @@ -173,7 +173,7 @@ public function getPoll(): Poll { */ private function getShare(): Share { if ($this->validateShareToken()) { - $this->share = $this->shareMapper->findByToken((string) $this->getToken()); + $this->share = $this->shareMapper->findByToken((string) $this->getSessionStoredShareToken()); $this->pollId = $this->share->getPollId(); } @@ -181,11 +181,11 @@ private function getShare(): Share { } private function validateShareToken(): bool { - return $this->isSessionTokenSet() && $this->getToken() !== $this->share->getToken(); + return $this->isSessionTokenSet() && $this->getSessionStoredShareToken() !== $this->share->getToken(); } private function isSessionTokenSet(): bool { - return boolval($this->getToken()); + return boolval($this->getSessionStoredShareToken()); } private function sideLoadShare(): void { @@ -237,7 +237,7 @@ public function getPollId(): int { return $this->getPoll()->getId(); } - private function getToken(): ?string { + private function getSessionStoredShareToken(): ?string { return $this->session->get(AppConstants::SESSION_KEY_SHARE_TOKEN); } From f999ebfe5c5f9a6de709423fbdba62dd95f0beb7 Mon Sep 17 00:00:00 2001 From: dartcafe Date: Wed, 20 Mar 2024 00:09:25 +0100 Subject: [PATCH 2/2] optimizations upon user role Signed-off-by: dartcafe --- lib/Controller/PublicController.php | 2 +- lib/Db/Poll.php | 16 ++- lib/Db/ShareMapper.php | 23 ++++ lib/Exceptions/InvalidPollIdException.php | 36 ----- lib/Model/Acl.php | 161 +++++++--------------- lib/Model/Settings/AppSettings.php | 18 +++ lib/Service/CommentService.php | 4 +- lib/Service/OptionService.php | 14 +- lib/Service/SubscriptionService.php | 6 +- lib/Service/VoteService.php | 10 +- 10 files changed, 124 insertions(+), 166 deletions(-) delete mode 100644 lib/Exceptions/InvalidPollIdException.php diff --git a/lib/Controller/PublicController.php b/lib/Controller/PublicController.php index ee650bf9f..7410c8565 100644 --- a/lib/Controller/PublicController.php +++ b/lib/Controller/PublicController.php @@ -47,7 +47,7 @@ /** * Always use parent's classe response* methods to make sure, the token gets set correctly. - * Requesting the token inside the controller is not possible, because the token is submitted + * Requesting the token inside the controller is not possible, because the token is submitted * as a paramter and not known while contruction time * i.e. ACL requests are not valid before calling the response* method * @psalm-api diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php index 01a9c40f8..fd60ef444 100644 --- a/lib/Db/Poll.php +++ b/lib/Db/Poll.php @@ -104,6 +104,16 @@ class Poll extends EntityWithUser implements JsonSerializable { public const TWO_DAYS = 172800; public const ONE_AND_HALF_DAY = 129600; + public const ROLE_USER = Share::TYPE_USER; + public const ROLE_ADMIN = Share::TYPE_ADMIN; + public const ROLE_EMAIL = Share::TYPE_EMAIL; + public const ROLE_CONTACT = Share::TYPE_CONTACT; + public const ROLE_EXTERNAL = Share::TYPE_EXTERNAL; + public const ROLE_OWNER = 'owner'; + public const ROLE_NONE = 'none'; + + + private IURLGenerator $urlGenerator; protected UserMapper $userMapper; private VoteMapper $voteMapper; @@ -137,7 +147,7 @@ class Poll extends EntityWithUser implements JsonSerializable { protected int $maxDate = 0; protected int $minDate = 0; protected int $currentUserVotes = 0; - protected string $userRole = "none"; + protected string $userRole = self::ROLE_NONE; protected ?int $isCurrentUserLocked = 0; public function __construct() { @@ -236,9 +246,9 @@ public function getExpired(): bool { public function getUserRole(): string { if ($this->userMapper->getCurrentUser()->getId() === $this->getOwner()) { - return 'owner'; + return self::ROLE_OWNER; } - return $this->userRole; + return $this->userRole; } public function getVoteUrl(): string { diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index ea59e197b..1549f3061 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -71,6 +71,29 @@ public function findByPoll(int $pollId, bool $getDeleted = false): array { return $this->findEntities($qb); } + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Share[] + * @psalm-return array + */ + public function findGroupShareByPoll(int $pollId, bool $getDeleted = false): array { + $qb = $this->db->getQueryBuilder(); + + $qb->select(self::TABLE . '.*') + ->from($this->getTableName(), self::TABLE) + ->groupBy(self::TABLE . '.id') + ->where($qb->expr()->eq(self::TABLE . '.poll_id', $qb->createNamedParameter($pollId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq(self::TABLE . '.type', $qb->createNamedParameter(Share::TYPE_GROUP, IQueryBuilder::PARAM_STR))); + + if (!$getDeleted) { + $qb->andWhere($qb->expr()->eq(self::TABLE . '.deleted', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + } + + $this->joinUserVoteCount($qb, self::TABLE); + + return $this->findEntities($qb); + } + /** * @throws \OCP\AppFramework\Db\DoesNotExistException if not found * @return Share[] diff --git a/lib/Exceptions/InvalidPollIdException.php b/lib/Exceptions/InvalidPollIdException.php deleted file mode 100644 index d269a2a95..000000000 --- a/lib/Exceptions/InvalidPollIdException.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * @author René Gieling - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\Polls\Exceptions; - -use OCP\AppFramework\Http; - -class InvalidPollIdException extends Exception { - public function __construct( - string $e = 'Poll id is invalid or missing' - ) { - parent::__construct($e, Http::STATUS_CONFLICT); - } -} diff --git a/lib/Model/Acl.php b/lib/Model/Acl.php index 3cbed9419..2392f52ec 100644 --- a/lib/Model/Acl.php +++ b/lib/Model/Acl.php @@ -35,8 +35,6 @@ use OCA\Polls\Db\UserMapper; use OCA\Polls\Exceptions\ForbiddenException; use OCA\Polls\Exceptions\InsufficientAttributesException; -use OCA\Polls\Exceptions\InvalidPollIdException; -use OCA\Polls\Exceptions\ShareNotFoundException; use OCA\Polls\Model\Settings\AppSettings; use OCP\ISession; @@ -81,7 +79,6 @@ public function __construct( private Poll $poll, private Share $share, ) { - $this->pollId = null; } /** @@ -133,18 +130,10 @@ public function getCurrentUserArray(): array { */ /** - * Set poll id and load poll + * Set poll id */ public function setPollId(int $pollId, string $permission = self::PERMISSION_POLL_VIEW): void { - if ($this->isSessionTokenSet()) { - if ($pollId !== $this->getShare()->getPollId()) { - throw new ForbiddenException('pollId does not match share'); - } - } else { - $this->pollId = $pollId; - } - - $this->getPoll(); + $this->pollId = $pollId; $this->request($permission); } @@ -153,17 +142,20 @@ public function setPollId(int $pollId, string $permission = self::PERMISSION_POL * @throws InsufficientAttributesException Thrown if stored pollId is null */ public function getPoll(): Poll { - if ($this->pollId === null) { - throw new InsufficientAttributesException('PollId may not be mull'); + if ($this->isSessionStoredShareTokenSet()) { + // if a share token is set, force usage of the share's pollId + $this->pollId = $this->getShare()->getPollId(); + } + + if (!boolval($this->pollId)) { + throw new InsufficientAttributesException('PollId is invalid: ' . (string) $this->pollId); } if ($this->pollId !== $this->poll->getId()) { + // if pollId differs from pollId from loaded poll (or poll is not loaded), get poll from db $this->poll = $this->pollMapper->find($this->pollId); } - // sideload existing share for internal user, if no token is set - $this->sideLoadShare(); - return $this->poll; } @@ -172,50 +164,19 @@ public function getPoll(): Poll { * load share from db by session stored token or rely on cached share */ private function getShare(): Share { - if ($this->validateShareToken()) { + if ($this->isSessionStoredShareTokenSet() && + $this->getSessionStoredShareToken() !== $this->share->getToken()) { + // Session stored token differs from share's token, + // reload share from db for session stored share token $this->share = $this->shareMapper->findByToken((string) $this->getSessionStoredShareToken()); - $this->pollId = $this->share->getPollId(); } - return $this->share; } - private function validateShareToken(): bool { - return $this->isSessionTokenSet() && $this->getSessionStoredShareToken() !== $this->share->getToken(); - } - - private function isSessionTokenSet(): bool { + private function isSessionStoredShareTokenSet(): bool { return boolval($this->getSessionStoredShareToken()); } - private function sideLoadShare(): void { - // do not load if share is loaded via public access - if ($this->isSessionTokenSet()) { - return; - } - - // do not load if user is not logged in - if (!$this->getCurrentUser()->getIsLoggedIn()) { - return; - } - - // do not load if a share is already loaded and matches the current poll id - if (boolval($this->share->getPollId() === $this->poll->getId())) { - return; - } - - // find share, which grants access or poll admin rights - try { - $this->share = $this->shareMapper->findByPollAndUser( - $this->poll->getId(), - $this->getCurrentUser()->getId() - ); - } catch (ShareNotFoundException $e) { - $this->share = new Share(); - } - - } - /** * loads the current user from the userMapper or returns the cached one */ @@ -224,19 +185,6 @@ private function getCurrentUser(): UserBase { return $this->currentUser; } - /** - * returns the current pollId; Either from share or from setPollId() - */ - public function getPollId(): int { - if ($this->isSessionTokenSet()) { - $this->pollId = $this->getShare()->getPollId(); - } - if (!$this->getPoll()->getId()) { - throw new InvalidPollIdException('No pollId set!'); - } - return $this->getPoll()->getId(); - } - private function getSessionStoredShareToken(): ?string { return $this->session->get(AppConstants::SESSION_KEY_SHARE_TOKEN); } @@ -252,30 +200,28 @@ public function getUserId(): string { * Checks, if the current user is the poll owner */ public function getIsPollOwner(): bool { - return ($this->getPoll()->getOwner() === $this->getUserId()); + return ($this->getPoll()->getUserRole() === Poll::ROLE_OWNER); } /** * Check perticular rights and inform via boolean value, if the right is granted or denied */ public function getIsAllowed(string $permission): bool { - $this->getShare(); - // $this->verifyConstraints(); return match ($permission) { self::PERMISSION_OVERRIDE => true, + self::PERMISSION_ALL_ACCESS => $this->appSettings->getAllAccessAllowed(), + self::PERMISSION_PUBLIC_SHARES => $this->appSettings->getPublicSharesAllowed(), self::PERMISSION_POLL_CREATE => $this->appSettings->getPollCreationAllowed(), self::PERMISSION_POLL_MAILADDRESSES_VIEW => $this->appSettings->getAllowSeeMailAddresses(), self::PERMISSION_POLL_DOWNLOAD => $this->appSettings->getPollDownloadAllowed(), - self::PERMISSION_ALL_ACCESS => $this->appSettings->getAllAccessAllowed(), - self::PERMISSION_PUBLIC_SHARES => $this->appSettings->getPublicSharesAllowed(), self::PERMISSION_POLL_VIEW => $this->getAllowAccessPoll(), self::PERMISSION_POLL_EDIT => $this->getAllowEditPoll(), self::PERMISSION_POLL_DELETE => $this->getAllowDeletePoll(), - self::PERMISSION_POLL_ARCHIVE => $this->getAllowDeletePoll(), - self::PERMISSION_POLL_TAKEOVER => $this->getAllowDeletePoll(), + self::PERMISSION_POLL_ARCHIVE => $this->getAllowEditPoll(), + self::PERMISSION_POLL_TAKEOVER => $this->getAllowEditPoll(), self::PERMISSION_POLL_SUBSCRIBE => $this->getAllowSubscribeToPoll(), self::PERMISSION_POLL_RESULTS_VIEW => $this->getShowResults(), - self::PERMISSION_POLL_USERNAMES_VIEW => $this->getAllowEditPoll() || !$this->poll->getAnonymous(), + self::PERMISSION_POLL_USERNAMES_VIEW => $this->getAllowEditPoll() || !$this->getPoll()->getAnonymous(), self::PERMISSION_OPTIONS_ADD => $this->getAllowAddOptions(), self::PERMISSION_OPTION_DELETE => $this->getAllowDeleteOption(), self::PERMISSION_COMMENT_ADD => $this->getAllowComment(), @@ -303,9 +249,8 @@ private function getIsInvolved(): bool { return ( $this->getIsPollOwner() || $this->getIsParticipant() - || $this->getIsInvitedViaGroupShare() - || $this->getIsInvitedViaGroupShare() - || $this->getIsPersonallyInvited()); + || $this->getIsPersonallyInvited()) + || $this->getIsInvitedViaGroupShare(); } /** @@ -334,28 +279,25 @@ private function getIsInvitedViaGroupShare(): bool { } return count( - array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) { - return ($item->getType() === Share::TYPE_GROUP && $this->getCurrentUser()->getIsInGroup($item->getUserId())); + array_filter($this->shareMapper->findGroupShareByPoll($this->getPoll()->getId()), function ($item) { + return ($this->getCurrentUser()->getIsInGroup($item->getUserId())); }) ) > 0; } /** - * getIsPersonallyInvited - Is the poll shared via user share? - * This only affects logged in users. - * @return bool Returns true, if the current poll contains a user share for the current user. + * getIsPersonallyInvited - Is the poll shared via user share with the current user? + * Checking via user role + * @return bool Returns true, if the current poll contains a user role which matches a share type */ private function getIsPersonallyInvited(): bool { - if ($this->getCurrentUser()->getIsLoggedIn() && $this->getShare()->getToken()) { - return in_array($this->getShare()->getType(), [ - Share::TYPE_ADMIN, - Share::TYPE_USER, - Share::TYPE_EXTERNAL, - Share::TYPE_EMAIL, - Share::TYPE_CONTACT - ]); - } - return false; + return in_array($this->getPoll()->getUserRole(), [ + Poll::ROLE_ADMIN, + Poll::ROLE_USER, + Poll::ROLE_EXTERNAL, + Poll::ROLE_EMAIL, + Poll::ROLE_CONTACT, + ]); } /** @@ -367,7 +309,8 @@ private function getIsPersonallyInvited(): bool { * Checks, if the user has delegated admin rights to edit poll settings via share */ private function getIsDelegatedAdmin(): bool { - return $this->getShare()->getType() === Share::TYPE_ADMIN && !boolval($this->getShare()->getLocked()); + return $this->getPoll()->getUserRole() === Poll::ROLE_ADMIN + && !$this->getPoll()->getIsCurrentUserLocked(); } /** @@ -394,7 +337,7 @@ private function getAllowEditPoll(): bool { } /** - * Checks, if user is allowed to access poll + * Checks, if user is allowed to access (view) poll */ private function getAllowAccessPoll(): bool { // edit rights include access to poll @@ -407,13 +350,13 @@ private function getAllowAccessPoll(): bool { return false; } - // grant access if user is involved in poll in any way - if ($this->getIsInvolved()) { + // grant access if poll poll is an open poll (for logged in users) + if ($this->getIsOpenPoll() && $this->getCurrentUser()->getIsLoggedIn()) { return true; } - - // grant access if poll poll is an open poll (for logged in users) - if ($this->getIsOpenPoll()) { + + // grant access if user is involved in poll in any way + if ($this->getIsInvolved()) { return true; } @@ -431,7 +374,7 @@ private function getAllowDeletePoll(): bool { return true; } - // admins are allowed to delete polls, in all other cases deny poll deletion right + // additionally site admins are allowed to delete polls, in all other cases deny poll deletion right return $this->getCurrentUser()->getIsAdmin(); } @@ -459,8 +402,8 @@ private function getAllowAddOptions(): bool { return false; } - // Request for option proposals is expired, deny - if (boolval($this->getShare()->getLocked())) { + // Locked Users are not allowed to add options + if (boolval($this->getPoll()->getIsCurrentUserLocked())) { return false; } @@ -497,7 +440,7 @@ private function getAllowComment(): bool { } // public shares are not allowed to comment - if (boolval($this->getShare()->getLocked())) { + if (boolval($this->getPoll()->getIsCurrentUserLocked())) { return false; } @@ -508,7 +451,7 @@ private function getAllowComment(): bool { * Checks, if user is allowed to delete comments from poll **/ private function getAllowDeleteComment(): bool { - return $this->getIsPollOwner() || $this->getIsDelegatedAdmin(); + return $this->getAllowEditPoll(); } /** @@ -525,8 +468,8 @@ private function getAllowVote(): bool { return false; } - // public shares are not allowed to vote - if (boolval($this->getShare()->getLocked())) { + // Locked users are not allowed to vote + if (boolval($this->getPoll()->getIsCurrentUserLocked())) { return false; } @@ -538,7 +481,7 @@ private function getAllowVote(): bool { * Checks, if user is allowed to subscribe to updates **/ private function getAllowSubscribeToPoll(): bool { - // user has no access right to this poll + // user with access to poll are always allowed to subscribe if (!$this->getAllowAccessPoll()) { return false; } @@ -560,7 +503,7 @@ private function getShowResults(): bool { return false; } - // show results, when poll is cloed + // show results, when poll is closed if ($this->getPoll()->getShowResults() === Poll::SHOW_RESULTS_CLOSED && $this->getPoll()->getExpired()) { return true; } diff --git a/lib/Model/Settings/AppSettings.php b/lib/Model/Settings/AppSettings.php index 6fce8d364..537a9a1c0 100644 --- a/lib/Model/Settings/AppSettings.php +++ b/lib/Model/Settings/AppSettings.php @@ -93,6 +93,9 @@ public function getIntegerSetting(string $key, int $default = 0): int { } // Checks + /** + * Poll creation permission is controlled by app settings + */ public function getPollCreationAllowed(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_ALLOW_POLL_CREATION) || $this->isMember($this->getGroupSetting(self::SETTING_POLL_CREATION_GROUPS)); @@ -100,6 +103,9 @@ public function getPollCreationAllowed(): bool { return false; } + /** + * Permission to see emailaddresses is controlled by app settings + */ public function getAllowSeeMailAddresses(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_SHOW_MAIL_ADDRESSES) || $this->isMember($this->getGroupSetting(self::SETTING_SHOW_MAIL_ADDRESSES_GROUPS)); @@ -107,6 +113,9 @@ public function getAllowSeeMailAddresses(): bool { return false; } + /** + * Permission to download emailaddresses is controlled by app settings + */ public function getPollDownloadAllowed(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_ALLOW_POLL_DOWNLOAD) || $this->isMember($this->getGroupSetting(self::SETTING_POLL_DOWNLOAD_GROUPS)); @@ -114,6 +123,9 @@ public function getPollDownloadAllowed(): bool { return false; } + /** + * Permission to share polls with all internal users is controlled by app settings (open poll) + */ public function getAllAccessAllowed(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_ALLOW_ALL_ACCESS) || $this->isMember($this->getGroupSetting(self::SETTING_ALL_ACCESS_GROUPS)); @@ -121,6 +133,9 @@ public function getAllAccessAllowed(): bool { return false; } + /** + * Permission to create public shares is controlled by app settings + */ public function getPublicSharesAllowed(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_ALLOW_PUBLIC_SHARES) || $this->isMember($this->getGroupSetting(self::SETTING_PUBLIC_SHARES_GROUPS)); @@ -128,6 +143,9 @@ public function getPublicSharesAllowed(): bool { return false; } + /** + * Permission to combine polls is controlled by app settings and only for internal users + */ public function getComboAllowed(): bool { if ($this->session->isLoggedIn()) { return $this->getBooleanSetting(self::SETTING_ALLOW_COMBO) diff --git a/lib/Service/CommentService.php b/lib/Service/CommentService.php index fd55be7e8..2f0acf15f 100644 --- a/lib/Service/CommentService.php +++ b/lib/Service/CommentService.php @@ -55,7 +55,7 @@ public function list(?int $pollId = null): array { } $this->acl->request(Acl::PERMISSION_COMMENT_ADD); - $comments = $this->commentMapper->findByPoll($this->acl->getPollId()); + $comments = $this->commentMapper->findByPoll($this->acl->getPoll()->getId()); // treat comments from the same user within 5 minutes as grouped comments $timeTolerance = 5 * 60; // init predecessor as empty Comment @@ -88,7 +88,7 @@ public function add(string $message, ?int $pollId = null): Comment { $this->acl->request(Acl::PERMISSION_COMMENT_ADD); $this->comment = new Comment(); - $this->comment->setPollId($this->acl->getPollId()); + $this->comment->setPollId($this->acl->getPoll()->getId()); $this->comment->setUserId($this->acl->getUserId()); $this->comment->setComment($message); $this->comment->setTimestamp(time()); diff --git a/lib/Service/OptionService.php b/lib/Service/OptionService.php index 150bca7dd..f5d01b675 100644 --- a/lib/Service/OptionService.php +++ b/lib/Service/OptionService.php @@ -76,7 +76,7 @@ public function list(?int $pollId = null): array { } try { - $this->options = $this->optionMapper->findByPoll($this->acl->getPollId(), !$this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)); + $this->options = $this->optionMapper->findByPoll($this->acl->getPoll()->getId(), !$this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)); if ($this->acl->getPoll()->getHideBookedUp() && !$this->acl->getIsAllowed(Acl::PERMISSION_POLL_EDIT)) { // hide booked up options, except the user has edit permission @@ -98,7 +98,7 @@ public function list(?int $pollId = null): array { * @return Option */ public function addForCurrentPoll(int $timestamp = 0, string $pollOptionText = '', int $duration = 0): Option { - $pollId = $this->acl->getPollId(); + $pollId = $this->acl->getPoll()->getId(); return $this->add($pollId, $timestamp, $pollOptionText, $duration); } @@ -281,7 +281,7 @@ public function sequence(int $optionId, int $step, string $unit, int $amount): a $this->eventDispatcher->dispatchTyped(new OptionCreatedEvent($this->option)); - return $this->optionMapper->findByPoll($this->acl->getPollId()); + return $this->optionMapper->findByPoll($this->acl->getPoll()->getId()); } /** @@ -381,18 +381,18 @@ public function setOrder(int $optionId, int $newOrder): array { if ($newOrder < 1) { $newOrder = 1; - } elseif ($newOrder > $this->getHighestOrder($this->acl->getPollId())) { - $newOrder = $this->getHighestOrder($this->acl->getPollId()); + } elseif ($newOrder > $this->getHighestOrder($this->acl->getPoll()->getId())) { + $newOrder = $this->getHighestOrder($this->acl->getPoll()->getId()); } - foreach ($this->optionMapper->findByPoll($this->acl->getPollId()) as $option) { + foreach ($this->optionMapper->findByPoll($this->acl->getPoll()->getId()) as $option) { $option->setOrder($this->moveModifier($this->option->getOrder(), $newOrder, $option->getOrder())); $this->optionMapper->update($option); } $this->eventDispatcher->dispatchTyped(new PollOptionReorderedEvent($this->acl->getPoll())); - return $this->optionMapper->findByPoll($this->acl->getPollId()); + return $this->optionMapper->findByPoll($this->acl->getPoll()->getId()); } /** diff --git a/lib/Service/SubscriptionService.php b/lib/Service/SubscriptionService.php index 16f28cc5d..e37ec7ab9 100644 --- a/lib/Service/SubscriptionService.php +++ b/lib/Service/SubscriptionService.php @@ -49,7 +49,7 @@ public function get(?int $pollId = null): bool { try { $this->acl->request(Acl::PERMISSION_POLL_SUBSCRIBE); - $this->subscriptionMapper->findByPollAndUser($this->acl->getPollId(), $this->acl->getUserId()); + $this->subscriptionMapper->findByPollAndUser($this->acl->getPoll()->getId(), $this->acl->getUserId()); // Subscription exists return true; } catch (DoesNotExistException $e) { @@ -67,7 +67,7 @@ public function set(bool $setToSubscribed, ?int $pollId = null): bool { if (!$setToSubscribed) { // user wants to unsubscribe, allow unsubscribe neverteheless the permissions are set try { - $subscription = $this->subscriptionMapper->findByPollAndUser($this->acl->getPollId(), $this->acl->getUserId()); + $subscription = $this->subscriptionMapper->findByPollAndUser($this->acl->getPoll()->getId(), $this->acl->getUserId()); $this->subscriptionMapper->delete($subscription); } catch (DoesNotExistException $e) { // Not found, assume already unsubscribed @@ -76,7 +76,7 @@ public function set(bool $setToSubscribed, ?int $pollId = null): bool { } else { try { $this->acl->request(Acl::PERMISSION_POLL_SUBSCRIBE); - $this->add($this->acl->getPollId(), $this->acl->getUserId()); + $this->add($this->acl->getPoll()->getId(), $this->acl->getUserId()); } catch (ForbiddenException $e) { return false; } catch (Exception $e) { diff --git a/lib/Service/VoteService.php b/lib/Service/VoteService.php index a8bb1a86c..096420e9c 100644 --- a/lib/Service/VoteService.php +++ b/lib/Service/VoteService.php @@ -63,10 +63,10 @@ public function list(?int $pollId = null): array { try { if (!$this->acl->getIsAllowed(Acl::PERMISSION_POLL_RESULTS_VIEW)) { // Just return the participants votes, no further anoymizing or obfuscating is nessecary - return $this->voteMapper->findByPollAndUser($this->acl->getpollId(), ($this->userMapper->getCurrentUserId())); + return $this->voteMapper->findByPollAndUser($this->acl->getPoll()->getId(), ($this->userMapper->getCurrentUserId())); } - $votes = $this->voteMapper->findByPoll($this->acl->getpollId()); + $votes = $this->voteMapper->findByPoll($this->acl->getPoll()->getId()); } catch (DoesNotExistException $e) { $votes = []; @@ -100,7 +100,7 @@ public function set(int $optionId, string $setTo): ?Vote { $deleteVoteInsteadOfNoVote = in_array(trim($setTo), [Vote::VOTE_NO, '']) && !boolval($this->acl->getPoll()->getUseNo()); try { - $this->vote = $this->voteMapper->findSingleVote($this->acl->getPollId(), $option->getPollOptionText(), $this->userMapper->getCurrentUserId()); + $this->vote = $this->voteMapper->findSingleVote($this->acl->getPoll()->getId(), $option->getPollOptionText(), $this->userMapper->getCurrentUserId()); if ($deleteVoteInsteadOfNoVote) { $this->vote->setVoteAnswer(''); @@ -113,7 +113,7 @@ public function set(int $optionId, string $setTo): ?Vote { // Vote does not exist, insert as new Vote $this->vote = new Vote(); - $this->vote->setPollId($this->acl->getPollId()); + $this->vote->setPollId($this->acl->getPoll()->getId()); $this->vote->setUserId($this->userMapper->getCurrentUserId()); $this->vote->setVoteOptionText($option->getPollOptionText()); $this->vote->setVoteOptionId($option->getId()); @@ -134,7 +134,7 @@ public function set(int $optionId, string $setTo): ?Vote { */ public function deleteCurrentUserFromPoll(bool $deleteOnlyOrphaned = false): string { $this->acl->request(Acl::PERMISSION_VOTE_EDIT); - $pollId = $this->acl->getPollId(); + $pollId = $this->acl->getPoll()->getId(); $userId = $this->userMapper->getCurrentUser()->getId(); return $this->delete($pollId, $userId, $deleteOnlyOrphaned); }