Completed
Push — master ( cf3397...88c060 )
by René
24s queued 11s
created

Acl   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 71
eloc 154
dl 0
loc 320
ccs 0
cts 159
cp 0
rs 2.7199
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A validateUserId() 0 3 2
A getIsAdmin() 0 2 2
A getPollId() 0 2 1
A setToken() 0 5 2
A getPersonalShare() 0 15 4
A getLoggedIn() 0 2 1
A getUserId() 0 5 2
A getIsOwner() 0 2 2
A hasEmail() 0 5 2
A getDisplayName() 0 5 2
A request() 0 3 2
D isAllowed() 0 48 25
A setShare() 0 6 1
A setPoll() 0 3 1
A getUserHasVoted() 0 4 1
A setPollId() 0 5 2
A getUserIsInvolved() 0 6 4
A getGroupShare() 0 8 4
A getValidPublicShare() 0 6 1
A getToken() 0 2 1
A hasAdminAccess() 0 2 2
A validateShareAccess() 0 8 4
A getValidAuthenticatedShare() 0 5 1
A jsonSerialize() 0 17 1

How to fix   Complexity   

Complex Class

Complex classes like Acl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Acl, and based on these observations, apply Extract Interface, too.

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_DELETE ='delete';
48
	public const PERMISSION_COMMENT ='comment';
49
	public const PERMISSION_SUBSCRIBE ='subscribe';
50
	public const PERMISSION_VOTE ='vote';
51
	public const PERMISSION_SEE_RESULTS ='seeResults';
52
	public const PERMISSION_SEE_USERNAMES ='seeUserNames';
53
	public const PERMISSION_TAKE_OVER ='takeOver';
54
55
	/** @var IUserManager */
56
	private $userManager;
57
58
	/** @var IGroupManager */
59
	private $groupManager;
60
61
	/** @var PollMapper */
62
	private $pollMapper;
63
64
	/** @var VoteMapper */
65
	private $voteMapper;
66
67
	/** @var ShareMapper */
68
	private $shareMapper;
69
70
	/** @var Poll */
71
	private $poll;
72
73
	/** @var Share */
74
	private $share;
75
76
	public function __construct(
77
		IUserManager $userManager,
78
		IGroupManager $groupManager,
79
		PollMapper $pollMapper,
80
		VoteMapper $voteMapper,
81
		ShareMapper $shareMapper
82
	) {
83
		$this->userManager = $userManager;
84
		$this->groupManager = $groupManager;
85
		$this->pollMapper = $pollMapper;
86
		$this->voteMapper = $voteMapper;
87
		$this->shareMapper = $shareMapper;
88
		$this->poll = new Poll;
89
		$this->share = new Share;
90
	}
91
92
93
	/**
94
	 * load share via token and than call setShare
95
	 */
96
	public function setToken(string $token = ''): Acl {
97
		try {
98
			return $this->setShare($this->shareMapper->findByToken($token));
99
		} catch (DoesNotExistException $e) {
100
			throw new NotAuthorizedException('Error loading share ' . $token);
101
		}
102
	}
103
104
	/**
105
	 * setShare - sets and validates the share
106
	 * read access is
107
	 */
108
	public function setShare(Share $share): Acl {
109
		$this->share = $share;
110
		$this->validateShareAccess();
111
		$this->setPollId($share->getPollId());
112
		$this->request(self::PERMISSION_VIEW);
113
		return $this;
114
	}
115
116
	public function getToken(): string {
117
		return strval($this->share->getToken());
118
	}
119
120
	public function setPollId(?int $pollId = 0): Acl {
121
		try {
122
			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

122
			return $this->setPoll($this->pollMapper->find(/** @scrutinizer ignore-type */ $pollId));
Loading history...
123
		} catch (DoesNotExistException $e) {
124
			throw new NotAuthorizedException('Error loading poll ' . $pollId);
125
		}
126
	}
127
128
	public function setPoll(Poll $poll): Acl {
129
		$this->poll = $poll;
130
		return $this;
131
	}
132
133
	public function getPollId(): int {
134
		return $this->poll->getId();
135
	}
136
137
	public function getUserId() {
138
		if ($this->getLoggedIn()) {
139
			return \OC::$server->getUserSession()->getUser()->getUID();
140
		} else {
141
			return $this->share->getUserId();
142
		}
143
	}
144
145
	public function validateUserId(string $userId): void {
146
		if ($this->getUserId() !== $userId) {
147
			throw new NotAuthorizedException;
148
		}
149
	}
