Passed
Push — master ( c222ba...946430 )
by John
15:06 queued 11s
created

ShareByMailProvider::getSharedWith()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 8
nop 5
dl 0
loc 35
rs 9.7
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Bjoern Schiessle <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author comradekingu <[email protected]>
9
 * @author Daniel Calviño Sánchez <[email protected]>
10
 * @author Daniel Kesselberg <[email protected]>
11
 * @author exner104 <[email protected]>
12
 * @author Frederic Werner <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Roeland Jago Douma <[email protected]>
18
 * @author rubo77 <[email protected]>
19
 * @author Stephan Müller <[email protected]>
20
 *
21
 * @license GNU AGPL version 3 or any later version
22
 *
23
 * This program is free software: you can redistribute it and/or modify
24
 * it under the terms of the GNU Affero General Public License as
25
 * published by the Free Software Foundation, either version 3 of the
26
 * License, or (at your option) any later version.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35
 *
36
 */
37
38
namespace OCA\ShareByMail;
39
40
use OC\HintException;
41
use OC\Share20\Exception\InvalidShare;
42
use OC\Share20\Share;
43
use OC\User\NoUserException;
44
use OCA\ShareByMail\Settings\SettingsManager;
45
use OCP\Activity\IManager;
46
use OCP\DB\QueryBuilder\IQueryBuilder;
47
use OCP\Defaults;
48
use OCP\EventDispatcher\IEventDispatcher;
49
use OCP\Files\Folder;
50
use OCP\Files\IRootFolder;
51
use OCP\Files\Node;
52
use OCP\IDBConnection;
53
use OCP\IL10N;
54
use OCP\ILogger;
55
use OCP\IURLGenerator;
56
use OCP\IUser;
57
use OCP\IUserManager;
58
use OCP\Mail\IMailer;
59
use OCP\Security\Events\GenerateSecurePasswordEvent;
60
use OCP\Security\IHasher;
61
use OCP\Security\ISecureRandom;
62
use OCP\Share\Exceptions\GenericShareException;
63
use OCP\Share\Exceptions\ShareNotFound;
64
use OCP\Share\IShare;
65
use OCP\Share\IShareProvider;
66
67
/**
68
 * Class ShareByMail
69
 *
70
 * @package OCA\ShareByMail
71
 */
72
class ShareByMailProvider implements IShareProvider {
73
74
	/** @var  IDBConnection */
75
	private $dbConnection;
76
77
	/** @var ILogger */
78
	private $logger;
79
80
	/** @var ISecureRandom */
81
	private $secureRandom;
82
83
	/** @var IUserManager */
84
	private $userManager;
85
86
	/** @var IRootFolder */
87
	private $rootFolder;
88
89
	/** @var IL10N */
90
	private $l;
91
92
	/** @var IMailer */
93
	private $mailer;
94
95
	/** @var IURLGenerator */
96
	private $urlGenerator;
97
98
	/** @var IManager  */
99
	private $activityManager;
100
101
	/** @var SettingsManager */
102
	private $settingsManager;
103
104
	/** @var Defaults */
105
	private $defaults;
106
107
	/** @var IHasher */
108
	private $hasher;
109
110
	/** @var IEventDispatcher */
111
	private $eventDispatcher;
112
113
	/**
114
	 * Return the identifier of this provider.
115
	 *
116
	 * @return string Containing only [a-zA-Z0-9]
117
	 */
118
	public function identifier() {
119
		return 'ocMailShare';
120
	}
121
122
	public function __construct(
123
		IDBConnection $connection,
124
		ISecureRandom $secureRandom,
125
		IUserManager $userManager,
126
		IRootFolder $rootFolder,
127
		IL10N $l,
128
		ILogger $logger,
129
		IMailer $mailer,
130
		IURLGenerator $urlGenerator,
131
		IManager $activityManager,
132
		SettingsManager $settingsManager,
133
		Defaults $defaults,
134
		IHasher $hasher,
135
		IEventDispatcher $eventDispatcher
136
	) {
137
		$this->dbConnection = $connection;
138
		$this->secureRandom = $secureRandom;
139
		$this->userManager = $userManager;
140
		$this->rootFolder = $rootFolder;
141
		$this->l = $l;
142
		$this->logger = $logger;
143
		$this->mailer = $mailer;
144
		$this->urlGenerator = $urlGenerator;
145
		$this->activityManager = $activityManager;
146
		$this->settingsManager = $settingsManager;
147
		$this->defaults = $defaults;
148
		$this->hasher = $hasher;
149
		$this->eventDispatcher = $eventDispatcher;
150
	}
151
152
	/**
153
	 * Share a path
154
	 *
155
	 * @param IShare $share
156
	 * @return IShare The share object
157
	 * @throws ShareNotFound
158
	 * @throws \Exception
159
	 */
160
	public function create(IShare $share) {
161
		$shareWith = $share->getSharedWith();
162
		/*
163
		 * Check if file is not already shared with the remote user
164
		 */
165
		$alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_EMAIL, $share->getNode(), 1, 0);
166
		if (!empty($alreadyShared)) {
167
			$message = 'Sharing %1$s failed, this item is already shared with %2$s';
168
			$message_t = $this->l->t('Sharing %1$s failed, this item is already shared with %2$s', [$share->getNode()->getName(), $shareWith]);
169
			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
170
			throw new \Exception($message_t);
171
		}
172
173
		// if the admin enforces a password for all mail shares we create a
174
		// random password and send it to the recipient
175
		$password = $share->getPassword() ?: '';
176
		$passwordEnforced = $this->settingsManager->enforcePasswordProtection();
177
		if ($passwordEnforced && empty($password)) {
178
			$password = $this->autoGeneratePassword($share);
179
		}
180
181
		if (!empty($password)) {
182
			$share->setPassword($this->hasher->hash($password));
183
		}
184
185
		$shareId = $this->createMailShare($share);
186
		$send = $this->sendPassword($share, $password);
187
		if ($passwordEnforced && $send === false) {
188
			$this->sendPasswordToOwner($share, $password);
189
		}
190
191
		$this->createShareActivity($share);
192
		$data = $this->getRawShare($shareId);
193
194
		return $this->createShareObject($data);
195
	}
