Passed
Pull Request — master (#1339)
by René
05:25 queued 01:34
created

ShareService::validate()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 28
ccs 0
cts 16
cp 0
rs 8.0555
cc 9
nc 9
nop 0
crap 90
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
namespace OCA\Polls\Service;
25
26
use OCP\AppFramework\Db\DoesNotExistException;
27
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
28
use OCA\Polls\Exceptions\NotAuthorizedException;
29
use OCA\Polls\Exceptions\InvalidShareTypeException;
30
use OCA\Polls\Exceptions\ShareAlreadyExistsException;
31
use OCA\Polls\Exceptions\NotFoundException;
32
33
use OCP\Security\ISecureRandom;
34
use OCP\IGroupManager;
35
use OCA\Polls\Db\ShareMapper;
36
use OCA\Polls\Db\Share;
37
use OCA\Polls\Model\Acl;
38
use OCA\Polls\Model\UserGroupClass;
39
40
class ShareService {
41
42
	/** @var string|null */
43
	private $userId;
44
45
	/** @var IGroupManager */
46
	private $groupManager;
47
48
	/** @var SystemService */
49
	private $systemService;
50
51
	/** @var ShareMapper */
52
	private $shareMapper;
53
54
	/** @var Share */
55
	private $share;
56
57
	/** @var MailService */
58
	private $mailService;
59
60
	/** @var Acl */
61
	private $acl;
62
63
	public function __construct(
64
		?string $UserId,
65
		IGroupManager $groupManager,
66
		SystemService $systemService,
67
		ShareMapper $shareMapper,
68
		Share $share,
69
		MailService $mailService,
70
		Acl $acl
71
	) {
72
		$this->userId = $UserId;
73
		$this->groupManager = $groupManager;
74
		$this->systemService = $systemService;
75
		$this->shareMapper = $shareMapper;
76
		$this->share = $share;
77
		$this->mailService = $mailService;
78
		$this->acl = $acl;
79
	}
80
81
	/**
82
	 * 	 * Read all shares of a poll based on the poll id and return list as array
83
	 *
84
	 * @return Share[]
85
	 *
86
	 * @psalm-return array<array-key, Share>
87
	 */
88
	public function list(int $pollId): array {
89
		try {
90
			$this->acl->setPollId($pollId)->request(Acl::PERMISSION_EDIT);
91
			$shares = $this->shareMapper->findByPoll($pollId);
92
		} catch (NotAuthorizedException $e) {
93
			return [];
94
		} catch (DoesNotExistException $e) {
95
			return [];
96
		}
97
98
		return $shares;
99
	}
100
101
	/**
102
	 * Validate share
103
	 */
104
	private function validate():void {
105
		switch ($this->share->getType()) {
106
			case Share::TYPE_PUBLIC:
107
				// public shares are alway valid
108
				break;
109
			case Share::TYPE_USER:
110
				if ($this->share->getUserId() !== $this->userId) {
111
					// share is not valid for user
112
					throw new NotAuthorizedException;
113
				}
114
				break;
115
			case Share::TYPE_GROUP:
116
				if (!\OC::$server->getUserSession()->isLoggedIn()) {
117
					throw new NotAuthorizedException;
118
				}
119
120
				if (!$this->groupManager->isInGroup($this->share->getUserId(), $this->userId)) {
121
					throw new NotAuthorizedException;
122
				}
123
124
				break;
125
			case Share::TYPE_EMAIL:
126
				break;
127
			case Share::TYPE_EXTERNAL:
128
				break;
129
			default:
130
				\OC::$server->getLogger()->alert(json_encode('invalid share type ' . $this->share->getType()));
131
				throw new NotAuthorizedException;
132
		}
133
	}
134
135
	/**
136
	 * Get share by token
137
	 */
138
	public function get(string $token, bool $validate = false) {
139
		try {
140
			$this->share = $this->shareMapper->findByToken($token);
141
			if ($validate) {
142
				$this->validate();
143
			}
144
		} catch (DoesNotExistException $e) {
145
			throw new NotFoundException('Token ' . $token . ' does not exist');
146
		}
147
		// Allow users entering the poll with a public share access
148
149
		if ($this->share->getType() === Share::TYPE_PUBLIC && \OC::$server->getUserSession()->isLoggedIn()) {
150
			try {
151
				// Test if the user has already access.
152
				$this->acl->setPollId($this->share->getPollId())->request(Acl::PERMISSION_VIEW);
153
			} catch (NotAuthorizedException $e) {
154
				// If he is not authorized until now, create a new personal share for this user.
155
				// Return the created share
156
				return $this->create(
157
					$this->share->getPollId(),
158
					UserGroupClass::getUserGroupChild(Share::TYPE_USER, \OC::$server->getUserSession()->getUser()->getUID()),
159
					true
160
				);
161
			}
162
		}
163
		return $this->share;
164
	}
165
166
	/**
167
	 * 	 * Get share by token
168
	 *
169
	 * @return Share
170
	 */
171
	public function setInvitationSent(string $token): Share {
172
		$share = $this->shareMapper->findByToken($token);
173
		$share->setInvitationSent(time());
174
		return $this->shareMapper->update($share);
175
	}
176
177
	/**
178
	 * 	 * crate share - MUST BE PRIVATE!
179
	 *
180
	 * @return Share
181
	 */
182
	private function create(int $pollId, UserGroupClass $userGroup, bool $preventInvitation = false): Share {
183
		$preventInvitation = $userGroup->getType() === UserGroupClass::TYPE_PUBLIC ? true : $preventInvitation;
184
		$token = \OC::$server->getSecureRandom()->generate(
185
			16,
186
			ISecureRandom::CHAR_DIGITS .
187
			ISecureRandom::CHAR_LOWER .
188
			ISecureRandom::CHAR_UPPER
189
		);
190
191
		$this->share = new Share();
192
		$this->share->setToken($token);
193
		$this->share->setPollId($pollId);
194
195
		// Convert user type contact to share type email
196
		if ($userGroup->getType() === UserGroupClass::TYPE_CONTACT) {
197
			$this->share->setType(Share::TYPE_EMAIL);
198
			$this->share->setUserId($userGroup->getEmailAddress());
199
		} else {
200
			$this->share->setType($userGroup->getType());
201
			$this->share->setUserId($userGroup->getType() === UserGroupClass::TYPE_PUBLIC ? $token : $userGroup->getPublicId());
202
		}
203
204
		$this->share->setInvitationSent($preventInvitation ? time() : 0);
205
		$this->share->setDisplayName($userGroup->getDisplayName());
206
		$this->share->setEmailAddress($userGroup->getEmailAddress());
207
208
		return $this->shareMapper->insert($this->share);
209
	}
210
211
	/**
212
	 * 	 * Add share
213
	 *
214
	 * @return Share
215
	 */
216
	public function add(int $pollId, string $type, string $userId = ''): Share {
217
		$this->acl->setPollId($pollId)->request(Acl::PERMISSION_EDIT);
218
219
		if ($type !== UserGroupClass::TYPE_PUBLIC) {
220
			try {
221
				$this->shareMapper->findByPollAndUser($pollId, $userId);
222
				throw new ShareAlreadyExistsException;
223
			} catch (MultipleObjectsReturnedException $e) {
224
				throw new ShareAlreadyExistsException;
225
			} catch (DoesNotExistException $e) {
226
				// continue
227
			}
228
		}
229
230
		$userGroup = UserGroupClass::getUserGroupChild($type, $userId);
231
		return $this->create($pollId, $userGroup);
232
	}
233
234
	/**
235
	 * 	 * Set emailAddress to personal share
236
	 * 	 * or update an email share with the username
237
	 *
238
	 * @return Share
239
	 */
240
	public function setEmailAddress(string $token, string $emailAddress): Share {
241
		try {
242
			$this->share = $this->shareMapper->findByToken($token);
243
		} catch (DoesNotExistException $e) {
244
			throw new NotFoundException('Token ' . $token . ' does not exist');
245
		}
246
247
		if ($this->share->getType() === Share::TYPE_EXTERNAL) {
248
			$this->systemService->validateEmailAddress($emailAddress);
249
			$this->share->setEmailAddress($emailAddress);
250
			// TODO: Send confirmation
251
			return $this->shareMapper->update($this->share);
252
		} else {
253
			throw new InvalidShareTypeException('Email address can only be set in external shares.');
254
		}
255
	}
256
257
	/**
258
	 * 	 * Create a personal share from a public share
259
	 * 	 * or update an email share with the username
260
	 *
261
	 * @return Share
262
	 */
263
	public function personal(string $token, string $userName, string $emailAddress = ''): Share {
264
		try {
265
			$this->share = $this->shareMapper->findByToken($token);
266
		} catch (DoesNotExistException $e) {
267
			throw new NotFoundException('Token ' . $token . ' does not exist');
268
		}
269
270
		$this->systemService->validatePublicUsername($userName, $token);
271
		$this->systemService->validateEmailAddress($emailAddress, true);
272
273
		if ($this->share->getType() === Share::TYPE_PUBLIC) {
274
			// Create new external share for user, who entered the poll via public link,
275
			// prevent invtation sending, when no email address is given
276
			$this->create(
277
				$this->share->getPollId(),
278
				UserGroupClass::getUserGroupChild(Share::TYPE_EXTERNAL, $userName, $userName, $emailAddress),
279
				!$emailAddress
280
			);
281
		} elseif ($this->share->getType() === Share::TYPE_EMAIL
282
				|| $this->share->getType() === Share::TYPE_CONTACT) {
283
			// Convert email and contact shares to external share, if user registers
284
			$this->share->setType(Share::TYPE_EXTERNAL);
285
			$this->share->setUserId($userName);
286
			$this->share->setDisplayName($userName);
287
288
			// prepare for resending inviataion to new email address
289
			if ($emailAddress !== $this->share->getEmailAddress()) {
290
				$this->share->setInvitationSent(0);
291
			}
292
			$this->share->setEmailAddress($emailAddress);
293
			$this->shareMapper->update($this->share);
294
		} else {
295
			throw new NotAuthorizedException;
296
		}
297
298
		// send invitaitoin mail, if invitationSent has no timestamp
299
		if (!$this->share->getInvitationSent()) {
300
			$this->mailService->resendInvitation($this->share->getToken());
301
		}
302
303
		return $this->share;
304
	}
305
306
	/**
307
	 * Delete share
308
	 */
309
	public function delete(string $token): string {
310
		try {
311
			$this->share = $this->shareMapper->findByToken($token);
312
			$this->acl->setPollId($this->share->getPollId())->request(Acl::PERMISSION_EDIT);
313
			$this->shareMapper->delete($this->share);
314
		} catch (DoesNotExistException $e) {
315
			// silently catch
316
		}
317
		return $token;
318
	}
319
}
320