Issues (27)

lib/Model/Acl.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Vinzenz Rosenkranz <[email protected]>
4
 *
5
 * @author René Gieling <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 *  This program is free software: you can redistribute it and/or modify
10
 *  it under the terms of the GNU Affero General Public License as
11
 *  published by the Free Software Foundation, either version 3 of the
12
 *  License, or (at your option) any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU Affero General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU Affero General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
25
namespace OCA\Polls\Model;
26
27
use JsonSerializable;
28
use OCP\AppFramework\Db\DoesNotExistException;
29
use OCA\Polls\Exceptions\NotAuthorizedException;
30
31
use OCP\IUserManager;
32
use OCP\IGroupManager;
33
use OCA\Polls\Db\Poll;
34
use OCA\Polls\Db\Share;
35
use OCA\Polls\Db\PollMapper;
36
use OCA\Polls\Db\VoteMapper;
37
use OCA\Polls\Db\ShareMapper;
38
39
/**
40
 * Class Acl
41
 *
42
 * @package OCA\Polls\Model\Acl
43
 */
44
class Acl implements JsonSerializable {
45
	public const PERMISSION_VIEW = 'view';
46
	public const PERMISSION_EDIT = 'edit';
47
	public const PERMISSION_ADD_OPTIONS = 'add_options';
48
	public const PERMISSION_DELETE = 'delete';
49
	public const PERMISSION_COMMENT = 'comment';
50
	public const PERMISSION_SUBSCRIBE = 'subscribe';
51
	public const PERMISSION_VOTE = 'vote';
52
	public const PERMISSION_SEE_RESULTS = 'seeResults';
53
	public const PERMISSION_SEE_USERNAMES = 'seeUserNames';
54
	public const PERMISSION_TAKE_OVER = 'takeOver';
55
56
	/** @var IUserManager */
57
	private $userManager;
58
59
	/** @var IGroupManager */
60
	private $groupManager;
61
62
	/** @var PollMapper */
63
	private $pollMapper;
64
65
	/** @var VoteMapper */
66
	private $voteMapper;
67
68
	/** @var ShareMapper */
69
	private $shareMapper;
70
71
	/** @var Poll */
72
	private $poll;
73
74
	/** @var Share */
75
	private $share;
76
77
	public function __construct(
78
		IUserManager $userManager,
79
		IGroupManager $groupManager,
80
		PollMapper $pollMapper,
81
		VoteMapper $voteMapper,
82
		ShareMapper $shareMapper
83
	) {
84
		$this->userManager = $userManager;
85
		$this->groupManager = $groupManager;
86
		$this->pollMapper = $pollMapper;
87
		$this->voteMapper = $voteMapper;
88
		$this->shareMapper = $shareMapper;
89
		$this->poll = new Poll;
90
		$this->share = new Share;
91
	}
92
93
94
	/**
95
	 * load share via token and than call setShare
96
	 */
97
	public function setToken(string $token = ''): Acl {
98
		try {
99
			return $this->setShare($this->shareMapper->findByToken($token));
100
		} catch (DoesNotExistException $e) {
101
			throw new NotAuthorizedException('Error loading share ' . $token);
102
		}
103
	}
104
105
	/**
106
	 * setShare - sets and validates the share
107
	 * read access is
108
	 */
109
	public function setShare(Share $share): Acl {
110
		$this->share = $share;
111
		$this->validateShareAccess();
112
		$this->setPollId($share->getPollId());
113
		$this->request(self::PERMISSION_VIEW);
114
		return $this;
115
	}
116
117
	public function getToken(): string {
118
		return strval($this->share->getToken());
119
	}
120
121
	public function setPollId(?int $pollId = 0): Acl {
122
		try {
123
			return $this->setPoll($this->pollMapper->find($pollId));
0 ignored issues
show
It seems like $pollId can also be of type null; however, parameter $id of OCA\Polls\Db\PollMapper::find() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
			return $this->setPoll($this->pollMapper->find(/** @scrutinizer ignore-type */ $pollId));
Loading history...
124
		} catch (DoesNotExistException $e) {
125
			throw new NotAuthorizedException('Error loading poll ' . $pollId);
126
		}
127
	}
128
129
	public function setPoll(Poll $poll): Acl {
130
		$this->poll = $poll;
131
		return $this;
132
	}
133
134
	public function getPollId(): int {
135
		return $this->poll->getId();
136
	}
137
138
	public function getUserId(): string {
139
		return $this->getLoggedIn() ? \OC::$server->getUserSession()->getUser()->getUID() : $this->share->getUserId();
140
	}
141
142
	public function validateUserId(string $userId): void {
143
		if ($this->getUserId() !== $userId) {
144
			throw new NotAuthorizedException;
145
		}
146
	}
147
148
	/**
149
	 * getIsOwner - Is user owner of the poll?
150
	 */
151
	public function getIsOwner(): bool {
152
		return ($this->getLoggedIn() && $this->poll->getOwner() === $this->getUserId());
153
	}
154
155
	private function getDisplayName(): string {
156
		return $this->getLoggedIn() ? $this->userManager->get($this->getUserId())->getDisplayName() : $this->share->getDisplayName();
157
	}
158
159
	public function isAllowed(string $permission): bool {
160
		switch ($permission) {
161
			case self::PERMISSION_VIEW:
162
				if ($this->getIsOwner() || $this->hasAdminAccess()) {
163
					// always grant access, if user has edit rights
164
					return true;
165
				} elseif ($this->poll->getDeleted()) {
166
					// always deny access, if poll is deleted
167
					return false;
168
				} elseif ($this->poll->getAccess() === Poll::ACCESS_PUBLIC) {
169
					// grant access if poll poll is public
170
					return true;
171
				} elseif ($this->getUserIsInvolved()) {
172
					// grant access if user is involved in poll in any way
173
					return true;
174
				} elseif ($this->getToken()) {
175
					// user has token
176
					return true;
177
				}
178
				break;
179
180
			case self::PERMISSION_EDIT:
181
				return $this->getIsOwner() || $this->hasAdminAccess();
182
			case self::PERMISSION_ADD_OPTIONS:
183
				return $this->getIsOwner()
184
					|| $this->hasAdminAccess()
185
					|| ($this->poll->getAllowProposals() === Poll::PROPOSAL_ALLOW
186
					&& !$this->poll->getProposalsExpired());
187
			case self::PERMISSION_DELETE:
188
				return $this->getIsOwner() || $this->hasAdminAccess() || $this->getIsAdmin();
189
			case self::PERMISSION_COMMENT:
190
				return $this->share->getType() !== Share::TYPE_PUBLIC && $this->poll->getallowComment();
191
			case self::PERMISSION_SUBSCRIBE:
192
				return $this->hasEmail();
193
			case self::PERMISSION_VOTE:
194
				return !$this->poll->getExpired() && $this->share->getType() !== Share::TYPE_PUBLIC;
195
			case self::PERMISSION_SEE_RESULTS:
196
				return $this->getIsOwner()
197
					|| $this->poll->getShowResults() === Poll::SHOW_RESULTS_ALWAYS
198
					|| $this->poll->getShowResults() === Poll::SHOW_RESULTS_CLOSED && $this->poll->getExpired();
199
			case self::PERMISSION_SEE_USERNAMES:
200
				return $this->getIsOwner() || !$this->poll->getAnonymous();
201
			case self::PERMISSION_TAKE_OVER:
202
				return $this->getIsAdmin();
203
			default:
204
				break;
205
		}
206
		return false;
207
	}
208
209
	public function request(string $permission): void {
210
		if (!$this->isAllowed($permission)) {
211
			throw new NotAuthorizedException('denied permission ' . $permission);
212
		}
213
	}
214
215
	public function jsonSerialize(): array {
216
		return	[
217
			'allowComment' => $this->isAllowed(self::PERMISSION_COMMENT),
218
			'allowAddOptions' => $this->isAllowed(self::PERMISSION_ADD_OPTIONS),
219
			'allowEdit' => $this->isAllowed(self::PERMISSION_EDIT),
220
			'allowSeeResults' => $this->isAllowed(self::PERMISSION_SEE_RESULTS),
221
			'allowSeeUsernames' => $this->isAllowed(self::PERMISSION_SEE_USERNAMES),
222
			'allowSubscribe' => $this->isAllowed(self::PERMISSION_SUBSCRIBE),
223
			'allowView' => $this->isAllowed(self::PERMISSION_VIEW),
224
			'allowVote' => $this->isAllowed(self::PERMISSION_VOTE),
225
			'displayName' => $this->getDisplayName(),
226
			'isOwner' => $this->getIsOwner(),
227
			'loggedIn' => $this->getLoggedIn(),
228
			'pollId' => $this->getPollId(),
229
			'token' => $this->getToken(),
230
			'userHasVoted' => $this->getUserHasVoted(),
231
			'userId' => $this->getUserId(),
232
			'userIsInvolved' => $this->getUserIsInvolved(),
233
			'pollExpired' => $this->poll->getExpired(),
234
			'pollExpire' => $this->poll->getExpire(),
235
		];
236
	}
237
238
	/**
239
	 * getLoggedIn - Is user logged in to nextcloud?
240
	 */
241
	private function getLoggedIn(): bool {
242
		return \OC::$server->getUserSession()->isLoggedIn();
243
	}
244
245
	/**
246
	 * getIsAdmin - Is the user admin
247
	 * Returns true, if user is in admin group
248
	 */
249
	private function getIsAdmin(): bool {
250
		return ($this->getLoggedIn() && $this->groupManager->isAdmin($this->getUserId()));
251
	}
252
253
	/**
254
	 * hasAdminAccess - Has user administrative rights?
255
	 * Returns true, if user is in admin group and poll has allowed admins to manage the poll,
256
	 * or when running console commands.
257
	 */
258
	private function hasAdminAccess(): bool {
259
		return (($this->getIsAdmin() && $this->poll->getAdminAccess()) || defined('OC_CONSOLE'));
260
	}
261
262
	/**
263
	 * getUserIsInvolved - Is user involved?
264
	 * Returns true, if the current user is involved in the share via share or if he is a participant.
265
	 */
266
	private function getUserIsInvolved(): bool {
267
		return (
268
			   $this->getIsOwner()
269
			|| $this->getUserHasVoted()
270
			|| $this->getGroupShare()
271
			|| $this->getPersonalShare());
272
	}
273
274
	/**
275
	 * getUserHasVoted - Is user a participant?
276
	 * Returns true, if the current user is already a particitipant of the current poll.
277
	 */
278
	private function getUserHasVoted(): bool {
279
		return count(
280
			$this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
281
		) > 0;
282
	}
283
284
	/**
285
	 * getGroupShare - Is the poll shared via group share?
286
	 * Returns true, if the current poll contains a group share with a group,
287
	 * where the current user is member of. This only affects logged users.
288
	 */
289
	private function getGroupShare(): int {
290
		if (!$this->getLoggedIn()) {
291
			return 0;
292
		}
293
		return count(
294
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
295
				return ($item->getType() === Share::TYPE_GROUP && $this->groupManager->isInGroup($this->getUserId(), $item->getUserId()));
296
			})
297
		);