196
197
	/**
198
	 * auto generate password in case of password enforcement on mail shares
199
	 *
200
	 * @param IShare $share
201
	 * @return string
202
	 * @throws \Exception
203
	 */
204
	protected function autoGeneratePassword($share) {
205
		$initiatorUser = $this->userManager->get($share->getSharedBy());
206
		$initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
207
		$allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
208
209
		if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
210
			throw new \Exception(
211
				$this->l->t("We can't send you the auto-generated password. Please set a valid email address in your personal settings and try again.")
212
			);
213
		}
214
215
		$passwordEvent = new GenerateSecurePasswordEvent();
216
		$this->eventDispatcher->dispatchTyped($passwordEvent);
217
218
		$password = $passwordEvent->getPassword();
219
		if ($password === null) {
220
			$password = $this->secureRandom->generate(8, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
221
		}
222
223
		return $password;
224
	}
225
226
	/**
227
	 * create activity if a file/folder was shared by mail
228
	 *
229
	 * @param IShare $share
230
	 * @param string $type
231
	 */
232
	protected function createShareActivity(IShare $share, string $type = 'share') {
233
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
234
235
		$this->publishActivity(
236
			$type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_SELF : Activity::SUBJECT_UNSHARED_EMAIL_SELF,
237
			[$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
238
			$share->getSharedBy(),
239
			$share->getNode()->getId(),
240
			(string) $userFolder->getRelativePath($share->getNode()->getPath())
241
		);
242
243
		if ($share->getShareOwner() !== $share->getSharedBy()) {
244
			$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
245
			$fileId = $share->getNode()->getId();
246
			$nodes = $ownerFolder->getById($fileId);
247
			$ownerPath = $nodes[0]->getPath();
248
			$this->publishActivity(
249
				$type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_BY : Activity::SUBJECT_UNSHARED_EMAIL_BY,
250
				[$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
251
				$share->getShareOwner(),
252
				$fileId,
253
				(string) $ownerFolder->getRelativePath($ownerPath)
254
			);
255
		}
256
	}
257
258
	/**
259
	 * create activity if a file/folder was shared by mail
260
	 *
261
	 * @param IShare $share
262
	 * @param string $sharedWith
263
	 * @param bool $sendToSelf
264
	 */
265
	protected function createPasswordSendActivity(IShare $share, $sharedWith, $sendToSelf) {
266
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
267
268
		if ($sendToSelf) {
269
			$this->publishActivity(
270
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
271
				[$userFolder->getRelativePath($share->getNode()->getPath())],
272
				$share->getSharedBy(),
273
				$share->getNode()->getId(),
274
				(string) $userFolder->getRelativePath($share->getNode()->getPath())
275
			);
276
		} else {
277
			$this->publishActivity(
278
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
279
				[$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
280
				$share->getSharedBy(),
281
				$share->getNode()->getId(),
282
				(string) $userFolder->getRelativePath($share->getNode()->getPath())
283
			);
284
		}
285
	}
286
287
288
	/**
289
	 * publish activity if a file/folder was shared by mail
290
	 *
291
	 * @param string $subject
292
	 * @param array $parameters
293
	 * @param string $affectedUser
294
	 * @param int $fileId
295
	 * @param string $filePath
296
	 */
297
	protected function publishActivity(string $subject, array $parameters, string $affectedUser, int $fileId, string $filePath) {
298
		$event = $this->activityManager->generateEvent();
299
		$event->setApp('sharebymail')
300
			->setType('shared')
301
			->setSubject($subject, $parameters)
302
			->setAffectedUser($affectedUser)
303
			->setObject('files', $fileId, $filePath);
304
		$this->activityManager->publish($event);
305
	}
306
307
	/**
308
	 * @param IShare $share
309
	 * @return int
310
	 * @throws \Exception
311
	 */
312
	protected function createMailShare(IShare $share) {
313
		$share->setToken($this->generateToken());
314
		$shareId = $this->addShareToDB(
315
			$share->getNodeId(),
316
			$share->getNodeType(),
317
			$share->getSharedWith(),
318
			$share->getSharedBy(),
319
			$share->getShareOwner(),
320
			$share->getPermissions(),
321
			$share->getToken(),
322
			$share->getPassword(),
323
			$share->getSendPasswordByTalk(),
324
			$share->getHideDownload()
325
		);
326
327
		try {
328
			$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
329
				['token' => $share->getToken()]);
330
			$this->sendMailNotification(
331
				$share->getNode()->getName(),
332
				$link,
333
				$share->getSharedBy(),
334
				$share->getSharedWith(),
335
				$share->getExpirationDate()
336
			);
337
		} catch (HintException $hintException) {
338
			$this->logger->logException($hintException, [
339
				'message' => 'Failed to send share by mail.',
340
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

340
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
341
				'app' => 'sharebymail',
342
			]);
343
			$this->removeShareFromTable($shareId);
344
			throw $hintException;
345
		} catch (\Exception $e) {
346
			$this->logger->logException($e, [
347
				'message' => 'Failed to send share by mail.',
348
				'level' => ILogger::ERROR,
0 ignored issues
show
Deprecated Code introduced by
The constant OCP\ILogger::ERROR has been deprecated: 20.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

348
				'level' => /** @scrutinizer ignore-deprecated */ ILogger::ERROR,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
349
				'app' => 'sharebymail',
350
			]);
351
			$this->removeShareFromTable($shareId);
352
			throw new HintException('Failed to send share by mail',
353
				$this->l->t('Failed to send share by email'));
354
		}
355
356
		return $shareId;
357
	}
358
359
	/**
360
	 * @param string $filename
361
	 * @param string $link
362
	 * @param string $initiator
363
	 * @param string $shareWith
364
	 * @param \DateTime|null $expiration
365
	 * @throws \Exception If mail couldn't be sent
366
	 */
367
	protected function sendMailNotification($filename,
368
											$link,
369
											$initiator,
370
											$shareWith,
371
											\DateTime $expiration = null) {
372
		$initiatorUser = $this->userManager->get($initiator);
373
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
374
		$message = $this->mailer->createMessage();
375
376
		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [
377
			'filename' => $filename,
378
			'link' => $link,
379
			'initiator' => $initiatorDisplayName,
380
			'expiration' => $expiration,
381
			'shareWith' => $shareWith,
382
		]);
383
384
		$emailTemplate->setSubject($this->l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]));
