Completed
Pull Request — master (#479)
by Maxence
02:08
created

FileShare::result()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.2728
c 0
b 0
f 0
cc 5
nc 6
nop 1
1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\GlobalScale;
31
32
33
use daita\MySmallPhpTools\Model\SimpleDataStore;
34
use daita\MySmallPhpTools\Traits\TArrayTools;
35
use Exception;
36
use OC;
37
use OC\Share20\Share;
38
use OCA\Circles\AppInfo\Application;
39
use OCA\Circles\Exceptions\CircleDoesNotExistException;
40
use OCA\Circles\Exceptions\GSStatusException;
41
use OCA\Circles\Exceptions\TokenDoesNotExistException;
42
use OCA\Circles\Model\Circle;
43
use OCA\Circles\Model\GlobalScale\GSEvent;
44
use OCA\Circles\Model\GlobalScale\GSShare;
45
use OCA\Circles\Model\Member;
46
use OCA\Circles\Model\SharesToken;
47
use OCA\Circles\Model\SharingFrame;
48
use OCA\Circles\Service\MiscService;
49
use OCP\Files\NotFoundException;
50
use OCP\IUser;
51
use OCP\Mail\IEMailTemplate;
52
use OCP\Share\Exceptions\IllegalIDChangeException;
53
use OCP\Share\Exceptions\ShareNotFound;
54
use OCP\Share\IShare;
55
use OCP\Util;
56
57
58
/**
59
 * Class FileShare
60
 *
61
 * @package OCA\Circles\GlobalScale
62
 */
63
class FileShare extends AGlobalScaleEvent {
64
65
66
	use TArrayTools;
67
68
69
	/**
70
	 * @param GSEvent $event
71
	 * @param bool $localCheck
72
	 * @param bool $mustBeChecked
73
	 */
74
	public function verify(GSEvent $event, bool $localCheck = false, bool $mustBeChecked = false): void {
75
		// if event/file is local, we generate a federate share for the same circle on other instances
76
		if ($event->getSource() !== $this->configService->getLocalCloudId()) {
77
			return;
78
		}
79
80
		try {
81
			$share = $this->getShareFromData($event->getData());
82
		} catch (Exception $e) {
83
			return;
84
		}
85
86
		try {
87
			$node = $share->getNode();
88
			$filename = $node->getName();
89
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
90
			$this->miscService->log('issue while FileShare: ' . $e->getMessage());
91
92
			return;
93
		}
94
95
		$event->getData()
96
			  ->s('gs_federated', $share->getToken())
97
			  ->s('gs_filename', '/' . $filename);
98
	}
99
100
101
	/**
102
	 * @param GSEvent $event
103
	 *
104
	 * @throws GSStatusException
105
	 * @throws CircleDoesNotExistException
106
	 */
107
	public function manage(GSEvent $event): void {
108
		$circle = $event->getCircle();
109
110
		// if event is not local, we create a federated file to the right instance of Nextcloud, using the right token
111
		if ($event->getSource() !== $this->configService->getLocalCloudId()) {
112
			try {
113
				$share = $this->getShareFromData($event->getData());
114
			} catch (Exception $e) {
115
				return;
116
			}
117
118
			$data = $event->getData();
119
			$token = $data->g('gs_federated');
120
			$filename = $data->g('gs_filename');
121
122
			$gsShare = new GSShare($share->getSharedWith(), $token);
123
			$gsShare->setOwner($share->getShareOwner());
124
			$gsShare->setInstance($event->getSource());
125
			$gsShare->setParent(-1);
126
			$gsShare->setMountPoint($filename);
127
128
			$this->gsSharesRequest->create($gsShare);
129
		} else {
130
			// if the event is local, we send mail to mail-as-members
131
			$members = $this->membersRequest->forceGetMembers(
132
				$circle->getUniqueId(), Member::LEVEL_MEMBER, Member::TYPE_MAIL, true
133
			);
134
135
			foreach ($members as $member) {
136
				$this->sendShareToContact($event, $circle, $member->getMemberId(), [$member->getUserId()]);
137
			}
138
		}
139
140
		// we also fill the event's result for further things, like contact-as-members
141
		$members = $this->membersRequest->forceGetMembers(
142
			$circle->getUniqueId(), Member::LEVEL_MEMBER, Member::TYPE_CONTACT, true
143
		);
144
145
		$accounts = [];
146
		foreach ($members as $member) {
147
			if ($member->getInstance() === '') {
148
				$accounts[] = $this->miscService->getInfosFromContact($member);
149
			}
150
		}
151
152
		$event->setResult(new SimpleDataStore(['contacts' => $accounts]));
153
	}
154
155
156
	/**
157
	 * @param GSEvent[] $events
158
	 *
159
	 * @throws CircleDoesNotExistException
160
	 */
161
	public function result(array $events): void {
162
		$event = null;
163
		$contacts = [];
164
		foreach (array_keys($events) as $instance) {
165
			$event = $events[$instance];
166
			$contacts = array_merge(
167
				$contacts, $event->getResult()
168
								 ->gArray('contacts')
169
			);
170
		}
171
172
		if ($event === null || !$event->hasCircle()) {
173
			return;
174
		}
175
176
		$circle = $event->getCircle();
177
178
		foreach ($contacts as $contact) {
179
			$this->sendShareToContact($event, $circle, $contact['memberId'], $contact['emails']);
180
		}
181
	}
182
183
184
	/**
185
	 * @param GSEvent $event
186
	 * @param Circle $circle
187
	 * @param string $memberId
188
	 * @param array $emails
189
	 *
190
	 * @throws CircleDoesNotExistException
191
	 */
192
	private function sendShareToContact(GSEvent $event, Circle $circle, string $memberId, array $emails) {
193
		try {
194
			$member = $this->membersRequest->forceGetMemberById($memberId);
195
			$share = $this->getShareFromData($event->getData());
196
		} catch (Exception $e) {
197
			return;
198
		}
199
200
		$newCircle = $this->circlesRequest->forceGetCircle($circle->getUniqueId(), true);
201
		$password = '';
202
		$sendPasswordByMail = true;
203 View Code Duplication
		if ($this->configService->enforcePasswordProtection($newCircle)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
			if ($newCircle->getSetting('password_single_enabled') === 'true') {
205
				$password = $newCircle->getPasswordSingle();
206
				$sendPasswordByMail = false;
207
			} else {
208
				$password = $this->miscService->token(15);
209
			}
210
		}
211
212
		try {
213
			$sharesToken =
214
				$this->tokensRequest->generateTokenForMember($member, (int)$share->getId(), $password);
215
		} catch (TokenDoesNotExistException $e) {
216
			return;
217
		}
218
219
		if (!$sendPasswordByMail) {
220
			$password = '';
221
		}
222
223
		foreach ($emails as $mail) {
224
			$this->sharedByMail($circle, $share, $mail, $sharesToken, $password);
225
		}
226
	}
227
228
229
	/**
230
	 * @param Circle $circle
231
	 * @param IShare $share
232
	 * @param string $email
233
	 * @param SharesToken $sharesToken
234
	 * @param string $password
235
	 */
236 View Code Duplication
	private function sharedByMail(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
		Circle $circle, IShare $share, string $email, SharesToken $sharesToken, string $password
238
	) {
239
		// genelink
240
		$link = $this->urlGenerator->linkToRouteAbsolute(
241
			'files_sharing.sharecontroller.showShare',
242
			['token' => $sharesToken->getToken()]
243
		);
244
245
		$lang = $this->configService->getCoreValueForUser($share->getSharedBy(), 'lang', '');
246
		if ($lang !== '') {
247
			$this->l10n = OC::$server->getL10N(Application::APP_NAME, $lang);
248
		}
249
250
		try {
251
			$this->sendMail(
252
				$share->getNode()
253
					  ->getName(), $link,
254
				MiscService::getDisplay($share->getSharedBy(), Member::TYPE_USER),
255
				$circle->getName(), $email
256
			);
257
			$this->sendPasswordByMail(
258
				$share, MiscService::getDisplay($share->getSharedBy(), Member::TYPE_USER),
259
				$email, $password
260
			);
261
		} catch (Exception $e) {
262
			OC::$server->getLogger()
263
					   ->log(1, 'Circles::sharedByMail - mail were not sent: ' . $e->getMessage());
264
		}
265
	}
266
267
268
	/**
269
	 * @param $fileName
270
	 * @param string $link
271
	 * @param string $author
272
	 * @param $circleName
273
	 * @param string $email
274
	 *
275
	 * @throws Exception
276
	 */
277 View Code Duplication
	protected function sendMail($fileName, $link, $author, $circleName, $email) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
		$message = $this->mailer->createMessage();
279
280
		$this->miscService->log(
281
			"Sending mail to circle '" . $circleName . "': " . $email . ' file: ' . $fileName
282
			. ' - link: ' . $link, 0
283
		);
284
285
		$subject = $this->l10n->t('%s shared »%s« with you.', [$author, $fileName]);
286
		$text = $this->l10n->t('%s shared »%s« with \'%s\'.', [$author, $fileName, $circleName]);
287
288
		$emailTemplate =
289
			$this->generateEmailTemplate($subject, $text, $fileName, $link, $author, $circleName);
290
291
		$instanceName = $this->defaults->getName();
292
		$senderName = $this->l10n->t('%s on %s', [$author, $instanceName]);
293
294
		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
295
		$message->setSubject($subject);
296
		$message->setPlainBody($emailTemplate->renderText());
297
		$message->setHtmlBody($emailTemplate->renderHtml());
298
		$message->setTo([$email]);
299
300
		$this->mailer->send($message);
301
	}
302
303
304
	/**
305
	 * @param IShare $share
306
	 * @param string $circleName
307
	 * @param string $email
308
	 *
309
	 * @param $password
310
	 *
311
	 * @throws NotFoundException
312
	 * @throws Exception
313
	 */
314 View Code Duplication
	protected function sendPasswordByMail(IShare $share, $circleName, $email, $password) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
315
		if (!$this->configService->sendPasswordByMail() || $password === '') {
316
			return;
317
		}
318
319
		$message = $this->mailer->createMessage();
320
321
		$this->miscService->log("Sending password mail to circle '" . $circleName . "': " . $email, 0);
322
323
		$filename = $share->getNode()
324
						  ->getName();
325
		$initiator = $share->getSharedBy();
326
		$shareWith = $share->getSharedWith();
327
328
		$initiatorUser = $this->userManager->get($initiator);
329
		$initiatorDisplayName =
330
			($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
331
		$initiatorEmailAddress =
332
			($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
333
334
		$plainBodyPart = $this->l10n->t(
335
			"%1\$s shared »%2\$s« with you.\nYou should have already received a separate mail with a link to access it.\n",
336
			[$initiatorDisplayName, $filename]
337
		);
338
		$htmlBodyPart = $this->l10n->t(
339
			'%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it.',
340
			[$initiatorDisplayName, $filename]
341
		);
342
343
		$emailTemplate = $this->mailer->createEMailTemplate(
344
			'sharebymail.RecipientPasswordNotification', [
345
														   'filename'       => $filename,
346
														   'password'       => $password,
347
														   'initiator'      => $initiatorDisplayName,
348
														   'initiatorEmail' => $initiatorEmailAddress,
349
														   'shareWith'      => $shareWith,
350
													   ]
351
		);
352
353
		$emailTemplate->setSubject(
354
			$this->l10n->t(
355
				'Password to access »%1$s« shared to you by %2$s', [$filename, $initiatorDisplayName]
356
			)
357
		);
358
		$emailTemplate->addHeader();
359
		$emailTemplate->addHeading($this->l10n->t('Password to access »%s«', [$filename]), false);
360
		$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
361
		$emailTemplate->addBodyText($this->l10n->t('It is protected with the following password:'));
362
		$emailTemplate->addBodyText($password);
363
364
		// The "From" contains the sharers name
365
		$instanceName = $this->defaults->getName();
366
		$senderName = $this->l10n->t(
367
			'%1$s via %2$s',
368
			[
369
				$initiatorDisplayName,
370
				$instanceName
371
			]
372
		);
373
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
374
		if ($initiatorEmailAddress !== null) {
375
			$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
376
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
377
		} else {
378
			$emailTemplate->addFooter();
379
		}
380
381
		$message->setTo([$email]);
382
		$message->useTemplate($emailTemplate);
383
		$this->mailer->send($message);
384
	}
385
386
387
	/**
388
	 * @param $subject
389
	 * @param $text
390
	 * @param $fileName
391
	 * @param $link
392
	 * @param string $author
393
	 * @param string $circleName
394
	 *
395
	 * @return IEMailTemplate
396
	 */
397 View Code Duplication
	private function generateEmailTemplate($subject, $text, $fileName, $link, $author, $circleName
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
	) {
399
		$emailTemplate = $this->mailer->createEMailTemplate(
400
			'circles.ShareNotification', [
401
										   'fileName'   => $fileName,
402
										   'fileLink'   => $link,
403
										   'author'     => $author,
404
										   'circleName' => $circleName,
405
									   ]
406
		);
407
408
		$emailTemplate->addHeader();
409
		$emailTemplate->addHeading($subject, false);
410
		$emailTemplate->addBodyText(
411
			htmlspecialchars($text) . '<br>' . htmlspecialchars(
412
				$this->l10n->t('Click the button below to open it.')
413
			), $text
414
		);
415
		$emailTemplate->addBodyButton(
416
			$this->l10n->t('Open »%s«', [htmlspecialchars($fileName)]), $link
417
		);
418
419
		return $emailTemplate;
420
	}
421
422
423
	/**
424
	 * @param string $circleId
425
	 *
426
	 * @return array
427
	 */
428
	private function getMailAddressFromCircle(string $circleId): array {
429
		$members = $this->membersRequest->forceGetMembers(
430
			$circleId, Member::LEVEL_MEMBER, Member::TYPE_MAIL
431
		);
432
433
		return array_map(
434
			function(Member $member) {
435
				return $member->getUserId();
436
			}, $members
437
		);
438
	}
439
440
441
	/**
442
	 * @param SimpleDataStore $data
443
	 *
444
	 * @return IShare
445
	 * @throws ShareNotFound
446
	 * @throws IllegalIDChangeException
447
	 */
448
	private function getShareFromData(SimpleDataStore $data) {
449
		$frame = SharingFrame::fromArray($data->gArray('frame'));
450
		$payload = $frame->getPayload();
451
		if (!key_exists('share', $payload)) {
452
			throw new ShareNotFound();
453
		}
454
455
		return $this->generateShare($payload['share']);
456
	}
457
458
459
	/**
460
	 * recreate the share from the JSON payload.
461
	 *
462
	 * @param array $data
463
	 *
464
	 * @return IShare
465
	 * @throws IllegalIDChangeException
466
	 */
467
	private function generateShare($data): IShare {
468
		$share = new Share($this->rootFolder, $this->userManager);
469
		$share->setId($data['id']);
470
		$share->setSharedBy($data['sharedBy']);
471
		$share->setSharedWith($data['sharedWith']);
472
		$share->setNodeId($data['nodeId']);
473
		$share->setShareOwner($data['shareOwner']);
474
		$share->setPermissions($data['permissions']);
475
		$share->setToken($data['token']);
476
		$share->setPassword($data['password']);
477
478
		return $share;
479
	}
480
481
482
}
483