Passed
Push — master ( 50fdd4...138f47 )
by Morris
10:54 queued 10s
created

ShareByMailProvider::getPasswordPolicy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

339
				'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...
340
				'app' => 'sharebymail',
341
			]);
342
			$this->removeShareFromTable($shareId);
343
			throw $hintException;
344
		} catch (\Exception $e) {
345
			$this->logger->logException($e, [
346
				'message' => 'Failed to send share by mail.',
347
				'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

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