385
		$emailTemplate->addHeader();
386
		$emailTemplate->addHeading($this->l->t('%1$s shared »%2$s« with you', [$initiatorDisplayName, $filename]), false);
387
		$text = $this->l->t('%1$s shared »%2$s« with you.', [$initiatorDisplayName, $filename]);
388
389
		$emailTemplate->addBodyText(
390
			htmlspecialchars($text . ' ' . $this->l->t('Click the button below to open it.')),
391
			$text
392
		);
393
		$emailTemplate->addBodyButton(
394
			$this->l->t('Open »%s«', [$filename]),
395
			$link
396
		);
397
398
		$message->setTo([$shareWith]);
399
400
		// The "From" contains the sharers name
401
		$instanceName = $this->defaults->getName();
402
		$senderName = $instanceName;
403
		if ($this->settingsManager->replyToInitiator()) {
404
			$senderName = $this->l->t(
405
				'%1$s via %2$s',
406
				[
407
					$initiatorDisplayName,
408
					$instanceName
409
				]
410
			);
411
		}
412
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
413
414
		// The "Reply-To" is set to the sharer if an mail address is configured
415
		// also the default footer contains a "Do not reply" which needs to be adjusted.
416
		$initiatorEmail = $initiatorUser->getEMailAddress();
417
		if ($this->settingsManager->replyToInitiator() && $initiatorEmail !== null) {
418
			$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
419
			$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
420
		} else {
421
			$emailTemplate->addFooter();
422
		}
