Completed
Push — master ( a4086a...810fb7 )
by Björn
66:13 queued 48:44
created

ShareByMailProvider::autoGeneratePassword()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 12
nop 1
dl 0
loc 25
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Bjoern Schiessle <[email protected]>
4
 *
5
 * @license GNU AGPL version 3 or any later version
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as
9
 * published by the Free Software Foundation, either version 3 of the
10
 * License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21
22
namespace OCA\ShareByMail;
23
24
use OC\CapabilitiesManager;
25
use OC\HintException;
26
use OC\Share20\Exception\InvalidShare;
27
use OCA\ShareByMail\Settings\SettingsManager;
28
use OCP\Activity\IManager;
29
use OCP\DB\QueryBuilder\IQueryBuilder;
30
use OCP\Defaults;
31
use OCP\Files\Folder;
32
use OCP\Files\IRootFolder;
33
use OCP\Files\Node;
34
use OCP\IDBConnection;
35
use OCP\IL10N;
36
use OCP\ILogger;
37
use OCP\IURLGenerator;
38
use OCP\IUser;
39
use OCP\IUserManager;
40
use OCP\Mail\IMailer;
41
use OCP\Security\IHasher;
42
use OCP\Security\ISecureRandom;
43
use OC\Share20\Share;
44
use OCP\Share\Exceptions\ShareNotFound;
45
use OCP\Share\IShare;
46
use OCP\Share\IShareProvider;
47
48
/**
49
 * Class ShareByMail
50
 *
51
 * @package OCA\ShareByMail
52
 */
