From e96c9e0e4a402277a9f18470c77503dd914c6de4 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 22 Aug 2019 03:17:17 +0200 Subject: [PATCH] Add the notifier and the API endpoint for user shares Signed-off-by: Joas Schilling --- apps/federatedfilesharing/lib/Notifier.php | 2 +- apps/files_sharing/appinfo/app.php | 1 + apps/files_sharing/appinfo/routes.php | 5 + .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + .../files_sharing/lib/AppInfo/Application.php | 11 ++ .../lib/Controller/ShareAPIController.php | 39 +++++++ .../lib/Notification/Listener.php | 72 ++++++++++++ .../lib/Notification/Notifier.php | 105 +++++++++++++++++- lib/private/Share20/DefaultShareProvider.php | 1 + 10 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 apps/files_sharing/lib/Notification/Listener.php diff --git a/apps/federatedfilesharing/lib/Notifier.php b/apps/federatedfilesharing/lib/Notifier.php index 02a46d65bb..1916390a1a 100644 --- a/apps/federatedfilesharing/lib/Notifier.php +++ b/apps/federatedfilesharing/lib/Notifier.php @@ -86,7 +86,7 @@ class Notifier implements INotifier { * @throws \InvalidArgumentException */ public function prepare(INotification $notification, string $languageCode): INotification { - if ($notification->getApp() !== 'files_sharing') { + if ($notification->getApp() !== 'files_sharing' || $notification->getObjectType() !== 'remote_share') { // Not my app => throw throw new \InvalidArgumentException(); } diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index c4f44095dc..8d4b511c26 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -38,6 +38,7 @@ use OCA\Files_Sharing\AppInfo\Application; $application = \OC::$server->query(Application::class); $application->registerMountProviders(); +$application->register(); $eventDispatcher = \OC::$server->getEventDispatcher(); $eventDispatcher->addListener( diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index ce7ba40919..ab5e829f86 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -73,6 +73,11 @@ return [ 'url' => '/api/v1/shares/{id}', 'verb' => 'DELETE', ], + [ + 'name' => 'ShareAPI#acceptShare', + 'url' => '/api/v1/shares/pending/{id}', + 'verb' => 'POST', + ], /* * Deleted Shares */ diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index 63c31f0a3c..d5609a9a60 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -51,6 +51,7 @@ return array( 'OCA\\Files_Sharing\\Migration\\OwncloudGuestShareType' => $baseDir . '/../lib/Migration/OwncloudGuestShareType.php', 'OCA\\Files_Sharing\\Migration\\SetPasswordColumn' => $baseDir . '/../lib/Migration/SetPasswordColumn.php', 'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php', + 'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php', 'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php', 'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php', 'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 659903300c..0975feafec 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -66,6 +66,7 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\Migration\\OwncloudGuestShareType' => __DIR__ . '/..' . '/../lib/Migration/OwncloudGuestShareType.php', 'OCA\\Files_Sharing\\Migration\\SetPasswordColumn' => __DIR__ . '/..' . '/../lib/Migration/SetPasswordColumn.php', 'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php', + 'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php', 'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php', 'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php', 'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php', diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index bba87ba991..92b3a92d43 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -32,6 +32,7 @@ namespace OCA\Files_Sharing\AppInfo; use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware; use OCA\Files_Sharing\Middleware\ShareInfoMiddleware; use OCA\Files_Sharing\MountProvider; +use OCA\Files_Sharing\Notification\Listener; use OCA\Files_Sharing\Notification\Notifier; use OCP\AppFramework\App; use OC\AppFramework\Utility\SimpleContainer; @@ -45,6 +46,7 @@ use \OCP\IContainer; use OCP\IServerContainer; use OCA\Files_Sharing\Capabilities; use OCA\Files_Sharing\External\Manager; +use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { public function __construct(array $urlParams = array()) { @@ -178,4 +180,13 @@ class Application extends App { $mountProviderCollection->registerProvider($this->getContainer()->query('MountProvider')); $mountProviderCollection->registerProvider($this->getContainer()->query('ExternalMountProvider')); } + + public function register(): void { + $dispatcher = $this->getContainer()->getServer()->getEventDispatcher(); + $dispatcher->addListener('OCP\Share::postShare', function(GenericEvent $event) { + /** @var Listener $listener */ + $listener = $this->getContainer()->query(Listener::class); + $listener->shareNotification($event); + }); + } } diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 66b2383ea7..acb95a0a3d 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -946,6 +946,45 @@ class ShareAPIController extends OCSController { return new DataResponse($this->formatShare($share)); } + /** + * @NoAdminRequired + * + * @param string $id + * @return DataResponse + * @throws OCSNotFoundException + * @throws OCSException + * @throws OCSBadRequestException + */ + public function acceptShare(string $id): DataResponse { + try { + $share = $this->getShareById($id); + } catch (ShareNotFound $e) { + throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); + } + + if (!$this->canAccessShare($share, false)) { + throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); + } + + if ($share->getShareType() !== Share::SHARE_TYPE_USER || + $share->getSharedWith() !== $this->currentUser) { + throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); + } + + $share->setStatus(IShare::STATUS_ACCEPTED); + + try { + $this->shareManager->updateShare($share); + } catch (GenericShareException $e) { + $code = $e->getCode() === 0 ? 403 : $e->getCode(); + throw new OCSException($e->getHint(), $code); + } catch (\Exception $e) { + throw new OCSBadRequestException($e->getMessage(), $e); + } + + return new DataResponse(); + } + /** * Does the user have read permission on the share * diff --git a/apps/files_sharing/lib/Notification/Listener.php b/apps/files_sharing/lib/Notification/Listener.php new file mode 100644 index 0000000000..98c40f3167 --- /dev/null +++ b/apps/files_sharing/lib/Notification/Listener.php @@ -0,0 +1,72 @@ + + * + * @author Joas Schilling + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Files_Sharing\Notification; + +use OC\Share\Share; +use OCP\Notification\IManager; +use OCP\Notification\INotification; +use OCP\Share\IShare; +use Symfony\Component\EventDispatcher\GenericEvent; + +class Listener { + + /** @var IManager */ + protected $notificationManager; + + public function __construct( + IManager $notificationManager + ) { + $this->notificationManager = $notificationManager; + } + + /** + * @param GenericEvent $event + */ + public function shareNotification(GenericEvent $event): void { + /** @var IShare $share */ + $share = $event->getSubject(); + $notification = $this->instantiateNotification($share); + + if ($share->getShareType() === Share::SHARE_TYPE_USER) { + $notification->setSubject('incoming_user_share') + ->setUser($share->getSharedWith()); + $this->notificationManager->notify($notification); + } + } + + /** + * @param IShare $share + * @return INotification + */ + protected function instantiateNotification(IShare $share): INotification { + $notification = $this->notificationManager->createNotification(); + $notification + ->setApp('files_sharing') + ->setObject('share', $share->getFullId()) + ->setDateTime($share->getShareTime()); + + return $notification; + } +} diff --git a/apps/files_sharing/lib/Notification/Notifier.php b/apps/files_sharing/lib/Notification/Notifier.php index a9028ec9cf..6ae009895d 100644 --- a/apps/files_sharing/lib/Notification/Notifier.php +++ b/apps/files_sharing/lib/Notification/Notifier.php @@ -2,8 +2,10 @@ declare(strict_types=1); /** * @copyright Copyright (c) 2019, Roeland Jago Douma + * @copyright Copyright (c) 2019, Joas Schilling * * @author Roeland Jago Douma + * @author Joas Schilling * * @license GNU AGPL version 3 or any later version * @@ -25,43 +27,70 @@ declare(strict_types=1); namespace OCA\Files_Sharing\Notification; use OCP\Files\IRootFolder; +use OCP\IL10N; +use OCP\IURLGenerator; use OCP\L10N\IFactory; use OCP\Notification\AlreadyProcessedException; use OCP\Notification\INotification; use OCP\Notification\INotifier; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; +use OCP\Share\IShare; class Notifier implements INotifier { /** @var IFactory */ protected $l10nFactory; - /** @var IManager */ private $shareManager; - /** @var IRootFolder */ private $rootFolder; + /** @var IURLGenerator */ + protected $url; + public function __construct(IFactory $l10nFactory, IManager $shareManager, - IRootFolder $rootFolder) { + IRootFolder $rootFolder, + IURLGenerator $url) { $this->l10nFactory = $l10nFactory; $this->shareManager = $shareManager; $this->rootFolder = $rootFolder; + $this->url = $url; } + /** + * Identifier of the notifier, only use [a-z0-9_] + * + * @return string + * @since 17.0.0 + */ public function getID(): string { return 'files_sharing'; } + /** + * Human readable name describing the notifier + * + * @return string + * @since 17.0.0 + */ public function getName(): string { - return $this->l10nFactory->get('files_sharing')->t('Files sharing'); + return $this->l10nFactory->get('files_sharing')->t('File sharing'); } + /** + * @param INotification $notification + * @param string $languageCode The code of the language that should be used to prepare the notification + * @return INotification + * @throws \InvalidArgumentException When the notification was not prepared by a notifier + * @throws AlreadyProcessedException When the notification is not needed anymore and should be deleted + * @since 9.0.0 + */ public function prepare(INotification $notification, string $languageCode): INotification { if ($notification->getApp() !== 'files_sharing' || - $notification->getSubject() !== 'expiresTomorrow') { + ($notification->getSubject() !== 'expiresTomorrow' && + $notification->getObjectType() !== 'share')) { throw new \InvalidArgumentException('Unhandled app or subject'); } @@ -74,6 +103,15 @@ class Notifier implements INotifier { throw new AlreadyProcessedException(); } + if ($notification->getSubject() === 'expiresTomorrow') { + $notification = $this->parseShareExpiration($share, $notification, $l); + } else { + $notification = $this->parseShareInvitation($share, $notification, $l); + } + return $notification; + } + + protected function parseShareExpiration(IShare $share, INotification $notification, IL10N $l): INotification { $node = $share->getNode(); $userFolder = $this->rootFolder->getUserFolder($notification->getUser()); $path = $userFolder->getRelativePath($node->getPath()); @@ -95,4 +133,61 @@ class Notifier implements INotifier { return $notification; } + + protected function parseShareInvitation(IShare $share, INotification $notification, IL10N $l): INotification { + if ($share->getShareType() === IShare::TYPE_USER) { + if ($share->getSharedWith() !== $notification->getUser()) { + throw new AlreadyProcessedException(); + } + + if ($share->getStatus() !== IShare::STATUS_PENDING) { + throw new AlreadyProcessedException(); + } + } + + switch ($notification->getSubject()) { + case 'incoming_user_share': + $subject = $l->t('You received {share} as a share from {user}'); + $subjectParameters = [ + 'share' => [ + 'type' => 'highlight', + 'id' => $notification->getObjectId(), + 'name' => $share->getNode()->getName(), + ], + 'user' => [ + 'type' => 'user', + 'id' => $share->getShareOwner(), + 'name' => $share->getShareOwner(), + ], + ]; + + $placeholders = $replacements = []; + foreach ($subjectParameters as $placeholder => $parameter) { + $placeholders[] = '{' . $placeholder . '}'; + $replacements[] = $parameter['name']; + } + + $notification->setParsedSubject(str_replace($placeholders, $replacements, $subject)) + ->setRichSubject($subject, $subjectParameters) + ->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg'))); + + $acceptAction = $notification->createAction(); + $acceptAction->setParsedLabel($l->t('Accept')) + ->setLink($this->url->linkToOCSRouteAbsolute('files_sharing.ShareAPI.acceptShare', ['id' => $share->getId()]), 'POST') + ->setPrimary(true); + $notification->addParsedAction($acceptAction); + + $rejectAction = $notification->createAction(); + $rejectAction->setParsedLabel($l->t('Reject')) + ->setLink($this->url->linkToOCSRouteAbsolute('files_sharing.ShareAPI.deleteShare', ['id' => $share->getId()]), 'DELETE') + ->setPrimary(false); + $notification->addParsedAction($rejectAction); + + return $notification; + break; + + default: + throw new \InvalidArgumentException('Invalid subject'); + } + } } diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 61c6236415..66d28869db 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -253,6 +253,7 @@ class DefaultShareProvider implements IShareProvider { ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('accepted', $qb->createNamedParameter($share->getStatus())) ->execute(); } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { $qb = $this->dbConn->getQueryBuilder();