Passed
Pull Request — master (#1339)
by René
04:16
created

Acl::requestTakeOver()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
cc 2
nc 2
nop 0
crap 6
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
use OCA\Polls\Exceptions\VoteLimitExceededException;
31
32
use OCP\IUserManager;
33
use OCP\IGroupManager;
34
use OCA\Polls\Db\Poll;
35
use OCA\Polls\Db\Share;
36
use OCA\Polls\Db\PollMapper;
37
use OCA\Polls\Db\VoteMapper;
38
use OCA\Polls\Db\ShareMapper;
39
40
/**
41
 * Class Acl
42
 *
43
 * @package OCA\Polls\Model\Acl
44
 */
45
class Acl implements JsonSerializable {
46
	public const PERMISSION_VIEW ='view';
47
	public const PERMISSION_EDIT ='edit';
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));
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() {
139
		if ($this->getLoggedIn()) {
140
			return \OC::$server->getUserSession()->getUser()->getUID();
141
		} else {
142
			return $this->share->getUserId();
143
		}
144
	}
145
146
	public function validateUserId(string $userId): void {
147
		if ($this->getUserId() !== $userId) {
148
			throw new NotAuthorizedException;
149
		}
150
	}
151
152
	private function getDisplayName(): string {
153
		if ($this->getLoggedIn()) {
154
			return $this->userManager->get($this->getUserId())->getDisplayName();
155
		} else {
156
			return $this->share->getDisplayName();
157
		}
158
	}
159
160
	public function isAllowed(string $permission): bool {
161
		switch ($permission) {
162
			case Self::PERMISSION_VIEW:
163
				if ($this->getIsOwner() || $this->hasAdminAccess()) {
164
					// always grant access, if user has edit rights
165
					return true;
166
				} elseif ($this->poll->getDeleted()) {
167
					// always deny access, if poll is deleted
168
					return false;
169
				} elseif ($this->poll->getAccess() === Poll::ACCESS_PUBLIC) {
170
					// grant access if poll poll is public
171
					return true;
172
				} elseif ($this->getUserIsInvolved()) {
173
					// grant access if user is involved in poll in any way
174
					return true;
175
				} elseif ($this->getToken()) {
176
					// user has token
177
					return true;
178
				}
179
				break;
180
181
			case Self::PERMISSION_EDIT:
182
				return $this->getIsOwner() || $this->hasAdminAccess();
183
			case Self::PERMISSION_DELETE:
184
				return $this->getIsOwner() || $this->hasAdminAccess() || $this->getIsAdmin();
185
			case Self::PERMISSION_COMMENT:
186
				return true;
187
			case Self::PERMISSION_SUBSCRIBE:
188
				return $this->hasEmail();
189
			case Self::PERMISSION_VOTE:
190
				return !$this->poll->getExpired() && $this->share->getType() !== Share::TYPE_PUBLIC;
191
			case Self::PERMISSION_SEE_RESULTS:
192
				if ($this->getIsOwner()) {
193
					return true;
194
				} elseif ($this->poll->getShowResults() === Poll::SHOW_RESULTS_ALWAYS) {
195
					return true;
196
				} elseif ($this->poll->getShowResults() === Poll::SHOW_RESULTS_CLOSED && $this->poll->getExpired()) {
197
					return true;
198
				}
199
				break;
200
			case Self::PERMISSION_SEE_USERNAMES:
201
				return $this->getIsOwner() || !$this->poll->getAnonymous();
202
			case Self::PERMISSION_TAKE_OVER:
203
				return $this->getIsAdmin();
204
			default:
205
				break;
206
		}
207
		return false;
208
	}
209
210
	public function request(string $permission): void {
211
		if (!$this->isAllowed($permission)) {
212
			throw new NotAuthorizedException('denied permission ' . $permission);
213
		}
214
	}
215
216
	public function getAllowYesVote(): bool {
217
		return !($this->poll->getVoteLimit() && $this->getYesVotes() >= $this->poll->getVoteLimit());
218
	}
219
220
	public function getOptionLimit(): int {
221
		return $this->poll->getOptionLimit();
222
	}
223
224
	private function getYesVotes(): int {
225
		return $this->voteMapper->countYesVotes($this->getUserId(), $this->getPollId());
226
	}
227
228
	public function requestYesVotes(): void {
229
		if (!$this->getAllowYesVote()) {
230
			throw new VoteLimitExceededException;
231
		}
232
	}
233
234
	public function jsonSerialize(): array {
235
		return	[
236
			'allowComment'      => $this->isAllowed(Self::PERMISSION_COMMENT),
237
			'allowEdit'         => $this->isAllowed(Self::PERMISSION_EDIT),
238
			'allowSeeResults'   => $this->isAllowed(Self::PERMISSION_SEE_RESULTS),
239
			'allowSeeUsernames' => $this->isAllowed(Self::PERMISSION_SEE_USERNAMES),
240
			'allowSubscribe'    => $this->isAllowed(Self::PERMISSION_SUBSCRIBE),
241
			'allowView'         => $this->isAllowed(Self::PERMISSION_VIEW),
242
			'allowVote'         => $this->isAllowed(Self::PERMISSION_VOTE),
243
			'displayName'       => $this->getDisplayName(),
244
			'isOwner'           => $this->getIsOwner(),
245
			'loggedIn'			=> $this->getLoggedIn(),
246
			'pollId'            => $this->getPollId(),
247
			'token'             => $this->getToken(),
248
			'userHasVoted'		=> $this->getUserHasVoted(),
249
			'userId'            => $this->getUserId(),
250
			'userIsInvolved'	=> $this->getUserIsInvolved(),
251
		];
252
	}