423
424
		$message->useTemplate($emailTemplate);
425
		$this->mailer->send($message);
426
	}
427
428
	/**
429
	 * send password to recipient of a mail share
430
	 *
431
	 * @param IShare $share
432
	 * @param string $password
433
	 * @return bool
434
	 */
435
	protected function sendPassword(IShare $share, $password) {
436
		$filename = $share->getNode()->getName();
437
		$initiator = $share->getSharedBy();
438
		$shareWith = $share->getSharedWith();
439
440
		if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) {
441
			return false;
442
		}
443
444
		$initiatorUser = $this->userManager->get($initiator);
445
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
446
		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
447
448
		$plainBodyPart = $this->l->t("%1\$s shared »%2\$s« with you.\nYou should have already received a separate mail with a link to access it.\n", [$initiatorDisplayName, $filename]);
449
		$htmlBodyPart = $this->l->t('%1$s shared »%2$s« with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
450
451
		$message = $this->mailer->createMessage();
452
453
		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [
454
			'filename' => $filename,
455
			'password' => $password,
456
			'initiator' => $initiatorDisplayName,
457
			'initiatorEmail' => $initiatorEmailAddress,
458
			'shareWith' => $shareWith,
459
		]);
460
461
		$emailTemplate->setSubject($this->l->t('Password to access »%1$s« shared to you by %2$s', [$filename, $initiatorDisplayName]));
462
		$emailTemplate->addHeader();
463
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
464
		$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
465
		$emailTemplate->addBodyText($this->l->t('It is protected with the following password:'));
466
		$emailTemplate->addBodyText($password);
467
468
		// The "From" contains the sharers name
469
		$instanceName = $this->defaults->getName();
470
		$senderName = $instanceName;
471
		if ($this->settingsManager->replyToInitiator()) {
472
			$senderName = $this->l->t(
473
				'%1$s via %2$s',
474
				[
475
					$initiatorDisplayName,
476
					$instanceName
477
				]
478
			);
479
		}
480
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
481
		if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
482
			$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
483
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
484
		} else {
485
			$emailTemplate->addFooter();
486
		}
487
488
		$message->setTo([$shareWith]);
489
		$message->useTemplate($emailTemplate);
490
		$this->mailer->send($message);
491
492
		$this->createPasswordSendActivity($share, $shareWith, false);
493
494
		return true;
495
	}
496
497
	protected function sendNote(IShare $share) {
498
		$recipient = $share->getSharedWith();
499
500
501
		$filename = $share->getNode()->getName();
502
		$initiator = $share->getSharedBy();
503
		$note = $share->getNote();
504
505
		$initiatorUser = $this->userManager->get($initiator);
506
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
507
		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
508
509
		$plainHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]);
510
		$htmlHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]);
511
512
		$message = $this->mailer->createMessage();
513
514
		$emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote');
515
516
		$emailTemplate->setSubject($this->l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName]));
517
		$emailTemplate->addHeader();
518
		$emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading);
519
		$emailTemplate->addBodyText(htmlspecialchars($note), $note);
520
521
		$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
522
			['token' => $share->getToken()]);
523
		$emailTemplate->addBodyButton(
524
			$this->l->t('Open »%s«', [$filename]),
525
			$link
526
		);
527
528
		// The "From" contains the sharers name
529
		$instanceName = $this->defaults->getName();
530
		$senderName = $instanceName;
531
		if ($this->settingsManager->replyToInitiator()) {
532
			$senderName = $this->l->t(
533
				'%1$s via %2$s',
534
				[
535
					$initiatorDisplayName,
536
					$instanceName
537
				]
538
			);
539
		}
540
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
541
		if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
542
			$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
543
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
544
		} else {
545
			$emailTemplate->addFooter();
546
		}
547
548
		$message->setTo([$recipient]);
549
		$message->useTemplate($emailTemplate);
550
		$this->mailer->send($message);