298
	}
299
300
	/**
301
	 * getPersonalShare - Is the poll shared via user share?
302
	 * Returns >0, if the current poll contains a user share for the current user.
303
	 * This only affects logged users.
304
	 */
305
	private function getPersonalShare(): int {
306
		if (!$this->getLoggedIn()) {
307
			return 0;
308
		}
309
		return count(
310
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
311
				return ($item->getUserId() === $this->getUserId()
312
					&& in_array($item->getType(), [
313
						Share::TYPE_USER,
314
						Share::TYPE_EXTERNAL,
315
						Share::TYPE_EMAIL,
316
						Share::TYPE_CONTACT
317
					])
318
				);
319
			})
320
		);
321
	}
322
323
	private function validateShareAccess(): void {
324
		if ($this->getLoggedIn()) {
325
			if (!$this->getValidAuthenticatedShare()) {
326
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for external users');
327
			};
328
		} else {
329
			if (!$this->getValidPublicShare()) {
330
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for internal users');
331
			};
332
		}
333
	}
334
335
	private function getValidPublicShare(): bool {
336
		return in_array($this->share->getType(), [
337
			Share::TYPE_PUBLIC,
338
			Share::TYPE_EMAIL,
339
			Share::TYPE_CONTACT,
340
			Share::TYPE_EXTERNAL
341
		]);
342
	}
343
344
	private function getValidAuthenticatedShare(): bool {
345
		return in_array($this->share->getType(), [
346
			Share::TYPE_PUBLIC,
347
			Share::TYPE_USER,
348
			Share::TYPE_GROUP
349
		]);
350
	}
351
352
	private function hasEmail(): bool {
353
		return $this->share->getToken() ? strlen($this->share->getEmailAddress()) > 0 : $this->getLoggedIn();
354
	}
355
}
356