253
254
	/**
255
	 * getLoggedIn - Is user logged in to nextcloud?
256
	 */
257
	private function getLoggedIn(): bool {
258
		return \OC::$server->getUserSession()->isLoggedIn();
259
	}
260
261
	/**
262
	 * getIsOwner - Is user owner of the poll?
263
	 */
264
	private function getIsOwner(): bool {
265
		return ($this->getLoggedIn() && $this->poll->getOwner() === $this->getUserId());
266
	}
267
268
	/**
269
	 * getIsAdmin - Is the user admin
270
	 * Returns true, if user is in admin group
271
	 */
272
	private function getIsAdmin(): bool {
273
		return ($this->getLoggedIn() && $this->groupManager->isAdmin($this->getUserId()));
274
	}
275
276
	/**
277
	 * hasAdminAccess - Has user administrative rights?
278
	 * Returns true, if user is in admin group and poll has allowed admins to manage the poll
279
	 */
280
	private function hasAdminAccess(): bool {
281
		return ($this->getIsAdmin() && $this->poll->getAdminAccess());
282
	}
283
284
	/**
285
	 * getUserIsInvolved - Is user involved?
286
	 * Returns true, if the current user is involved in the share via share or if he is a participant.
287
	 */
288
	private function getUserIsInvolved(): bool {
289
		return (
290
			   $this->getIsOwner()
291
			|| $this->getUserHasVoted()
292
			|| $this->getGroupShare()
293
			|| $this->getPersonalShare());
294
	}
295
296
	/**
297
	 * getUserHasVoted - Is user a participant?
298
	 * Returns true, if the current user is already a particitipant of the current poll.
299
	 */
300
	private function getUserHasVoted(): bool {
301
		return count(
302
			$this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
303
		) > 0;
304
	}
305
306
	/**
307
	 * getGroupShare - Is the poll shared via group share?
308
	 * Returns true, if the current poll contains a group share with a group,
309
	 * where the current user is member of. This only affects logged users.
310
	 */
311
	private function getGroupShare(): int {
312
		if (!$this->getLoggedIn()) {
313
			return 0;
314
		}
315
		return count(
316
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
317
				if ($item->getType() === Share::TYPE_GROUP && $this->groupManager->isInGroup($this->getUserId(), $item->getUserId())) {
318
					return true;
319
				}
320
			})
321
		);
322
	}
323
324
	/**
325
	 * getPersonalShare - Is the poll shared via user share?
326
	 * Returns >0, if the current poll contains a user share for the current user.
327
	 * This only affects logged users.
328
	 */
329
	private function getPersonalShare(): int {
330
		if (!$this->getLoggedIn()) {
331
			return 0;
332
		}
333
		return count(
334
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
335
				if (in_array($item->getType(), [
336
					Share::TYPE_USER,
337
					Share::TYPE_EXTERNAL,
338
					Share::TYPE_EMAIL,
339
					Share::TYPE_CONTACT
340
				])
341
					&& $item->getUserId() === $this->getUserId()
342
				) {
343
					return true;
344
				}
345
			})
346
		);
347
	}
348
349
	private function getPublicShare(): int {
0 ignored issues
show
Unused Code introduced by
The method getPublicShare() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
350
		return count(
351
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
352
				if ($item->getType() === Share::TYPE_PUBLIC && $item->getToken() === $this->getToken()) {
353
					return true;
354
				}
355
			})
356
		);
357
	}
358
359
	private function validateShareAccess(): void {
360
		if ($this->getLoggedIn()) {
361
			if (!$this->getValidAuthenticatedShare()) {
362
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for external users');
363
			};
364
		} else {
365
			if (!$this->getValidPublicShare()) {
366
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for internal users');
367
			};
368
		}
369
	}
370
371
	private function getValidPublicShare(): bool {
372
		return in_array($this->share->getType(), [
373
			Share::TYPE_PUBLIC,
374
			Share::TYPE_EMAIL,
375
			Share::TYPE_CONTACT,
376
			Share::TYPE_EXTERNAL
377
		]);
378
	}
379
380
	private function getValidAuthenticatedShare(): bool {
381
		return in_array($this->share->getType(), [
382
			Share::TYPE_PUBLIC,
383
			Share::TYPE_USER,
384
			Share::TYPE_GROUP
385
		]);
386
	}
387
388
	private function hasEmail(): bool {
389
		if ($this->share->getToken()) {
390
			return strlen($this->share->getEmailAddress()) > 0;
391
		} else {
392
			return $this->getLoggedIn();
393
		}
394
	}
395
}
396