Passed
Pull Request — master (#1443)
by René
05:04
created

ShareService::deleteEmailAddress()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
ccs 0
cts 0
cp 0
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
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 ?: $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, bool $emptyIsValid = false): 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, $emptyIsValid);
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
	 * Delete emailAddress of personal share
259
	 *
260
	 * @return Share
261
	 */
262
	public function deleteEmailAddress(string $token): Share {
263
		try {
264
			$this->share = $this->shareMapper->findByToken($token);
265
		} catch (DoesNotExistException $e) {
266
			throw new NotFoundException('Token ' . $token . ' does not exist');
267
		}
268
269
		if ($this->share->getType() === Share::TYPE_EXTERNAL) {
270
			$this->share->setEmailAddress('');
271
			return $this->shareMapper->update($this->share);
272
		} else {
273
			throw new InvalidShareTypeException('Email address can only be set in external shares.');
274
		}
275
	}
276
277
	/**
278
	 * Create a personal share from a public share
279
	 * or update an email share with the username
280
	 *
281
	 * @return Share
282
	 */
283
	public function personal(string $token, string $userName, string $emailAddress = ''): Share {
284
		try {
285
			$this->share = $this->shareMapper->findByToken($token);
286
		} catch (DoesNotExistException $e) {
287
			throw new NotFoundException('Token ' . $token . ' does not exist');
288
		}
289
290
		$this->systemService->validatePublicUsername($userName, $token);
291
		$this->systemService->validateEmailAddress($emailAddress, true);
292
293
		if ($this->share->getType() === Share::TYPE_PUBLIC) {
294
			// Create new external share for user, who entered the poll via public link,
295
			// prevent invtation sending, when no email address is given
296
			$this->create(
297
				$this->share->getPollId(),
298
				UserGroupClass::getUserGroupChild(Share::TYPE_EXTERNAL, $userName, $userName, $emailAddress),
299
				!$emailAddress
300
			);
301
		} elseif ($this->share->getType() === Share::TYPE_EMAIL
302
				|| $this->share->getType() === Share::TYPE_CONTACT) {
303
			// Convert email and contact shares to external share, if user registers
304
			$this->share->setType(Share::TYPE_EXTERNAL);
305
			$this->share->setUserId($userName);
306
			$this->share->setDisplayName($userName);
307
308
			// prepare for resending inviataion to new email address
309
			if ($emailAddress !== $this->share->getEmailAddress()) {
310
				$this->share->setInvitationSent(0);
311
			}
312
			$this->share->setEmailAddress($emailAddress);
313
			$this->shareMapper->update($this->share);
314
		} else {
315
			throw new NotAuthorizedException;
316
		}
317
318
		// send invitaitoin mail, if invitationSent has no timestamp
319
		if (!$this->share->getInvitationSent()) {
320
			$this->mailService->resendInvitation($this->share->getToken());
321
		}
322
323
		return $this->share;
324
	}
325
326
	/**
327
	 * Delete share
328
	 */
329
	public function delete(string $token): string {
330
		try {
331
			$this->share = $this->shareMapper->findByToken($token);
332
			$this->acl->setPollId($this->share->getPollId())->request(Acl::PERMISSION_EDIT);
333
			$this->shareMapper->delete($this->share);
334
		} catch (DoesNotExistException $e) {
335
			// silently catch
336
		}
337
		return $token;
338
	}
339
}
340