551
	}
552
553
	/**
554
	 * send auto generated password to the owner. This happens if the admin enforces
555
	 * a password for mail shares and forbid to send the password by mail to the recipient
556
	 *
557
	 * @param IShare $share
558
	 * @param string $password
559
	 * @return bool
560
	 * @throws \Exception
561
	 */
562
	protected function sendPasswordToOwner(IShare $share, $password) {
563
		$filename = $share->getNode()->getName();
564
		$initiator = $this->userManager->get($share->getSharedBy());
565
		$initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
566
		$initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
567
		$shareWith = $share->getSharedWith();
568
569
		if ($initiatorEMailAddress === null) {
570
			throw new \Exception(
571
				$this->l->t("We can't send you the auto-generated password. Please set a valid email address in your personal settings and try again.")
572
			);
573
		}
574
575
		$bodyPart = $this->l->t('You just shared »%1$s« with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.', [$filename, $shareWith, $this->defaults->getName()]);
576
577
		$message = $this->mailer->createMessage();
578
		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [
579
			'filename' => $filename,
580
			'password' => $password,
581
			'initiator' => $initiatorDisplayName,
582
			'initiatorEmail' => $initiatorEMailAddress,
583
			'shareWith' => $shareWith,
584
		]);
585
586
		$emailTemplate->setSubject($this->l->t('Password to access »%1$s« shared by you with %2$s', [$filename, $shareWith]));
587
		$emailTemplate->addHeader();
588
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
589
		$emailTemplate->addBodyText($bodyPart);
590
		$emailTemplate->addBodyText($this->l->t('This is the password:'));
591
		$emailTemplate->addBodyText($password);
592
		$emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
593
		$emailTemplate->addFooter();
594
595
		$instanceName = $this->defaults->getName();
596
		$senderName = $this->l->t(
597
			'%1$s via %2$s',
598
			[
599
				$initiatorDisplayName,
600
				$instanceName
601
			]
602
		);
603
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
604
		$message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
605
		$message->useTemplate($emailTemplate);
606
		$this->mailer->send($message);
607
608
		$this->createPasswordSendActivity($share, $shareWith, true);
609
610
		return true;
611
	}
612
613
	/**
614
	 * generate share token
615
	 *
616
	 * @return string
617
	 */
618
	protected function generateToken($size = 15) {
619
		$token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE);
620
		return $token;
621
	}
622
623
	/**
624
	 * Get all children of this share
625
	 *
626
	 * @param IShare $parent
627
	 * @return IShare[]
628
	 */
629
	public function getChildren(IShare $parent) {
630
		$children = [];
631
632
		$qb = $this->dbConnection->getQueryBuilder();
633
		$qb->select('*')
634
			->from('share')
635
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
636
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
637
			->orderBy('id');
638
639
		$cursor = $qb->execute();
640
		while ($data = $cursor->fetch()) {
641
			$children[] = $this->createShareObject($data);
642
		}
643
		$cursor->closeCursor();
644
645
		return $children;
646
	}
647
648
	/**
649
	 * add share to the database and return the ID
650
	 *
651
	 * @param int $itemSource
652
	 * @param string $itemType
653
	 * @param string $shareWith
654
	 * @param string $sharedBy
655
	 * @param string $uidOwner
656
	 * @param int $permissions
657
	 * @param string $token
658
	 * @param string $password
659
	 * @param bool $sendPasswordByTalk
660
	 * @param bool $hideDownload
661
	 * @return int
662
	 */
663
	protected function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $password, $sendPasswordByTalk, $hideDownload) {
664
		$qb = $this->dbConnection->getQueryBuilder();
665
		$qb->insert('share')
666
			->setValue('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
667
			->setValue('item_type', $qb->createNamedParameter($itemType))
668
			->setValue('item_source', $qb->createNamedParameter($itemSource))
669
			->setValue('file_source', $qb->createNamedParameter($itemSource))
670
			->setValue('share_with', $qb->createNamedParameter($shareWith))
671
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
672
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
673
			->setValue('permissions', $qb->createNamedParameter($permissions))
674
			->setValue('token', $qb->createNamedParameter($token))
675
			->setValue('password', $qb->createNamedParameter($password))
676
			->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
677
			->setValue('stime', $qb->createNamedParameter(time()))
678
			->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT));
679
680
		/*
681
		 * Added to fix https://github.com/owncloud/core/issues/22215
682
		 * Can be removed once we get rid of ajax/share.php
683
		 */
684
		$qb->setValue('file_target', $qb->createNamedParameter(''));
