ShareByMailProvider.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
  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. namespace OCA\ShareByMail;
  22. use OC\CapabilitiesManager;
  23. use OC\HintException;
  24. use OC\Share20\Exception\InvalidShare;
  25. use OC\User\NoUserException;
  26. use OCA\ShareByMail\Settings\SettingsManager;
  27. use OCP\Activity\IManager;
  28. use OCP\DB\QueryBuilder\IQueryBuilder;
  29. use OCP\Defaults;
  30. use OCP\Files\Folder;
  31. use OCP\Files\IRootFolder;
  32. use OCP\Files\Node;
  33. use OCP\IDBConnection;
  34. use OCP\IL10N;
  35. use OCP\ILogger;
  36. use OCP\IURLGenerator;
  37. use OCP\IUser;
  38. use OCP\IUserManager;
  39. use OCP\Mail\IMailer;
  40. use OCP\Security\IHasher;
  41. use OCP\Security\ISecureRandom;
  42. use OC\Share20\Share;
  43. use OCP\Share\Exceptions\GenericShareException;
  44. use OCP\Share\Exceptions\ShareNotFound;
  45. use OCP\Share\IShare;
  46. use OCP\Share\IShareProvider;
  47. /**
  48. * Class ShareByMail
  49. *
  50. * @package OCA\ShareByMail
  51. */
  52. class ShareByMailProvider implements IShareProvider {
  53. /** @var IDBConnection */
  54. private $dbConnection;
  55. /** @var ILogger */
  56. private $logger;
  57. /** @var ISecureRandom */
  58. private $secureRandom;
  59. /** @var IUserManager */
  60. private $userManager;
  61. /** @var IRootFolder */
  62. private $rootFolder;
  63. /** @var IL10N */
  64. private $l;
  65. /** @var IMailer */
  66. private $mailer;
  67. /** @var IURLGenerator */
  68. private $urlGenerator;
  69. /** @var IManager */
  70. private $activityManager;
  71. /** @var SettingsManager */
  72. private $settingsManager;
  73. /** @var Defaults */
  74. private $defaults;
  75. /** @var IHasher */
  76. private $hasher;
  77. /** @var CapabilitiesManager */
  78. private $capabilitiesManager;
  79. /**
  80. * Return the identifier of this provider.
  81. *
  82. * @return string Containing only [a-zA-Z0-9]
  83. */
  84. public function identifier() {
  85. return 'ocMailShare';
  86. }
  87. /**
  88. * DefaultShareProvider constructor.
  89. *
  90. * @param IDBConnection $connection
  91. * @param ISecureRandom $secureRandom
  92. * @param IUserManager $userManager
  93. * @param IRootFolder $rootFolder
  94. * @param IL10N $l
  95. * @param ILogger $logger
  96. * @param IMailer $mailer
  97. * @param IURLGenerator $urlGenerator
  98. * @param IManager $activityManager
  99. * @param SettingsManager $settingsManager
  100. * @param Defaults $defaults
  101. * @param IHasher $hasher
  102. * @param CapabilitiesManager $capabilitiesManager
  103. */
  104. public function __construct(
  105. IDBConnection $connection,
  106. ISecureRandom $secureRandom,
  107. IUserManager $userManager,
  108. IRootFolder $rootFolder,
  109. IL10N $l,
  110. ILogger $logger,
  111. IMailer $mailer,
  112. IURLGenerator $urlGenerator,
  113. IManager $activityManager,
  114. SettingsManager $settingsManager,
  115. Defaults $defaults,
  116. IHasher $hasher,
  117. CapabilitiesManager $capabilitiesManager
  118. ) {
  119. $this->dbConnection = $connection;
  120. $this->secureRandom = $secureRandom;
  121. $this->userManager = $userManager;
  122. $this->rootFolder = $rootFolder;
  123. $this->l = $l;
  124. $this->logger = $logger;
  125. $this->mailer = $mailer;
  126. $this->urlGenerator = $urlGenerator;
  127. $this->activityManager = $activityManager;
  128. $this->settingsManager = $settingsManager;
  129. $this->defaults = $defaults;
  130. $this->hasher = $hasher;
  131. $this->capabilitiesManager = $capabilitiesManager;
  132. }
  133. /**
  134. * Share a path
  135. *
  136. * @param IShare $share
  137. * @return IShare The share object
  138. * @throws ShareNotFound
  139. * @throws \Exception
  140. */
  141. public function create(IShare $share) {
  142. $shareWith = $share->getSharedWith();
  143. /*
  144. * Check if file is not already shared with the remote user
  145. */
  146. $alreadyShared = $this->getSharedWith($shareWith, \OCP\Share::SHARE_TYPE_EMAIL, $share->getNode(), 1, 0);
  147. if (!empty($alreadyShared)) {
  148. $message = 'Sharing %s failed, this item is already shared with %s';
  149. $message_t = $this->l->t('Sharing %s failed, this item is already shared with %s', array($share->getNode()->getName(), $shareWith));
  150. $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
  151. throw new \Exception($message_t);
  152. }
  153. // if the admin enforces a password for all mail shares we create a
  154. // random password and send it to the recipient
  155. $password = '';
  156. $passwordEnforced = $this->settingsManager->enforcePasswordProtection();
  157. if ($passwordEnforced) {
  158. $password = $this->autoGeneratePassword($share);
  159. }
  160. $shareId = $this->createMailShare($share);
  161. $send = $this->sendPassword($share, $password);
  162. if ($passwordEnforced && $send === false) {
  163. $this->sendPasswordToOwner($share, $password);
  164. }
  165. $this->createShareActivity($share);
  166. $data = $this->getRawShare($shareId);
  167. return $this->createShareObject($data);
  168. }
  169. /**
  170. * auto generate password in case of password enforcement on mail shares
  171. *
  172. * @param IShare $share
  173. * @return string
  174. * @throws \Exception
  175. */
  176. protected function autoGeneratePassword($share) {
  177. $initiatorUser = $this->userManager->get($share->getSharedBy());
  178. $initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
  179. $allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
  180. if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
  181. throw new \Exception(
  182. $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.")
  183. );
  184. }
  185. $passwordPolicy = $this->getPasswordPolicy();
  186. $passwordCharset = ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_DIGITS;
  187. $passwordLength = 8;
  188. if (!empty($passwordPolicy)) {
  189. $passwordLength = (int)$passwordPolicy['minLength'] > 0 ? (int)$passwordPolicy['minLength'] : $passwordLength;
  190. $passwordCharset .= $passwordPolicy['enforceSpecialCharacters'] ? ISecureRandom::CHAR_SYMBOLS : '';
  191. }
  192. $password = $this->secureRandom->generate($passwordLength, $passwordCharset);
  193. $share->setPassword($this->hasher->hash($password));
  194. return $password;
  195. }
  196. /**
  197. * get password policy
  198. *
  199. * @return array
  200. */
  201. protected function getPasswordPolicy() {
  202. $capabilities = $this->capabilitiesManager->getCapabilities();
  203. if (isset($capabilities['password_policy'])) {
  204. return $capabilities['password_policy'];
  205. }
  206. return [];
  207. }
  208. /**
  209. * create activity if a file/folder was shared by mail
  210. *
  211. * @param IShare $share
  212. */
  213. protected function createShareActivity(IShare $share) {
  214. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  215. $this->publishActivity(
  216. Activity::SUBJECT_SHARED_EMAIL_SELF,
  217. [$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
  218. $share->getSharedBy(),
  219. $share->getNode()->getId(),
  220. $userFolder->getRelativePath($share->getNode()->getPath())
  221. );
  222. if ($share->getShareOwner() !== $share->getSharedBy()) {
  223. $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
  224. $fileId = $share->getNode()->getId();
  225. $nodes = $ownerFolder->getById($fileId);
  226. $ownerPath = $nodes[0]->getPath();
  227. $this->publishActivity(
  228. Activity::SUBJECT_SHARED_EMAIL_BY,
  229. [$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
  230. $share->getShareOwner(),
  231. $fileId,
  232. $ownerFolder->getRelativePath($ownerPath)
  233. );
  234. }
  235. }
  236. /**
  237. * create activity if a file/folder was shared by mail
  238. *
  239. * @param IShare $share
  240. * @param string $sharedWith
  241. * @param bool $sendToSelf
  242. */
  243. protected function createPasswordSendActivity(IShare $share, $sharedWith, $sendToSelf) {
  244. $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
  245. if ($sendToSelf) {
  246. $this->publishActivity(
  247. Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
  248. [$userFolder->getRelativePath($share->getNode()->getPath())],
  249. $share->getSharedBy(),
  250. $share->getNode()->getId(),
  251. $userFolder->getRelativePath($share->getNode()->getPath())
  252. );
  253. } else {
  254. $this->publishActivity(
  255. Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
  256. [$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
  257. $share->getSharedBy(),
  258. $share->getNode()->getId(),
  259. $userFolder->getRelativePath($share->getNode()->getPath())
  260. );
  261. }
  262. }
  263. /**
  264. * publish activity if a file/folder was shared by mail
  265. *
  266. * @param $subject
  267. * @param $parameters
  268. * @param $affectedUser
  269. * @param $fileId
  270. * @param $filePath
  271. */
  272. protected function publishActivity($subject, $parameters, $affectedUser, $fileId, $filePath) {
  273. $event = $this->activityManager->generateEvent();
  274. $event->setApp('sharebymail')
  275. ->setType('shared')
  276. ->setSubject($subject, $parameters)
  277. ->setAffectedUser($affectedUser)
  278. ->setObject('files', $fileId, $filePath);
  279. $this->activityManager->publish($event);
  280. }
  281. /**
  282. * @param IShare $share
  283. * @return int
  284. * @throws \Exception
  285. */
  286. protected function createMailShare(IShare $share) {
  287. $share->setToken($this->generateToken());
  288. $shareId = $this->addShareToDB(
  289. $share->getNodeId(),
  290. $share->getNodeType(),
  291. $share->getSharedWith(),
  292. $share->getSharedBy(),
  293. $share->getShareOwner(),
  294. $share->getPermissions(),
  295. $share->getToken(),
  296. $share->getPassword(),
  297. $share->getSendPasswordByTalk()
  298. );
  299. try {
  300. $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
  301. ['token' => $share->getToken()]);
  302. $this->sendMailNotification(
  303. $share->getNode()->getName(),
  304. $link,
  305. $share->getSharedBy(),
  306. $share->getSharedWith(),
  307. $share->getExpirationDate()
  308. );
  309. } catch (HintException $hintException) {
  310. $this->logger->logException($hintException, [
  311. 'message' => 'Failed to send share by mail.',
  312. 'level' => ILogger::ERROR,
  313. 'app' => 'sharebymail',
  314. ]);
  315. $this->removeShareFromTable($shareId);
  316. throw $hintException;
  317. } catch (\Exception $e) {
  318. $this->logger->logException($e, [
  319. 'message' => 'Failed to send share by mail.',
  320. 'level' => ILogger::ERROR,
  321. 'app' => 'sharebymail',
  322. ]);
  323. $this->removeShareFromTable($shareId);
  324. throw new HintException('Failed to send share by mail',
  325. $this->l->t('Failed to send share by email'));
  326. }
  327. return $shareId;
  328. }
  329. /**
  330. * @param string $filename
  331. * @param string $link
  332. * @param string $initiator
  333. * @param string $shareWith
  334. * @param \DateTime|null $expiration
  335. * @throws \Exception If mail couldn't be sent
  336. */
  337. protected function sendMailNotification($filename,
  338. $link,
  339. $initiator,
  340. $shareWith,
  341. \DateTime $expiration = null) {
  342. $initiatorUser = $this->userManager->get($initiator);
  343. $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
  344. $message = $this->mailer->createMessage();
  345. $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [
  346. 'filename' => $filename,
  347. 'link' => $link,
  348. 'initiator' => $initiatorDisplayName,
  349. 'expiration' => $expiration,
  350. 'shareWith' => $shareWith,
  351. ]);
  352. $emailTemplate->setSubject($this->l->t('%s shared »%s« with you', array($initiatorDisplayName, $filename)));
  353. $emailTemplate->addHeader();
  354. $emailTemplate->addHeading($this->l->t('%s shared »%s« with you', [$initiatorDisplayName, $filename]), false);
  355. $text = $this->l->t('%s shared »%s« with you.', [$initiatorDisplayName, $filename]);
  356. $emailTemplate->addBodyText(
  357. htmlspecialchars($text . ' ' . $this->l->t('Click the button below to open it.')),
  358. $text
  359. );
  360. $emailTemplate->addBodyButton(
  361. $this->l->t('Open »%s«', [$filename]),
  362. $link
  363. );
  364. $message->setTo([$shareWith]);
  365. // The "From" contains the sharers name
  366. $instanceName = $this->defaults->getName();
  367. $senderName = $this->l->t(
  368. '%s via %s',
  369. [
  370. $initiatorDisplayName,
  371. $instanceName
  372. ]
  373. );
  374. $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
  375. // The "Reply-To" is set to the sharer if an mail address is configured
  376. // also the default footer contains a "Do not reply" which needs to be adjusted.
  377. $initiatorEmail = $initiatorUser->getEMailAddress();
  378. if($initiatorEmail !== null) {
  379. $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
  380. $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
  381. } else {
  382. $emailTemplate->addFooter();
  383. }
  384. $message->useTemplate($emailTemplate);
  385. $this->mailer->send($message);
  386. }
  387. /**
  388. * send password to recipient of a mail share
  389. *
  390. * @param IShare $share
  391. * @param string $password
  392. * @return bool
  393. */
  394. protected function sendPassword(IShare $share, $password) {
  395. $filename = $share->getNode()->getName();
  396. $initiator = $share->getSharedBy();
  397. $shareWith = $share->getSharedWith();
  398. if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) {
  399. return false;
  400. }
  401. $initiatorUser = $this->userManager->get($initiator);
  402. $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
  403. $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
  404. $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]);
  405. $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]);
  406. $message = $this->mailer->createMessage();
  407. $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [
  408. 'filename' => $filename,
  409. 'password' => $password,
  410. 'initiator' => $initiatorDisplayName,
  411. 'initiatorEmail' => $initiatorEmailAddress,
  412. 'shareWith' => $shareWith,
  413. ]);
  414. $emailTemplate->setSubject($this->l->t('Password to access »%s« shared to you by %s', [$filename, $initiatorDisplayName]));
  415. $emailTemplate->addHeader();
  416. $emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
  417. $emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
  418. $emailTemplate->addBodyText($this->l->t('It is protected with the following password:'));
  419. $emailTemplate->addBodyText($password);
  420. // The "From" contains the sharers name
  421. $instanceName = $this->defaults->getName();
  422. $senderName = $this->l->t(
  423. '%s via %s',
  424. [
  425. $initiatorDisplayName,
  426. $instanceName
  427. ]
  428. );
  429. $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
  430. if ($initiatorEmailAddress !== null) {
  431. $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
  432. $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
  433. } else {
  434. $emailTemplate->addFooter();
  435. }
  436. $message->setTo([$shareWith]);
  437. $message->useTemplate($emailTemplate);
  438. $this->mailer->send($message);
  439. $this->createPasswordSendActivity($share, $shareWith, false);
  440. return true;
  441. }
  442. protected function sendNote(IShare $share) {
  443. $recipient = $share->getSharedWith();
  444. $filename = $share->getNode()->getName();
  445. $initiator = $share->getSharedBy();
  446. $note = $share->getNote();
  447. $initiatorUser = $this->userManager->get($initiator);
  448. $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
  449. $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
  450. $plainHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]);
  451. $htmlHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]);
  452. $message = $this->mailer->createMessage();
  453. $emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote');
  454. $emailTemplate->setSubject($this->l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName]));
  455. $emailTemplate->addHeader();
  456. $emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading);
  457. $emailTemplate->addBodyText(htmlspecialchars($note), $note);
  458. $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
  459. ['token' => $share->getToken()]);
  460. $emailTemplate->addBodyButton(
  461. $this->l->t('Open »%s«', [$filename]),
  462. $link
  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. $message->setTo([$recipient]);
  481. $message->useTemplate($emailTemplate);
  482. $this->mailer->send($message);
  483. }
  484. /**
  485. * send auto generated password to the owner. This happens if the admin enforces
  486. * a password for mail shares and forbid to send the password by mail to the recipient
  487. *
  488. * @param IShare $share
  489. * @param string $password
  490. * @return bool
  491. * @throws \Exception
  492. */
  493. protected function sendPasswordToOwner(IShare $share, $password) {
  494. $filename = $share->getNode()->getName();
  495. $initiator = $this->userManager->get($share->getSharedBy());
  496. $initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
  497. $initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
  498. $shareWith = $share->getSharedWith();
  499. if ($initiatorEMailAddress === null) {
  500. throw new \Exception(
  501. $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.")
  502. );
  503. }
  504. $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()]);
  505. $message = $this->mailer->createMessage();
  506. $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [
  507. 'filename' => $filename,
  508. 'password' => $password,
  509. 'initiator' => $initiatorDisplayName,
  510. 'initiatorEmail' => $initiatorEMailAddress,
  511. 'shareWith' => $shareWith,
  512. ]);
  513. $emailTemplate->setSubject($this->l->t('Password to access »%s« shared with %s', [$filename, $shareWith]));
  514. $emailTemplate->addHeader();
  515. $emailTemplate->addHeading($this->l->t('Password to access »%s«', [$filename]), false);
  516. $emailTemplate->addBodyText($bodyPart);
  517. $emailTemplate->addBodyText($this->l->t('This is the password:'));
  518. $emailTemplate->addBodyText($password);
  519. $emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
  520. $emailTemplate->addFooter();
  521. if ($initiatorEMailAddress) {
  522. $message->setFrom([$initiatorEMailAddress => $initiatorDisplayName]);
  523. }
  524. $message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
  525. $message->useTemplate($emailTemplate);
  526. $this->mailer->send($message);
  527. $this->createPasswordSendActivity($share, $shareWith, true);
  528. return true;
  529. }
  530. /**
  531. * generate share token
  532. *
  533. * @return string
  534. */
  535. protected function generateToken($size = 15) {
  536. $token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE);
  537. return $token;
  538. }
  539. /**
  540. * Get all children of this share
  541. *
  542. * @param IShare $parent
  543. * @return IShare[]
  544. */
  545. public function getChildren(IShare $parent) {
  546. $children = [];
  547. $qb = $this->dbConnection->getQueryBuilder();
  548. $qb->select('*')
  549. ->from('share')
  550. ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
  551. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
  552. ->orderBy('id');
  553. $cursor = $qb->execute();
  554. while($data = $cursor->fetch()) {
  555. $children[] = $this->createShareObject($data);
  556. }
  557. $cursor->closeCursor();
  558. return $children;
  559. }
  560. /**
  561. * add share to the database and return the ID
  562. *
  563. * @param int $itemSource
  564. * @param string $itemType
  565. * @param string $shareWith
  566. * @param string $sharedBy
  567. * @param string $uidOwner
  568. * @param int $permissions
  569. * @param string $token
  570. * @param string $password
  571. * @param bool $sendPasswordByTalk
  572. * @return int
  573. */
  574. protected function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $password, $sendPasswordByTalk) {
  575. $qb = $this->dbConnection->getQueryBuilder();
  576. $qb->insert('share')
  577. ->setValue('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
  578. ->setValue('item_type', $qb->createNamedParameter($itemType))
  579. ->setValue('item_source', $qb->createNamedParameter($itemSource))
  580. ->setValue('file_source', $qb->createNamedParameter($itemSource))
  581. ->setValue('share_with', $qb->createNamedParameter($shareWith))
  582. ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
  583. ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
  584. ->setValue('permissions', $qb->createNamedParameter($permissions))
  585. ->setValue('token', $qb->createNamedParameter($token))
  586. ->setValue('password', $qb->createNamedParameter($password))
  587. ->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
  588. ->setValue('stime', $qb->createNamedParameter(time()));
  589. /*
  590. * Added to fix https://github.com/owncloud/core/issues/22215
  591. * Can be removed once we get rid of ajax/share.php
  592. */
  593. $qb->setValue('file_target', $qb->createNamedParameter(''));
  594. $qb->execute();
  595. $id = $qb->getLastInsertId();
  596. return (int)$id;
  597. }
  598. /**
  599. * Update a share
  600. *
  601. * @param IShare $share
  602. * @param string|null $plainTextPassword
  603. * @return IShare The share object
  604. */
  605. public function update(IShare $share, $plainTextPassword = null) {
  606. $originalShare = $this->getShareById($share->getId());
  607. // a real password was given
  608. $validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
  609. if($validPassword && ($originalShare->getPassword() !== $share->getPassword() ||
  610. ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
  611. $this->sendPassword($share, $plainTextPassword);
  612. }
  613. /*
  614. * We allow updating the permissions and password of mail shares
  615. */
  616. $qb = $this->dbConnection->getQueryBuilder();
  617. $qb->update('share')
  618. ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
  619. ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
  620. ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
  621. ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
  622. ->set('password', $qb->createNamedParameter($share->getPassword()))
  623. ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
  624. ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
  625. ->set('note', $qb->createNamedParameter($share->getNote()))
  626. ->execute();
  627. if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
  628. $this->sendNote($share);
  629. }
  630. return $share;
  631. }
  632. /**
  633. * @inheritdoc
  634. */
  635. public function move(IShare $share, $recipient) {
  636. /**
  637. * nothing to do here, mail shares are only outgoing shares
  638. */
  639. return $share;
  640. }
  641. /**
  642. * Delete a share (owner unShares the file)
  643. *
  644. * @param IShare $share
  645. */
  646. public function delete(IShare $share) {
  647. $this->removeShareFromTable($share->getId());
  648. }
  649. /**
  650. * @inheritdoc
  651. */
  652. public function deleteFromSelf(IShare $share, $recipient) {
  653. // nothing to do here, mail shares are only outgoing shares
  654. }
  655. public function restore(IShare $share, string $recipient): IShare {
  656. throw new GenericShareException('not implemented');
  657. }
  658. /**
  659. * @inheritdoc
  660. */
  661. public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
  662. $qb = $this->dbConnection->getQueryBuilder();
  663. $qb->select('*')
  664. ->from('share');
  665. $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
  666. /**
  667. * Reshares for this user are shares where they are the owner.
  668. */
  669. if ($reshares === false) {
  670. //Special case for old shares created via the web UI
  671. $or1 = $qb->expr()->andX(
  672. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  673. $qb->expr()->isNull('uid_initiator')
  674. );
  675. $qb->andWhere(
  676. $qb->expr()->orX(
  677. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
  678. $or1
  679. )
  680. );
  681. } else {
  682. $qb->andWhere(
  683. $qb->expr()->orX(
  684. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  685. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
  686. )
  687. );
  688. }
  689. if ($node !== null) {
  690. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  691. }
  692. if ($limit !== -1) {
  693. $qb->setMaxResults($limit);
  694. }
  695. $qb->setFirstResult($offset);
  696. $qb->orderBy('id');
  697. $cursor = $qb->execute();
  698. $shares = [];
  699. while($data = $cursor->fetch()) {
  700. $shares[] = $this->createShareObject($data);
  701. }
  702. $cursor->closeCursor();
  703. return $shares;
  704. }
  705. /**
  706. * @inheritdoc
  707. */
  708. public function getShareById($id, $recipientId = null) {
  709. $qb = $this->dbConnection->getQueryBuilder();
  710. $qb->select('*')
  711. ->from('share')
  712. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
  713. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
  714. $cursor = $qb->execute();
  715. $data = $cursor->fetch();
  716. $cursor->closeCursor();
  717. if ($data === false) {
  718. throw new ShareNotFound();
  719. }
  720. try {
  721. $share = $this->createShareObject($data);
  722. } catch (InvalidShare $e) {
  723. throw new ShareNotFound();
  724. }
  725. return $share;
  726. }
  727. /**
  728. * Get shares for a given path
  729. *
  730. * @param \OCP\Files\Node $path
  731. * @return IShare[]
  732. */
  733. public function getSharesByPath(Node $path) {
  734. $qb = $this->dbConnection->getQueryBuilder();
  735. $cursor = $qb->select('*')
  736. ->from('share')
  737. ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
  738. ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
  739. ->execute();
  740. $shares = [];
  741. while($data = $cursor->fetch()) {
  742. $shares[] = $this->createShareObject($data);
  743. }
  744. $cursor->closeCursor();
  745. return $shares;
  746. }
  747. /**
  748. * @inheritdoc
  749. */
  750. public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
  751. /** @var IShare[] $shares */
  752. $shares = [];
  753. //Get shares directly with this user
  754. $qb = $this->dbConnection->getQueryBuilder();
  755. $qb->select('*')
  756. ->from('share');
  757. // Order by id
  758. $qb->orderBy('id');
  759. // Set limit and offset
  760. if ($limit !== -1) {
  761. $qb->setMaxResults($limit);
  762. }
  763. $qb->setFirstResult($offset);
  764. $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)));
  765. $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
  766. // Filter by node if provided
  767. if ($node !== null) {
  768. $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
  769. }
  770. $cursor = $qb->execute();
  771. while($data = $cursor->fetch()) {
  772. $shares[] = $this->createShareObject($data);
  773. }
  774. $cursor->closeCursor();
  775. return $shares;
  776. }
  777. /**
  778. * Get a share by token
  779. *
  780. * @param string $token
  781. * @return IShare
  782. * @throws ShareNotFound
  783. */
  784. public function getShareByToken($token) {
  785. $qb = $this->dbConnection->getQueryBuilder();
  786. $cursor = $qb->select('*')
  787. ->from('share')
  788. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
  789. ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
  790. ->execute();
  791. $data = $cursor->fetch();
  792. if ($data === false) {
  793. throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
  794. }
  795. try {
  796. $share = $this->createShareObject($data);
  797. } catch (InvalidShare $e) {
  798. throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
  799. }
  800. return $share;
  801. }
  802. /**
  803. * remove share from table
  804. *
  805. * @param string $shareId
  806. */
  807. protected function removeShareFromTable($shareId) {
  808. $qb = $this->dbConnection->getQueryBuilder();
  809. $qb->delete('share')
  810. ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
  811. $qb->execute();
  812. }
  813. /**
  814. * Create a share object from an database row
  815. *
  816. * @param array $data
  817. * @return IShare
  818. * @throws InvalidShare
  819. * @throws ShareNotFound
  820. */
  821. protected function createShareObject($data) {
  822. $share = new Share($this->rootFolder, $this->userManager);
  823. $share->setId((int)$data['id'])
  824. ->setShareType((int)$data['share_type'])
  825. ->setPermissions((int)$data['permissions'])
  826. ->setTarget($data['file_target'])
  827. ->setMailSend((bool)$data['mail_send'])
  828. ->setNote($data['note'])
  829. ->setToken($data['token']);
  830. $shareTime = new \DateTime();
  831. $shareTime->setTimestamp((int)$data['stime']);
  832. $share->setShareTime($shareTime);
  833. $share->setSharedWith($data['share_with']);
  834. $share->setPassword($data['password']);
  835. $share->setSendPasswordByTalk($data['password_by_talk']);
  836. if ($data['uid_initiator'] !== null) {
  837. $share->setShareOwner($data['uid_owner']);
  838. $share->setSharedBy($data['uid_initiator']);
  839. } else {
  840. //OLD SHARE
  841. $share->setSharedBy($data['uid_owner']);
  842. $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
  843. $owner = $path->getOwner();
  844. $share->setShareOwner($owner->getUID());
  845. }
  846. if ($data['expiration'] !== null) {
  847. $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
  848. if ($expiration !== false) {
  849. $share->setExpirationDate($expiration);
  850. }
  851. }
  852. $share->setNodeId((int)$data['file_source']);
  853. $share->setNodeType($data['item_type']);
  854. $share->setProviderId($this->identifier());
  855. return $share;
  856. }
  857. /**
  858. * Get the node with file $id for $user
  859. *
  860. * @param string $userId
  861. * @param int $id
  862. * @return \OCP\Files\File|\OCP\Files\Folder
  863. * @throws InvalidShare
  864. */
  865. private function getNode($userId, $id) {
  866. try {
  867. $userFolder = $this->rootFolder->getUserFolder($userId);
  868. } catch (NoUserException $e) {
  869. throw new InvalidShare();
  870. }
  871. $nodes = $userFolder->getById($id);
  872. if (empty($nodes)) {
  873. throw new InvalidShare();
  874. }
  875. return $nodes[0];
  876. }
  877. /**
  878. * A user is deleted from the system
  879. * So clean up the relevant shares.
  880. *
  881. * @param string $uid
  882. * @param int $shareType
  883. */
  884. public function userDeleted($uid, $shareType) {
  885. $qb = $this->dbConnection->getQueryBuilder();
  886. $qb->delete('share')
  887. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
  888. ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
  889. ->execute();
  890. }
  891. /**
  892. * This provider does not support group shares
  893. *
  894. * @param string $gid
  895. */
  896. public function groupDeleted($gid) {
  897. }
  898. /**
  899. * This provider does not support group shares
  900. *
  901. * @param string $uid
  902. * @param string $gid
  903. */
  904. public function userDeletedFromGroup($uid, $gid) {
  905. }
  906. /**
  907. * get database row of a give share
  908. *
  909. * @param $id
  910. * @return array
  911. * @throws ShareNotFound
  912. */
  913. protected function getRawShare($id) {
  914. // Now fetch the inserted share and create a complete share object
  915. $qb = $this->dbConnection->getQueryBuilder();
  916. $qb->select('*')
  917. ->from('share')
  918. ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
  919. $cursor = $qb->execute();
  920. $data = $cursor->fetch();
  921. $cursor->closeCursor();
  922. if ($data === false) {
  923. throw new ShareNotFound;
  924. }
  925. return $data;
  926. }
  927. public function getSharesInFolder($userId, Folder $node, $reshares) {
  928. $qb = $this->dbConnection->getQueryBuilder();
  929. $qb->select('*')
  930. ->from('share', 's')
  931. ->andWhere($qb->expr()->orX(
  932. $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
  933. $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
  934. ))
  935. ->andWhere(
  936. $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL))
  937. );
  938. /**
  939. * Reshares for this user are shares where they are the owner.
  940. */
  941. if ($reshares === false) {
  942. $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
  943. } else {
  944. $qb->andWhere(
  945. $qb->expr()->orX(
  946. $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
  947. $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
  948. )
  949. );
  950. }
  951. $qb->innerJoin('s', 'filecache' ,'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
  952. $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
  953. $qb->orderBy('id');
  954. $cursor = $qb->execute();
  955. $shares = [];
  956. while ($data = $cursor->fetch()) {
  957. $shares[$data['fileid']][] = $this->createShareObject($data);
  958. }
  959. $cursor->closeCursor();
  960. return $shares;
  961. }
  962. /**
  963. * @inheritdoc
  964. */
  965. public function getAccessList($nodes, $currentAccess) {
  966. $ids = [];
  967. foreach ($nodes as $node) {
  968. $ids[] = $node->getId();
  969. }
  970. $qb = $this->dbConnection->getQueryBuilder();
  971. $qb->select('share_with')
  972. ->from('share')
  973. ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_EMAIL)))
  974. ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
  975. ->andWhere($qb->expr()->orX(
  976. $qb->expr()->eq('item_type', $qb->createNamedParameter('file')),
  977. $qb->expr()->eq('item_type', $qb->createNamedParameter('folder'))
  978. ))
  979. ->setMaxResults(1);
  980. $cursor = $qb->execute();
  981. $mail = $cursor->fetch() !== false;
  982. $cursor->closeCursor();
  983. return ['public' => $mail];
  984. }
  985. }