150
151
	private function getDisplayName(): string {
152
		if ($this->getLoggedIn()) {
153
			return $this->userManager->get($this->getUserId())->getDisplayName();
154
		} else {
155
			return $this->share->getDisplayName();
156
		}
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_DELETE:
183
				return $this->getIsOwner() || $this->hasAdminAccess() || $this->getIsAdmin();
184
			case self::PERMISSION_COMMENT:
185
				return true;
186
			case self::PERMISSION_SUBSCRIBE:
187
				return $this->hasEmail();
188
			case self::PERMISSION_VOTE:
189
				return !$this->poll->getExpired() && $this->share->getType() !== Share::TYPE_PUBLIC;
190
			case self::PERMISSION_SEE_RESULTS:
191
				if ($this->getIsOwner()) {
192
					return true;
193
				} elseif ($this->poll->getShowResults() === Poll::SHOW_RESULTS_ALWAYS) {
194
					return true;
195
				} elseif ($this->poll->getShowResults() === Poll::SHOW_RESULTS_CLOSED && $this->poll->getExpired()) {
196
					return true;
197
				}
198
				break;
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
			'allowEdit'         => $this->isAllowed(self::PERMISSION_EDIT),
219
			'allowSeeResults'   => $this->isAllowed(self::PERMISSION_SEE_RESULTS),
220
			'allowSeeUsernames' => $this->isAllowed(self::PERMISSION_SEE_USERNAMES),
221
			'allowSubscribe'    => $this->isAllowed(self::PERMISSION_SUBSCRIBE),
222
			'allowView'         => $this->isAllowed(self::PERMISSION_VIEW),
223
			'allowVote'         => $this->isAllowed(self::PERMISSION_VOTE),
224
			'displayName'       => $this->getDisplayName(),
225
			'isOwner'           => $this->getIsOwner(),
226
			'loggedIn'			=> $this->getLoggedIn(),
227
			'pollId'            => $this->getPollId(),
228
			'token'             => $this->getToken(),
229
			'userHasVoted'		=> $this->getUserHasVoted(),
230
			'userId'            => $this->getUserId(),
231
			'userIsInvolved'	=> $this->getUserIsInvolved(),
232
		];
233
	}
234
235
	/**
236
	 * getLoggedIn - Is user logged in to nextcloud?
237
	 */
238
	private function getLoggedIn(): bool {
239
		return \OC::$server->getUserSession()->isLoggedIn();
240
	}
241
242
	/**
243
	 * getIsOwner - Is user owner of the poll?
244
	 */
245
	private function getIsOwner(): bool {
246
		return ($this->getLoggedIn() && $this->poll->getOwner() === $this->getUserId());
247
	}
248
249
	/**
250
	 * getIsAdmin - Is the user admin
251
	 * Returns true, if user is in admin group
252
	 */
253
	private function getIsAdmin(): bool {
254
		return ($this->getLoggedIn() && $this->groupManager->isAdmin($this->getUserId()));
255
	}
256
257
	/**
258
	 * hasAdminAccess - Has user administrative rights?
259
	 * Returns true, if user is in admin group and poll has allowed admins to manage the poll
260
	 */
261
	private function hasAdminAccess(): bool {
262
		return ($this->getIsAdmin() && $this->poll->getAdminAccess());
263
	}
264
265
	/**
266
	 * getUserIsInvolved - Is user involved?
267
	 * Returns true, if the current user is involved in the share via share or if he is a participant.
268
	 */
269
	private function getUserIsInvolved(): bool {
270
		return (
271
			   $this->getIsOwner()
272
			|| $this->getUserHasVoted()
273
			|| $this->getGroupShare()
274
			|| $this->getPersonalShare());
275
	}
276
277
	/**
278
	 * getUserHasVoted - Is user a participant?
279
	 * Returns true, if the current user is already a particitipant of the current poll.
280
	 */
281
	private function getUserHasVoted(): bool {
282
		return count(
283
			$this->voteMapper->findParticipantsVotes($this->getPollId(), $this->getUserId())
284
		) > 0;
285
	}
286
287
	/**
288
	 * getGroupShare - Is the poll shared via group share?
289
	 * Returns true, if the current poll contains a group share with a group,
290
	 * where the current user is member of. This only affects logged users.
291
	 */
292
	private function getGroupShare(): int {
293
		if (!$this->getLoggedIn()) {
294
			return 0;
295
		}
296
		return count(
297
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
298
				if ($item->getType() === Share::TYPE_GROUP && $this->groupManager->isInGroup($this->getUserId(), $item->getUserId())) {
299
					return true;
300
				}
301
			})
302
		);
303
	}
304
305
	/**
306
	 * getPersonalShare - Is the poll shared via user share?
307
	 * Returns >0, if the current poll contains a user share for the current user.
308
	 * This only affects logged users.
309
	 */
310
	private function getPersonalShare(): int {
311
		if (!$this->getLoggedIn()) {
312
			return 0;
313
		}
314
		return count(
315
			array_filter($this->shareMapper->findByPoll($this->getPollId()), function ($item) {
316
				if (in_array($item->getType(), [
317
					Share::TYPE_USER,
318
					Share::TYPE_EXTERNAL,
319
					Share::TYPE_EMAIL,
320
					Share::TYPE_CONTACT
321
				])
322
					&& $item->getUserId() === $this->getUserId()
323
				) {
324
					return true;
325
				}
326
			})
327
		);
328
	}
329
330
	private function validateShareAccess(): void {
331
		if ($this->getLoggedIn()) {
332
			if (!$this->getValidAuthenticatedShare()) {
333
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for external users');
334
			};
335
		} else {
336
			if (!$this->getValidPublicShare()) {
337
				throw new NotAuthorizedException('Share type "' . $this->share->getType() . '"only valid for internal users');
338
			};
339
		}
340
	}
341
342
	private function getValidPublicShare(): bool {
343
		return in_array($this->share->getType(), [
344
			Share::TYPE_PUBLIC,
345
			Share::TYPE_EMAIL,
346
			Share::TYPE_CONTACT,
347
			Share::TYPE_EXTERNAL
348
		]);
349
	}
350
351
	private function getValidAuthenticatedShare(): bool {
352
		return in_array($this->share->getType(), [
353
			Share::TYPE_PUBLIC,
354
			Share::TYPE_USER,
355
			Share::TYPE_GROUP
356
		]);
357
	}
358
359
	private function hasEmail(): bool {
360
		if ($this->share->getToken()) {
361
			return strlen($this->share->getEmailAddress()) > 0;
362
		} else {
363
			return $this->getLoggedIn();
364
		}
365
	}
366
}
367