685
686
		$qb->execute();
687
		$id = $qb->getLastInsertId();
688
689
		return (int)$id;
690
	}
691
692
	/**
693
	 * Update a share
694
	 *
695
	 * @param IShare $share
696
	 * @param string|null $plainTextPassword
697
	 * @return IShare The share object
698
	 */
699
	public function update(IShare $share, $plainTextPassword = null) {
700
		$originalShare = $this->getShareById($share->getId());
701
702
		// a real password was given
703
		$validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
704
705
		if ($validPassword && ($originalShare->getPassword() !== $share->getPassword() ||
706
								($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
707
			$this->sendPassword($share, $plainTextPassword);
708
		}
709
		/*
710
		 * We allow updating the permissions and password of mail shares
711
		 */
712
		$qb = $this->dbConnection->getQueryBuilder();
713
		$qb->update('share')
714
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
715
			->set('permissions', $qb->createNamedParameter($share->getPermissions()))
716
			->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
717
			->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
718
			->set('password', $qb->createNamedParameter($share->getPassword()))
719
			->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
720
			->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
721
			->set('note', $qb->createNamedParameter($share->getNote()))
722
			->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
723
			->execute();
724
725
		if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
726
			$this->sendNote($share);
727
		}
728
729
		return $share;
730
	}
731
732
	/**
733
	 * @inheritdoc
734
	 */
735
	public function move(IShare $share, $recipient) {
736
		/**
737
		 * nothing to do here, mail shares are only outgoing shares
738
		 */
739
		return $share;
740
	}
741
742
	/**
743
	 * Delete a share (owner unShares the file)
744
	 *
745
	 * @param IShare $share
746
	 */
747
	public function delete(IShare $share) {
748
		try {
749
			$this->createShareActivity($share, 'unshare');
750
		} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
751
		}
752
753
		$this->removeShareFromTable($share->getId());
754
	}
755
756
	/**
757
	 * @inheritdoc
758
	 */
759
	public function deleteFromSelf(IShare $share, $recipient) {
760
		// nothing to do here, mail shares are only outgoing shares
761
	}
762
763
	public function restore(IShare $share, string $recipient): IShare {
764
		throw new GenericShareException('not implemented');
765
	}
766
767
	/**
768
	 * @inheritdoc
769
	 */
770
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
771
		$qb = $this->dbConnection->getQueryBuilder();
772
		$qb->select('*')
773
			->from('share');
774
775
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
776
777
		/**
778
		 * Reshares for this user are shares where they are the owner.
779
		 */
780
		if ($reshares === false) {
781
			//Special case for old shares created via the web UI
782
			$or1 = $qb->expr()->andX(
783
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
784
				$qb->expr()->isNull('uid_initiator')
785
			);
786
787
			$qb->andWhere(
788
				$qb->expr()->orX(
789
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
790
					$or1
791
				)
792
			);
793
		} else {
794
			$qb->andWhere(
795
				$qb->expr()->orX(
796
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
797
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
798
				)
799
			);
800
		}
801
802
		if ($node !== null) {
803
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
804
		}
805
806
		if ($limit !== -1) {
807
			$qb->setMaxResults($limit);
808
		}
809
810
		$qb->setFirstResult($offset);
811
		$qb->orderBy('id');
812
813
		$cursor = $qb->execute();
814
		$shares = [];
815
		while ($data = $cursor->fetch()) {
816
			$shares[] = $this->createShareObject($data);
817
		}
818
		$cursor->closeCursor();
819
820
		return $shares;
821
	}
822
823
	/**
824
	 * @inheritdoc
825
	 */
826
	public function getShareById($id, $recipientId = null) {
827
		$qb = $this->dbConnection->getQueryBuilder();
828
829
		$qb->select('*')
830
			->from('share')
831
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
832
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
833
834
		$cursor = $qb->execute();
835
		$data = $cursor->fetch();
836
		$cursor->closeCursor();
837
838
		if ($data === false) {
839
			throw new ShareNotFound();
840
		}
841
842
		try {
843
			$share = $this->createShareObject($data);
844
		} catch (InvalidShare $e) {
845
			throw new ShareNotFound();
846
		}
847
848
		return $share;
849
	}
850
851
	/**
852
	 * Get shares for a given path
853
	 *
854
	 * @param \OCP\Files\Node $path
855
	 * @return IShare[]
856
	 */
