Passed
Pull Request — master (#1340)
by René
08:20 queued 04:06
created

Acl::jsonSerialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 17
ccs 0
cts 13
cp 0
rs 9.7333
cc 1
nc 1
nop 0
crap 2
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));
0 ignored issues
show
Bug introduced by
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() {
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 jsonSerialize(): array {
217
		return	[
218
			'allowComment'      => $this->isAllowed(self::PERMISSION_COMMENT),
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
		];
234
	}
235
236
	/**
237
	 * getLoggedIn - Is user logged in to nextcloud?
238
	 */
239
	private function getLoggedIn(): bool {
240
		return \OC::$server->getUserSession()->isLoggedIn();
241
	}
242
243
	/**
244
	 * getIsOwner - Is user owner of the poll?
245
	 */
246
	private function getIsOwner(): bool {
247
		return ($this->getLoggedIn() && $this->poll->getOwner() === $this->getUserId());
248
	}
249
250
	/**
251
	 * getIsAdmin - Is the user admin
252
	 * Returns true, if user is in admin group
253
	 */
254
	private function getIsAdmin(): bool {
255
		return ($this->getLoggedIn() && $this->groupManager->isAdmin($this->getUserId()));
256
	}
257
258
	/**
259
	 * hasAdminAccess - Has user administrative rights?
260
	 * Returns true, if user is in admin group and poll has allowed admins to manage the poll
261
	 */
262
	private function hasAdminAccess(): bool {
263
		return ($this->getIsAdmin() && $this->poll->getAdminAccess());
264
	}
265
266
	/**
267
	 * getUserIsInvolved - Is user involved?
268
	 * Returns true, if the current user is involved in the share via share or if he is a participant.
269
	 */
270
	private function getUserIsInvolved(): bool {
271
		return (
272
			   $this->getIsOwner()
273
			|| $this->getUserHasVoted()
274
			|| $this->getGroupShare()
275
			|| $this->getPersonalShare());
276
	}
277
278
	/**
279
	 * getUserHasVoted - Is user a participant?
280
	 * Returns true, if the current user is already a particitipant of the current poll.
281
	 */
282
	private function getUserHasVoted(): bool {
283
		return count(
284
			$this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
285
		) > 0;
286
	}
287
288
	/**
289
	 * getGroupShare - Is the poll shared via group share?
290
	 * Returns true, if the current poll contains a group share with a group,
291
	 * where the current user is member of. This only affects logged users.
292
	 */
293
	private function getGroupShare(): int {
294
		if (!$this->getLoggedIn()) {
295
			return 0;
296
		}
297
		return count(
298
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
299
				if ($item->getType() === Share::TYPE_GROUP && $this->groupManager->isInGroup($this->getUserId(), $item->getUserId())) {
300
					return true;
301
				}
302
			})
303
		);
304
	}
305
306
	/**
307
	 * getPersonalShare - Is the poll shared via user share?
308
	 * Returns >0, if the current poll contains a user share for the current user.
309
	 * This only affects logged users.
310
	 */
311
	private function getPersonalShare(): int {
312
		if (!$this->getLoggedIn()) {
313
			return 0;
314
		}
315
		return count(
316
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
317
				if (in_array($item->getType(), [
318
					Share::TYPE_USER,
319
					Share::TYPE_EXTERNAL,
320
					Share::TYPE_EMAIL,
321
					Share::TYPE_CONTACT
322
				])
323
					&& $item->getUserId() === $this->getUserId()
324
				) {
325
					return true;
326
				}
327
			})
328
		);
329
	}
330
331
	private function validateShareAccess(): void {
332
		if ($this->getLoggedIn()) {
333
			if (!$this->getValidAuthenticatedShare()) {
334
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for external users');
335
			};
336
		} else {
337
			if (!$this->getValidPublicShare()) {
338
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for internal users');
339
			};
340
		}
341
	}
342
343
	private function getValidPublicShare(): bool {
344
		return in_array($this->share->getType(), [
345
			Share::TYPE_PUBLIC,
346
			Share::TYPE_EMAIL,
347
			Share::TYPE_CONTACT,
348
			Share::TYPE_EXTERNAL
349
		]);
350
	}
351
352
	private function getValidAuthenticatedShare(): bool {
353
		return in_array($this->share->getType(), [
354
			Share::TYPE_PUBLIC,
355
			Share::TYPE_USER,
356
			Share::TYPE_GROUP
357
		]);
358
	}
359
360
	private function hasEmail(): bool {
361
		if ($this->share->getToken()) {
362
			return strlen($this->share->getEmailAddress()) > 0;
363
		} else {
364
			return $this->getLoggedIn();
365
		}
366
	}
367
}
368