53
class ShareByMailProvider implements IShareProvider {
54
55
	/** @var  IDBConnection */
56
	private $dbConnection;
57
58
	/** @var ILogger */
59
	private $logger;
60
61
	/** @var ISecureRandom */
62
	private $secureRandom;
63
64
	/** @var IUserManager */
65
	private $userManager;
66
67
	/** @var IRootFolder */
68
	private $rootFolder;
69
70
	/** @var IL10N */
71
	private $l;
72
73
	/** @var IMailer */
74
	private $mailer;
75
76
	/** @var IURLGenerator */
77
	private $urlGenerator;
78
79
	/** @var IManager  */
80
	private $activityManager;
81
82
	/** @var SettingsManager */
83
	private $settingsManager;
84
85
	/** @var Defaults */
86
	private $defaults;
87
88
	/** @var IHasher */
89
	private $hasher;
90
91
	/** @var  CapabilitiesManager */
92
	private $capabilitiesManager;
93
94
	/**
95
	 * Return the identifier of this provider.
96
	 *
97
	 * @return string Containing only [a-zA-Z0-9]
98
	 */
99
	public function identifier() {
100
		return 'ocMailShare';
101
	}
102
103
	/**
104
	 * DefaultShareProvider constructor.
105
	 *
106
	 * @param IDBConnection $connection
107
	 * @param ISecureRandom $secureRandom
108
	 * @param IUserManager $userManager
109
	 * @param IRootFolder $rootFolder
110
	 * @param IL10N $l
111
	 * @param ILogger $logger
112
	 * @param IMailer $mailer
113
	 * @param IURLGenerator $urlGenerator
114
	 * @param IManager $activityManager
115
	 * @param SettingsManager $settingsManager
116
	 * @param Defaults $defaults
117
	 * @param IHasher $hasher
118
	 * @param CapabilitiesManager $capabilitiesManager
119
	 */
120 View Code Duplication
	public function __construct(
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...
121
		IDBConnection $connection,
122
		ISecureRandom $secureRandom,
123
		IUserManager $userManager,
124
		IRootFolder $rootFolder,
125
		IL10N $l,
126
		ILogger $logger,
127
		IMailer $mailer,
128
		IURLGenerator $urlGenerator,
129
		IManager $activityManager,
130
		SettingsManager $settingsManager,
131
		Defaults $defaults,
132
		IHasher $hasher,
133
		CapabilitiesManager $capabilitiesManager
134
	) {
135
		$this->dbConnection = $connection;
136
		$this->secureRandom = $secureRandom;
137
		$this->userManager = $userManager;
138
		$this->rootFolder = $rootFolder;
139
		$this->l = $l;
140
		$this->logger = $logger;
141
		$this->mailer = $mailer;
142
		$this->urlGenerator = $urlGenerator;
143
		$this->activityManager = $activityManager;
144
		$this->settingsManager = $settingsManager;
145
		$this->defaults = $defaults;
146
		$this->hasher = $hasher;
147
		$this->capabilitiesManager = $capabilitiesManager;
148
	}
149
150
	/**
151
	 * Share a path
152
	 *
153
	 * @param IShare $share
154
	 * @return IShare The share object
155
	 * @throws ShareNotFound
156
	 * @throws \Exception
157
	 */
158
	public function create(IShare $share) {
159
160
		$shareWith = $share->getSharedWith();
161
		/*
162
		 * Check if file is not already shared with the remote user
163
		 */
164
		$alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_EMAIL, $share->getNode(), 1, 0);
165 View Code Duplication
		if (!empty($alreadyShared)) {
166
			$message = 'Sharing %s failed, this item is already shared with %s';
167
			$message_t = $this->l->t('Sharing %s failed, this item is already shared with %s', array($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 = '';
175
		$passwordEnforced = $this->settingsManager->enforcePasswordProtection();
176
		if ($passwordEnforced) {
177
			$password = $this->autoGeneratePassword($share);
178
		}
179
180
		$shareId = $this->createMailShare($share);
181
		$send = $this->sendPassword($share, $password);
182
		if ($passwordEnforced && $send === false) {
183
			$this->sendPasswordToOwner($share, $password);
184
		}
185
186
		$this->createShareActivity($share);
187
		$data = $this->getRawShare($shareId);
188
189
		return $this->createShareObject($data);
190
191
	}
192
193
	/**
194
	 * auto generate password in case of password enforcement on mail shares
195
	 *
196
	 * @param IShare $share
197
	 * @return string
198
	 * @throws \Exception
199
	 */
200
	protected function autoGeneratePassword($share) {
201
		$initiatorUser = $this->userManager->get($share->getSharedBy());
202
		$initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
203
		$allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
204
205
		if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
206
			throw new \Exception(
207
				$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.")
208
			);
209
		}
210
211
		$passwordPolicy = $this->getPasswordPolicy();
212
		$passwordCharset = ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS;
213
		$passwordLength = 8;
214
		if (!empty($passwordPolicy)) {
215
			$passwordLength = (int)$passwordPolicy['minLength'] > 0 ? (int)$passwordPolicy['minLength'] : $passwordLength;
216
			$passwordCharset .= $passwordPolicy['enforceSpecialCharacters'] ? ISecureRandom::CHAR_SYMBOLS : '';
217
		}
218
219
		$password = $this->secureRandom->generate($passwordLength, $passwordCharset);
220
221
		$share->setPassword($this->hasher->hash($password));
222
223
		return $password;
224
	}
225
226
	/**
227
	 * get password policy
228
	 *
229
	 * @return array
230
	 */
231
	protected function getPasswordPolicy() {
232
		$capabilities = $this->capabilitiesManager->getCapabilities();
233
		if (isset($capabilities['password_policy'])) {
234
			return $capabilities['password_policy'];
235
		}
236
237
		return [];
238
	}
239
240
	/**
241
	 * create activity if a file/folder was shared by mail
242
	 *
243
	 * @param IShare $share
244
	 */
245
	protected function createShareActivity(IShare $share) {
246
247
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
248
249
		$this->publishActivity(
250
			Activity::SUBJECT_SHARED_EMAIL_SELF,
251
			[$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
252
			$share->getSharedBy(),
253
			$share->getNode()->getId(),
254
			$userFolder->getRelativePath($share->getNode()->getPath())
255
		);
256
257
		if ($share->getShareOwner() !== $share->getSharedBy()) {
258
			$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
259
			$fileId = $share->getNode()->getId();
260
			$nodes = $ownerFolder->getById($fileId);
261
			$ownerPath = $nodes[0]->getPath();
262
			$this->publishActivity(
263
				Activity::SUBJECT_SHARED_EMAIL_BY,
264
				[$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
265
				$share->getShareOwner(),
266
				$fileId,
267
				$ownerFolder->getRelativePath($ownerPath)
268
			);
269
		}
270
271
	}
272
273
	/**
274
	 * create activity if a file/folder was shared by mail
275
	 *
276
	 * @param IShare $share
277
	 * @param string $sharedWith
278
	 * @param bool $sendToSelf
279
	 */
280
	protected function createPasswordSendActivity(IShare $share, $sharedWith, $sendToSelf) {
281
282
		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
283
284
		if ($sendToSelf) {
285
			$this->publishActivity(
286
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
287
				[$userFolder->getRelativePath($share->getNode()->getPath())],
288
				$share->getSharedBy(),
289
				$share->getNode()->getId(),
290
				$userFolder->getRelativePath($share->getNode()->getPath())
291
			);
292
		} else {
293
			$this->publishActivity(
294
				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
295
				[$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
296
				$share->getSharedBy(),
297
				$share->getNode()->getId(),
298
				$userFolder->getRelativePath($share->getNode()->getPath())
299
			);
300
		}
301
	}
302
303
304
	/**
305
	 * publish activity if a file/folder was shared by mail
306
	 *
307
	 * @param $subject
308
	 * @param $parameters
309
	 * @param $affectedUser
310
	 * @param $fileId
311
	 * @param $filePath
312
	 */
313 View Code Duplication
	protected function publishActivity($subject, $parameters, $affectedUser, $fileId, $filePath) {
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...
314
		$event = $this->activityManager->generateEvent();
315
		$event->setApp('sharebymail')
316
			->setType('shared')
317
			->setSubject($subject, $parameters)
318
			->setAffectedUser($affectedUser)
319
			->setObject('files', $fileId, $filePath);
320
		$this->activityManager->publish($event);
321
322
	}
323
324
	/**
325
	 * @param IShare $share
326
	 * @return int
327
	 * @throws \Exception
328
	 */
329
	protected function createMailShare(IShare $share) {
330
		$share->setToken($this->generateToken());
331
		$shareId = $this->addShareToDB(
332
			$share->getNodeId(),
333
			$share->getNodeType(),
334
			$share->getSharedWith(),
335
			$share->getSharedBy(),
336
			$share->getShareOwner(),
337
			$share->getPermissions(),
338
			$share->getToken(),
339
			$share->getPassword()
340
		);
341
342
		try {
343
			$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
344
				['token' => $share->getToken()]);
345
			$this->sendMailNotification(
346
				$share->getNode()->getName(),
347
				$link,
348
				$share->getShareOwner(),
349
				$share->getSharedBy(),
350
				$share->getSharedWith()
351
			);
352
		} catch (HintException $hintException) {
353
			$this->logger->error('Failed to send share by mail: ' . $hintException->getMessage());
354
			$this->removeShareFromTable($shareId);
355
			throw $hintException;
356
		} catch (\Exception $e) {
357
			$this->logger->error('Failed to send share by mail: ' . $e->getMessage());
358
			$this->removeShareFromTable($shareId);
359
			throw new HintException('Failed to send share by mail',
360
				$this->l->t('Failed to send share by E-mail'));
361
		}
362
363
		return $shareId;
364
365
	}
366
367
	/**
368
	 * @param string $filename
369
	 * @param string $link
370
	 * @param string $owner
371
	 * @param string $initiator
372
	 * @param string $shareWith
373
	 * @throws \Exception If mail couldn't be sent
374
	 */
375
	protected function sendMailNotification($filename,
376
											$link,
377
											$owner,
378
											$initiator,
379
											$shareWith) {
380
		$ownerUser = $this->userManager->get($owner);
381
		$initiatorUser = $this->userManager->get($initiator);
382
		$ownerDisplayName = ($ownerUser instanceof IUser) ? $ownerUser->getDisplayName() : $owner;
383
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
384 View Code Duplication
		if ($owner === $initiator) {
385
			$subject = (string)$this->l->t('%s shared »%s« with you', array($ownerDisplayName, $filename));
386
		} else {
387
			$subject = (string)$this->l->t('%s shared »%s« with you on behalf of %s', array($ownerDisplayName, $filename, $initiatorDisplayName));
388
		}
389
390
		$message = $this->mailer->createMessage();
391
392
		$emailTemplate = $this->mailer->createEMailTemplate();
393
394
		$emailTemplate->addHeader();
395
		$emailTemplate->addHeading($this->l->t('%s shared »%s« with you', [$ownerDisplayName, $filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
396 View Code Duplication
		if ($owner === $initiator) {
397
			$text = $this->l->t('%s shared »%s« with you.', [$ownerDisplayName, $filename]);
398
		} else {
399
			$text= $this->l->t('%s shared »%s« with you on behalf of %s.', [$ownerDisplayName, $filename, $initiator]);
400
		}
401
		$emailTemplate->addBodyText(
402
			$text . ' ' . $this->l->t('Click the button below to open it.'),
403
			$text
404
		);
405
		$emailTemplate->addBodyButton(
406
			$this->l->t('Open »%s«', [$filename]),
407
			$link
408
		);
409
410
		$message->setTo([$shareWith]);
411
412
		// The "From" contains the sharers name
413
		$instanceName = $this->defaults->getName();
414
		$senderName = $this->l->t(
415
			'%s via %s',
416
			[
417
				$ownerDisplayName,
418
				$instanceName
419
			]
420
		);
421
		$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
422
423
		// The "Reply-To" is set to the sharer if an mail address is configured
424
		// also the default footer contains a "Do not reply" which needs to be adjusted.
425
		$ownerEmail = $ownerUser->getEMailAddress();
426
		if($ownerEmail !== null) {
427
			$message->setReplyTo([$ownerEmail => $ownerDisplayName]);
428
			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
429
		} else {
430
			$emailTemplate->addFooter();
431
		}
432
433
		$message->setSubject($subject);
434
		$message->setPlainBody($emailTemplate->renderText());
435
		$message->setHtmlBody($emailTemplate->renderHtml());
436
		$this->mailer->send($message);
437
	}
438
439
	/**
440
	 * send password to recipient of a mail share
441
	 *
442
	 * @param IShare $share
443
	 * @param string $password
444
	 * @return bool
445
	 */
446
	protected function sendPassword(IShare $share, $password) {
447
448
		$filename = $share->getNode()->getName();
449
		$initiator = $share->getSharedBy();
450
		$shareWith = $share->getSharedWith();
451
452
		if ($password === '' || $this->settingsManager->sendPasswordByMail() === false) {
453
			return false;
454
		}
455
456
		$initiatorUser = $this->userManager->get($initiator);
457
		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
458
		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
459
460
		$subject = (string)$this->l->t('Password to access »%s« shared to you by %s', [$filename, $initiatorDisplayName]);
461
		$plainBodyPart = $this->l->t("%s shared »%s« with you.\nYou should have already received a separate mail with a link to access it.\n", [$initiatorDisplayName, $filename]);
462
		$htmlBodyPart = $this->l->t('%s shared »%s« with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
463
464
		$message = $this->mailer->createMessage();
465
466
		$emailTemplate = $this->mailer->createEMailTemplate();
467
		$emailTemplate->addHeader();
468
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
469
		$emailTemplate->addBodyText($htmlBodyPart, $plainBodyPart);
470
		$emailTemplate->addBodyText($this->l->t('It is protected with the following password: %s', [$password]));
471
		$emailTemplate->addFooter();
472
473
		if ($initiatorEmailAddress !== null) {
474
			$message->setFrom([$initiatorEmailAddress => $initiatorDisplayName]);
475
		}
476
		$message->setTo([$shareWith]);
477
		$message->setSubject($subject);
478
		$message->setBody($emailTemplate->renderText(), 'text/plain');
479
		$message->setHtmlBody($emailTemplate->renderHtml());
480
		$this->mailer->send($message);
481
482
		$this->createPasswordSendActivity($share, $shareWith, false);
483
484
		return true;
485
	}
486
487
	/**
488
	 * send auto generated password to the owner. This happens if the admin enforces
489
	 * a password for mail shares and forbid to send the password by mail to the recipient
490
	 *
491
	 * @param IShare $share
492
	 * @param string $password
493
	 * @return bool
494
	 * @throws \Exception
495
	 */
496
	protected function sendPasswordToOwner(IShare $share, $password) {
497
498
		$filename = $share->getNode()->getName();
499
		$initiator = $this->userManager->get($share->getSharedBy());
500
		$initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
501
		$initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
502
		$shareWith = $share->getSharedWith();
503
504
		if ($initiatorEMailAddress === null) {
505
			throw new \Exception(
506
				$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.")
507
			);
508
		}
509
510
		$subject = (string)$this->l->t('Password to access »%s« shared with %s', [$filename, $shareWith]);
511
		$bodyPart = $this->l->t("You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %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()]);
512
513
		$message = $this->mailer->createMessage();
514
		$emailTemplate = $this->mailer->createEMailTemplate();
515
516
		$emailTemplate->addHeader();
517
		$emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
518
		$emailTemplate->addBodyText($bodyPart);
519
		$emailTemplate->addBodyText($this->l->t('This is the password: %s', [$password]));
520
		$emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
521
		$emailTemplate->addFooter();
522
523
		if ($initiatorEMailAddress) {
524
			$message->setFrom([$initiatorEMailAddress => $initiatorDisplayName]);
525
		}
526
		$message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
527
		$message->setSubject($subject);
528
		$message->setBody($emailTemplate->renderText(), 'text/plain');
529
		$message->setHtmlBody($emailTemplate->renderHtml());
530
		$this->mailer->send($message);
531
532
		$this->createPasswordSendActivity($share, $shareWith, true);
533
534
		return true;
535
	}
536
537
	/**
538
	 * generate share token
539
	 *
540
	 * @return string
541
	 */
542
	protected function generateToken($size = 15) {
543
		$token = $this->secureRandom->generate(
544
			$size, ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS);
545
		return $token;
546
	}
547
548
	/**
549
	 * Get all children of this share
550
	 *
551
	 * @param IShare $parent
552
	 * @return IShare[]
553
	 */
554 View Code Duplication
	public function getChildren(IShare $parent) {
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...
555
		$children = [];
556
557
		$qb = $this->dbConnection->getQueryBuilder();
558
		$qb->select('*')
559
			->from('share')
560
			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
561
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
562
			->orderBy('id');
563
564
		$cursor = $qb->execute();
565
		while($data = $cursor->fetch()) {
566
			$children[] = $this->createShareObject($data);
567
		}
568
		$cursor->closeCursor();
569
570
		return $children;
571
	}
572
573
	/**
574
	 * add share to the database and return the ID
575
	 *
576
	 * @param int $itemSource
577
	 * @param string $itemType
578
	 * @param string $shareWith
579
	 * @param string $sharedBy
580
	 * @param string $uidOwner
581
	 * @param int $permissions
582
	 * @param string $token
583
	 * @return int
584
	 */
585
	protected function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $password) {
586
		$qb = $this->dbConnection->getQueryBuilder();
587
		$qb->insert('share')
588
			->setValue('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
589
			->setValue('item_type', $qb->createNamedParameter($itemType))
590
			->setValue('item_source', $qb->createNamedParameter($itemSource))
591
			->setValue('file_source', $qb->createNamedParameter($itemSource))
592
			->setValue('share_with', $qb->createNamedParameter($shareWith))
593
			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
594
			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
595
			->setValue('permissions', $qb->createNamedParameter($permissions))
596
			->setValue('token', $qb->createNamedParameter($token))
597
			->setValue('password', $qb->createNamedParameter($password))
598
			->setValue('stime', $qb->createNamedParameter(time()));
599
600
		/*
601
		 * Added to fix https://github.com/owncloud/core/issues/22215
602
		 * Can be removed once we get rid of ajax/share.php
603
		 */
604
		$qb->setValue('file_target', $qb->createNamedParameter(''));
605
606
		$qb->execute();
607
		$id = $qb->getLastInsertId();
608
609
		return (int)$id;
610
	}
611
612
	/**
613
	 * Update a share
614
	 *
615
	 * @param IShare $share
616
	 * @param string|null $plainTextPassword
617
	 * @return IShare The share object
618
	 */
619
	public function update(IShare $share, $plainTextPassword = null) {
620
621
		$originalShare = $this->getShareById($share->getId());
622
623
		// a real password was given
624
		$validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
625
626
		if($validPassword && $originalShare->getPassword() !== $share->getPassword()) {
627
			$this->sendPassword($share, $plainTextPassword);
628
		}
629
		/*
630
		 * We allow updating the permissions and password of mail shares
631
		 */
632
		$qb = $this->dbConnection->getQueryBuilder();
633
		$qb->update('share')
634
			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
635
			->set('permissions', $qb->createNamedParameter($share->getPermissions()))
636
			->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
637
			->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
638
			->set('password', $qb->createNamedParameter($share->getPassword()))
639
			->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
640
			->execute();
641
642
		return $share;
643
	}
644
645
	/**
646
	 * @inheritdoc
647
	 */
648
	public function move(IShare $share, $recipient) {
649
		/**
650
		 * nothing to do here, mail shares are only outgoing shares
651
		 */
652
		return $share;
653
	}
654
655
	/**
656
	 * Delete a share (owner unShares the file)
657
	 *
658
	 * @param IShare $share
659
	 */
660
	public function delete(IShare $share) {
661
		$this->removeShareFromTable($share->getId());
662
	}
663
664
	/**
665
	 * @inheritdoc
666
	 */
667
	public function deleteFromSelf(IShare $share, $recipient) {
668
		// nothing to do here, mail shares are only outgoing shares
669
		return;
670
	}
671
672
	/**
673
	 * @inheritdoc
674
	 */
675 View Code Duplication
	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
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...
676
		$qb = $this->dbConnection->getQueryBuilder();
677
		$qb->select('*')
678
			->from('share');
679
680
		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
681
682
		/**
683
		 * Reshares for this user are shares where they are the owner.
684
		 */
685
		if ($reshares === false) {
686
			//Special case for old shares created via the web UI
687
			$or1 = $qb->expr()->andX(
688
				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
689
				$qb->expr()->isNull('uid_initiator')
690
			);
691
692
			$qb->andWhere(
693
				$qb->expr()->orX(
694
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
695
					$or1
696
				)
697
			);
698
		} else {
699
			$qb->andWhere(
700
				$qb->expr()->orX(
701
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
702
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
703
				)
704
			);
705
		}
706
707
		if ($node !== null) {
708
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
709
		}
710
711
		if ($limit !== -1) {
712
			$qb->setMaxResults($limit);
713
		}
714
715
		$qb->setFirstResult($offset);
716
		$qb->orderBy('id');
717
718
		$cursor = $qb->execute();
719
		$shares = [];
720
		while($data = $cursor->fetch()) {
721
			$shares[] = $this->createShareObject($data);
722
		}
723
		$cursor->closeCursor();
724
725
		return $shares;
726
	}
727
728
	/**
729
	 * @inheritdoc
730
	 */
731 View Code Duplication
	public function getShareById($id, $recipientId = null) {
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...
732
		$qb = $this->dbConnection->getQueryBuilder();
733
734
		$qb->select('*')
735
			->from('share')
736
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
737
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
738
739
		$cursor = $qb->execute();
740
		$data = $cursor->fetch();
741
		$cursor->closeCursor();
742
743
		if ($data === false) {
744
			throw new ShareNotFound();
745
		}
746
747
		try {
748
			$share = $this->createShareObject($data);
749
		} catch (InvalidShare $e) {
750
			throw new ShareNotFound();
751
		}
752
753
		return $share;
754
	}
755
756
	/**
757
	 * Get shares for a given path
758
	 *
759
	 * @param \OCP\Files\Node $path
760
	 * @return IShare[]
761
	 */
762 View Code Duplication
	public function getSharesByPath(Node $path) {
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...
763
		$qb = $this->dbConnection->getQueryBuilder();
764
765
		$cursor = $qb->select('*')
766
			->from('share')
767
			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
768
			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
769
			->execute();
770
771
		$shares = [];
772
		while($data = $cursor->fetch()) {
773
			$shares[] = $this->createShareObject($data);
774
		}
775
		$cursor->closeCursor();
776
777
		return $shares;
778
	}
779
780
	/**
781
	 * @inheritdoc
782
	 */
783 View Code Duplication
	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
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...
784
		/** @var IShare[] $shares */
785
		$shares = [];
786
787
		//Get shares directly with this user
788
		$qb = $this->dbConnection->getQueryBuilder();
789
		$qb->select('*')
790
			->from('share');
791
792
		// Order by id
793
		$qb->orderBy('id');
794
795
		// Set limit and offset
796
		if ($limit !== -1) {
797
			$qb->setMaxResults($limit);
798
		}
799
		$qb->setFirstResult($offset);
800
801
		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
802
		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
803
804
		// Filter by node if provided
805
		if ($node !== null) {
806
			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
807
		}
808
809
		$cursor = $qb->execute();
810
811
		while($data = $cursor->fetch()) {
812
			$shares[] = $this->createShareObject($data);
813
		}
814
		$cursor->closeCursor();
815
816
817
		return $shares;
818
	}
819
820
	/**
821
	 * Get a share by token
822
	 *
823
	 * @param string $token
824
	 * @return IShare
825
	 * @throws ShareNotFound
826
	 */
827 View Code Duplication
	public function getShareByToken($token) {
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...
828
		$qb = $this->dbConnection->getQueryBuilder();
829
830
		$cursor = $qb->select('*')
831
			->from('share')
832
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
833
			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
834
			->execute();
835
836
		$data = $cursor->fetch();
837
838
		if ($data === false) {
839
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
840
		}
841
842
		try {
843
			$share = $this->createShareObject($data);
844
		} catch (InvalidShare $e) {
845
			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
846
		}
847
848
		return $share;
849
	}
850
851
	/**
852
	 * remove share from table
853
	 *
854
	 * @param string $shareId
855
	 */
856
	protected function removeShareFromTable($shareId) {
857
		$qb = $this->dbConnection->getQueryBuilder();
858
		$qb->delete('share')
859
			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
860
		$qb->execute();
861
	}
862
863
	/**
864
	 * Create a share object from an database row
865
	 *
866
	 * @param array $data
867
	 * @return IShare
868
	 * @throws InvalidShare
869
	 * @throws ShareNotFound
870
	 */
871
	protected function createShareObject($data) {
872
873
		$share = new Share($this->rootFolder, $this->userManager);
874
		$share->setId((int)$data['id'])
875
			->setShareType((int)$data['share_type'])
876
			->setPermissions((int)$data['permissions'])
877
			->setTarget($data['file_target'])
878
			->setMailSend((bool)$data['mail_send'])
879
			->setToken($data['token']);
880
881
		$shareTime = new \DateTime();
882
		$shareTime->setTimestamp((int)$data['stime']);
883
		$share->setShareTime($shareTime);
884
		$share->setSharedWith($data['share_with']);
885
		$share->setPassword($data['password']);
886
887
		if ($data['uid_initiator'] !== null) {
888
			$share->setShareOwner($data['uid_owner']);
889
			$share->setSharedBy($data['uid_initiator']);
890
		} else {
891
			//OLD SHARE
892
			$share->setSharedBy($data['uid_owner']);
893
			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
894
895
			$owner = $path->getOwner();
896
			$share->setShareOwner($owner->getUID());
897
		}
898
899 View Code Duplication
		if ($data['expiration'] !== null) {
900
			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
901
			if ($expiration !== false) {
902
				$share->setExpirationDate($expiration);
903
			}
904
		}
905
906
		$share->setNodeId((int)$data['file_source']);
907
		$share->setNodeType($data['item_type']);
908
909
		$share->setProviderId($this->identifier());
910
911
		return $share;
912
	}
913
914
	/**
915
	 * Get the node with file $id for $user
916
	 *
917
	 * @param string $userId
918
	 * @param int $id
919
	 * @return \OCP\Files\File|\OCP\Files\Folder
920
	 * @throws InvalidShare
921
	 */
922 View Code Duplication
	private function getNode($userId, $id) {
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...
923
		try {
924
			$userFolder = $this->rootFolder->getUserFolder($userId);
925
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCA\ShareByMail\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...
926
			throw new InvalidShare();
927
		}
928
929
		$nodes = $userFolder->getById($id);
930
931
		if (empty($nodes)) {
932
			throw new InvalidShare();
933
		}
934
935
		return $nodes[0];
936
	}
937
938
	/**
939
	 * A user is deleted from the system
940
	 * So clean up the relevant shares.
941
	 *
942
	 * @param string $uid
943
	 * @param int $shareType
944
	 */
945 View Code Duplication
	public function userDeleted($uid, $shareType) {
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...
946
		$qb = $this->dbConnection->getQueryBuilder();
947
948
		$qb->delete('share')
949
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
950
			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
951
			->execute();
952
	}
953
954
	/**
955
	 * This provider does not support group shares
956
	 *
957
	 * @param string $gid
958
	 */
959
	public function groupDeleted($gid) {
960
		return;
961
	}
962
963
	/**
964
	 * This provider does not support group shares
965
	 *
966
	 * @param string $uid
967
	 * @param string $gid
968
	 */
969
	public function userDeletedFromGroup($uid, $gid) {
970
		return;
971
	}
972
973
	/**
974
	 * get database row of a give share
975
	 *
976
	 * @param $id
977
	 * @return array
978
	 * @throws ShareNotFound
979
	 */
980 View Code Duplication
	protected function getRawShare($id) {
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...
981
982
		// Now fetch the inserted share and create a complete share object
983
		$qb = $this->dbConnection->getQueryBuilder();
984
		$qb->select('*')
985
			->from('share')
986
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
987
988
		$cursor = $qb->execute();
989
		$data = $cursor->fetch();
990
		$cursor->closeCursor();
991
992
		if ($data === false) {
993
			throw new ShareNotFound;
994
		}
995
996
		return $data;
997
	}
998
999 View Code Duplication
	public function getSharesInFolder($userId, Folder $node, $reshares) {
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...
1000
		$qb = $this->dbConnection->getQueryBuilder();
1001
		$qb->select('*')
1002
			->from('share', 's')
1003
			->andWhere($qb->expr()->orX(
1004
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1005
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1006
			))
1007
			->andWhere(
1008
				$qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
1009
			);
1010
1011
		/**
1012
		 * Reshares for this user are shares where they are the owner.
1013
		 */
1014
		if ($reshares === false) {
1015
			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
1016
		} else {
1017
			$qb->andWhere(
1018
				$qb->expr()->orX(
1019
					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
1020
					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
1021
				)
1022
			);
1023
		}
1024
1025
		$qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
1026
		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
1027
1028
		$qb->orderBy('id');
1029
1030
		$cursor = $qb->execute();
1031
		$shares = [];
1032
		while ($data = $cursor->fetch()) {
1033
			$shares[$data['fileid']][] = $this->createShareObject($data);
1034
		}
1035
		$cursor->closeCursor();
1036
1037
		return $shares;
1038
	}
1039
1040
	/**
1041
	 * @inheritdoc
1042
	 */
1043
	public function getAccessList($nodes, $currentAccess) {
1044
		$ids = [];
1045
		foreach ($nodes as $node) {
1046
			$ids[] = $node->getId();
1047
		}
1048
1049
		$qb = $this->dbConnection->getQueryBuilder();
1050
		$qb->select('share_with')
1051
			->from('share')
1052
			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
1053
			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1054
			->andWhere($qb->expr()->orX(
1055
				$qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
1056
				$qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
1057
			))
1058
			->setMaxResults(1);
1059
		$cursor = $qb->execute();
1060
1061
		$mail = $cursor->fetch() !== false;
1062
		$cursor->closeCursor();
1063
1064
		return ['public' => $mail];
1065
	}
1066
1067
}
1068