857
	public function getSharesByPath(Node $path) {
858
		$qb = $this->dbConnection->getQueryBuilder();
859
860
		$cursor = $qb->select('*')
861
			->from('share')
862
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
863
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
864
			->execute();
865
866
		$shares = [];
867
		while ($data = $cursor->fetch()) {
868
			$shares[] = $this->createShareObject($data);
869
		}
870
		$cursor->closeCursor();
871
872
		return $shares;
873
	}
874
875
	/**
876
	 * @inheritdoc
877
	 */
878
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
879
		/** @var IShare[] $shares */
880
		$shares = [];
881
882
		//Get shares directly with this user
883
		$qb = $this->dbConnection->getQueryBuilder();
884
		$qb->select('*')
885
			->from('share');
886
887
		// Order by id
888
		$qb->orderBy('id');
889
890
		// Set limit and offset
891
		if ($limit !== -1) {
892
			$qb->setMaxResults($limit);
893
		}
894
		$qb->setFirstResult($offset);
895
896
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
897
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
898
899
		// Filter by node if provided
900
		if ($node !== null) {
901
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
902
		}
903
904
		$cursor = $qb->execute();
905
906
		while ($data = $cursor->fetch()) {
907
			$shares[] = $this->createShareObject($data);
908
		}
909
		$cursor->closeCursor();
910
911
912
		return $shares;
913
	}
914
915
	/**
916
	 * Get a share by token
917
	 *
918
	 * @param string $token
919
	 * @return IShare
920
	 * @throws ShareNotFound
921
	 */
922
	public function getShareByToken($token) {
923
		$qb = $this->dbConnection->getQueryBuilder();
924
925
		$cursor = $qb->select('*')
926
			->from('share')
927
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
928
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
929
			->execute();
930
931
		$data = $cursor->fetch();
932
933
		if ($data === false) {
934
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
935
		}
936
937
		try {
938
			$share = $this->createShareObject($data);
939
		} catch (InvalidShare $e) {
940
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
941
		}
942
943
		return $share;
944
	}
945
946
	/**
947
	 * remove share from table
948
	 *
949
	 * @param string $shareId
950
	 */
951
	protected function removeShareFromTable($shareId) {
952
		$qb = $this->dbConnection->getQueryBuilder();
953
		$qb->delete('share')
954
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
955
		$qb->execute();
956
	}
957
958
	/**
959
	 * Create a share object from an database row
960
	 *
961
	 * @param array $data
962
	 * @return IShare
963
	 * @throws InvalidShare
964
	 * @throws ShareNotFound
965
	 */
966
	protected function createShareObject($data) {
967
		$share = new Share($this->rootFolder, $this->userManager);
968
		$share->setId((int)$data['id'])
969
			->setShareType((int)$data['share_type'])
970
			->setPermissions((int)$data['permissions'])
971
			->setTarget($data['file_target'])
972
			->setMailSend((bool)$data['mail_send'])
973
			->setNote($data['note'])
974
			->setToken($data['token']);
975
976
		$shareTime = new \DateTime();
977
		$shareTime->setTimestamp((int)$data['stime']);
978
		$share->setShareTime($shareTime);
979
		$share->setSharedWith($data['share_with']);
980
		$share->setPassword($data['password']);
981
		$share->setSendPasswordByTalk((bool)$data['password_by_talk']);
982
		$share->setHideDownload((bool)$data['hide_download']);
983
984
		if ($data['uid_initiator'] !== null) {
985
			$share->setShareOwner($data['uid_owner']);
986
			$share->setSharedBy($data['uid_initiator']);
987
		} else {
988
			//OLD SHARE
989
			$share->setSharedBy($data['uid_owner']);
990
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
991
992
			$owner = $path->getOwner();
993
			$share->setShareOwner($owner->getUID());
994
		}
995
996
		if ($data['expiration'] !== null) {
997
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
998
			if ($expiration !== false) {
999
				$share->setExpirationDate($expiration);
1000
			}
1001
		}
1002
1003
		$share->setNodeId((int)$data['file_source']);
1004
		$share->setNodeType($data['item_type']);
1005
1006
		$share->setProviderId($this->identifier());
1007
1008
		return $share;
1009
	}
1010
1011
	/**
1012
	 * Get the node with file $id for $user
1013
	 *
1014
	 * @param string $userId
1015
	 * @param int $id
1016
	 * @return \OCP\Files\File|\OCP\Files\Folder
1017
	 * @throws InvalidShare
1018
	 */
1019
	private function getNode($userId, $id) {
1020
		try {
1021
			$userFolder = $this->rootFolder->getUserFolder($userId);
1022
		} catch (NoUserException $e) {
1023
			throw new InvalidShare();
1024
		}
1025
1026
		$nodes = $userFolder->getById($id);
1027
1028
		if (empty($nodes)) {
1029
			throw new InvalidShare();
1030
		}
1031
1032
		return $nodes[0];
1033
	}
1034
1035
	/**
1036
	 * A user is deleted from the system
1037
	 * So clean up the relevant shares.
1038
	 *
1039
	 * @param string $uid
1040
	 * @param int $shareType
1041
	 */
1042
	public function userDeleted($uid, $shareType) {
1043
		$qb = $this->dbConnection->getQueryBuilder();
1044
1045
		$qb->delete('share')
1046
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1047
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
1048
			->execute();
1049
	}
1050
1051
	/**
1052
	 * This provider does not support group shares
1053
	 *
1054
	 * @param string $gid
1055
	 */
1056
	public function groupDeleted($gid) {
1057
	}
1058
1059
	/**
1060
	 * This provider does not support group shares
1061
	 *
1062
	 * @param string $uid
1063
	 * @param string $gid
1064
	 */
1065
	public function userDeletedFromGroup($uid, $gid) {
1066
	}
1067
1068
	/**
1069
	 * get database row of a give share
1070
	 *
1071
	 * @param $id
1072
	 * @return array
1073
	 * @throws ShareNotFound
1074
	 */
1075
	protected function getRawShare($id) {
1076
1077
		// Now fetch the inserted share and create a complete share object
1078
		$qb = $this->dbConnection->getQueryBuilder();
1079
		$qb->select('*')
1080
			->from('share')
1081
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
1082
1083
		$cursor = $qb->execute();
1084
		$data = $cursor->fetch();
1085
		$cursor->closeCursor();
1086
1087
		if ($data === false) {
1088
			throw new ShareNotFound;
1089
		}
1090
1091
		return $data;
1092
	}
1093
1094
	public function getSharesInFolder($userId, Folder $node, $reshares) {
1095
		$qb = $this->dbConnection->getQueryBuilder();
1096
		$qb->select('*')
1097
			->from('share', 's')
1098
			->andWhere($qb->expr()->orX(
1099
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1100
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1101
			))
1102
			->andWhere(
1103
				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
1104
			);
1105
1106
		/**
1107
		 * Reshares for this user are shares where they are the owner.
1108
		 */
1109
		if ($reshares === false) {
1110
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
1111
		} else {
1112
			$qb->andWhere(
1113
				$qb->expr()->orX(
1114
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
1115
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
1116
				)
1117
			);
1118
		}
1119
1120
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
1121
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
1122
1123
		$qb->orderBy('id');
1124
1125
		$cursor = $qb->execute();
1126
		$shares = [];
1127
		while ($data = $cursor->fetch()) {
1128
			$shares[$data['fileid']][] = $this->createShareObject($data);
1129
		}
1130
		$cursor->closeCursor();
1131
1132
		return $shares;
1133
	}
1134
1135
	/**
1136
	 * @inheritdoc
1137
	 */
1138
	public function getAccessList($nodes, $currentAccess) {
1139
		$ids = [];
1140
		foreach ($nodes as $node) {
1141
			$ids[] = $node->getId();
1142
		}
1143
1144
		$qb = $this->dbConnection->getQueryBuilder();
1145
		$qb->select('share_with')
1146
			->from('share')
1147
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1148
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1149
			->andWhere($qb->expr()->orX(
1150
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1151
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1152
			))
1153
			->setMaxResults(1);
1154
		$cursor = $qb->execute();
1155
1156
		$mail = $cursor->fetch() !== false;
1157
		$cursor->closeCursor();
1158
1159
		return ['public' => $mail];
1160
	}
1161
1162
	public function getAllShares(): iterable {
1163
		$qb = $this->dbConnection->getQueryBuilder();
1164
1165
		$qb->select('*')
1166
			->from('share')
1167
			->where(
1168
				$qb->expr()->orX(
1169
					$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share\IShare::TYPE_EMAIL))
1170
				)
1171
			);
1172
1173
		$cursor = $qb->execute();
1174
		while ($data = $cursor->fetch()) {
1175
			try {
1176
				$share = $this->createShareObject($data);
1177
			} catch (InvalidShare $e) {
1178
				continue;
1179
			} catch (ShareNotFound $e) {
1180
				continue;
1181
			}
1182
1183
			yield $share;
1184
		}
1185
		$cursor->closeCursor();
1186
	}
1187
}
1188