diff options
author | Joas Schilling <coding@schilljs.com> | 2019-04-10 17:01:43 +0300 |
---|---|---|
committer | Joas Schilling <coding@schilljs.com> | 2019-07-18 10:38:06 +0300 |
commit | 7efe105bdb1ef1e8e827048acd059e5d3a098e8e (patch) | |
tree | 3460498c59bbb5c5b981ce51ff38c2890703760d /lib | |
parent | 8c378b4e6adfa4b68288910b13c2c54e801a83c2 (diff) |
Send a push message when a notification was deleted
Signed-off-by: Joas Schilling <coding@schilljs.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/App.php | 12 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 29 | ||||
-rw-r--r-- | lib/Capabilities.php | 1 | ||||
-rw-r--r-- | lib/Controller/EndpointController.php | 21 | ||||
-rw-r--r-- | lib/Handler.php | 27 | ||||
-rw-r--r-- | lib/Notifier/AdminNotifications.php | 24 | ||||
-rw-r--r-- | lib/Push.php | 113 |
7 files changed, 197 insertions, 30 deletions
diff --git a/lib/App.php b/lib/App.php index 6be3dde..192e100 100644 --- a/lib/App.php +++ b/lib/App.php @@ -42,7 +42,7 @@ class App implements IApp { * @throws \InvalidArgumentException When the notification is not valid * @since 8.2.0 */ - public function notify(INotification $notification) { + public function notify(INotification $notification): void { $notificationId = $this->handler->add($notification); try { @@ -66,7 +66,13 @@ class App implements IApp { * @param INotification $notification * @since 8.2.0 */ - public function markProcessed(INotification $notification) { - $this->handler->delete($notification); + public function markProcessed(INotification $notification): void { + $deleted = $this->handler->delete($notification); + + foreach ($deleted as $user => $notifications) { + foreach ($notifications as $notificationId) { + $this->push->pushDeleteToDevice($user, $notificationId); + } + } } } diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 0110423..e91a02a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -41,31 +41,26 @@ class Application extends \OCP\AppFramework\App { }); } - public function register() { + public function register(): void { $this->registerNotificationApp(); $this->registerAdminNotifications(); $this->registerUserInterface(); } - protected function registerNotificationApp() { - $container = $this->getContainer(); - $container->getServer()->getNotificationManager()->registerApp(function() use($container) { - return $container->query(App::class); - }); + protected function registerNotificationApp(): void { + $this->getContainer() + ->getServer() + ->getNotificationManager() + ->registerApp(App::class); } - protected function registerAdminNotifications() { - $this->getContainer()->getServer()->getNotificationManager()->registerNotifier(function() { - return $this->getContainer()->query(AdminNotifications::class); - }, function() { - $l = $this->getContainer()->getServer()->getL10NFactory()->get('notifications'); - return [ - 'id' => 'admin_notifications', - 'name' => $l->t('Admin notifications'), - ]; - }); + protected function registerAdminNotifications(): void { + $this->getContainer() + ->getServer() + ->getNotificationManager() + ->registerNotifier(AdminNotifications::class); } - protected function registerUserInterface() { + protected function registerUserInterface(): void { // Only display the app on index.php except for public shares $server = $this->getContainer()->getServer(); $request = $server->getRequest(); diff --git a/lib/Capabilities.php b/lib/Capabilities.php index be654c1..6e19e90 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -49,6 +49,7 @@ class Capabilities implements ICapability { 'push' => [ 'devices', 'object-data', + 'delete', ], 'admin-notifications' => [ 'ocs', diff --git a/lib/Controller/EndpointController.php b/lib/Controller/EndpointController.php index 8eff06c..4f60c87 100644 --- a/lib/Controller/EndpointController.php +++ b/lib/Controller/EndpointController.php @@ -23,6 +23,7 @@ namespace OCA\Notifications\Controller; use OCA\Notifications\Exceptions\NotificationNotFoundException; use OCA\Notifications\Handler; +use OCA\Notifications\Push; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCSController; @@ -37,15 +38,15 @@ use OCP\Notification\INotification; class EndpointController extends OCSController { /** @var Handler */ private $handler; - /** @var IManager */ private $manager; - + /** @var IConfig */ + private $config; /** @var IUserSession */ private $session; + /** @var Push */ + private $push; - /** @var IConfig */ - private $config; /** * @param string $appName @@ -54,14 +55,22 @@ class EndpointController extends OCSController { * @param IManager $manager * @param IConfig $config * @param IUserSession $session + * @param Push $push */ - public function __construct($appName, IRequest $request, Handler $handler, IManager $manager, IConfig $config, IUserSession $session) { + public function __construct(string $appName, + IRequest $request, + Handler $handler, + IManager $manager, + IConfig $config, + IUserSession $session, + Push $push) { parent::__construct($appName, $request); $this->handler = $handler; $this->manager = $manager; $this->config = $config; $this->session = $session; + $this->push = $push; } /** @@ -154,6 +163,7 @@ class EndpointController extends OCSController { } $this->handler->deleteById($id, $this->getCurrentUser()); + $this->push->pushDeleteToDevice($this->getCurrentUser(), $id); return new DataResponse(); } @@ -164,6 +174,7 @@ class EndpointController extends OCSController { */ public function deleteAllNotifications(): DataResponse { $this->handler->deleteByUser($this->getCurrentUser()); + $this->push->pushDeleteToDevice($this->getCurrentUser(), 0); return new DataResponse(); } diff --git a/lib/Handler.php b/lib/Handler.php index 863425b..d8c2451 100644 --- a/lib/Handler.php +++ b/lib/Handler.php @@ -84,12 +84,33 @@ class Handler { * Delete the notifications matching the given Notification * * @param INotification $notification + * @return array A Map with all deleted notifications [user => [notifications]] */ - public function delete(INotification $notification) { + public function delete(INotification $notification): array { $sql = $this->connection->getQueryBuilder(); - $sql->delete('notifications'); + $sql->select('notification_id', 'user') + ->from('notifications'); + $this->sqlWhere($sql, $notification); - $sql->execute(); + $statement = $sql->execute(); + + $deleted = []; + while ($row = $statement->fetch()) { + if (!isset($deleted[$row['user']])) { + $deleted[$row['user']] = []; + } + + $deleted[$row['user']][] = (int) $row['notification_id']; + } + $statement->closeCursor(); + + foreach ($deleted as $user => $notifications) { + foreach ($notifications as $notificationId) { + $this->deleteById($notificationId, $user); + } + } + + return $deleted; } /** diff --git a/lib/Notifier/AdminNotifications.php b/lib/Notifier/AdminNotifications.php index 1e63672..1de2bc3 100644 --- a/lib/Notifier/AdminNotifications.php +++ b/lib/Notifier/AdminNotifications.php @@ -25,6 +25,7 @@ namespace OCA\Notifications\Notifier; use OCP\IURLGenerator; use OCP\L10N\IFactory; +use OCP\Notification\AlreadyProcessedException; use OCP\Notification\INotification; use OCP\Notification\INotifier; @@ -46,12 +47,33 @@ class AdminNotifications implements INotifier { } /** + * Identifier of the notifier, only use [a-z0-9_] + * + * @return string + * @since 17.0.0 + */ + public function getID(): string { + return 'admin_notifications'; + } + + /** + * Human readable name describing the notifier + * + * @return string + * @since 17.0.0 + */ + public function getName(): string { + return $this->l10nFactory->get('notifications')->t('Admin notifications'); + } + + /** * @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 */ - public function prepare(INotification $notification, $languageCode): INotification { + public function prepare(INotification $notification, string $languageCode): INotification { if ($notification->getApp() !== 'admin_notifications') { throw new \InvalidArgumentException('Unknown app'); } diff --git a/lib/Push.php b/lib/Push.php index a9cca15..5b8c087 100644 --- a/lib/Push.php +++ b/lib/Push.php @@ -66,7 +66,7 @@ class Push { $this->log = $log; } - public function pushToDevice(int $id, INotification $notification) { + public function pushToDevice(int $id, INotification $notification): void { $user = $this->userManager->get($notification->getUser()); if (!($user instanceof IUser)) { return; @@ -170,6 +170,76 @@ class Push { } } + public function pushDeleteToDevice(string $userId, int $notificationId): void { + $user = $this->userManager->get($userId); + if (!($user instanceof IUser)) { + return; + } + + $devices = $this->getDevicesForUser($userId); + if (empty($devices)) { + return; + } + + $userKey = $this->keyManager->getKey($user); + $pushNotifications = []; + foreach ($devices as $device) { + try { + $payload = json_encode($this->encryptAndSignDelete($userKey, $device, $notificationId)); + + $proxyServer = rtrim($device['proxyserver'], '/'); + if (!isset($pushNotifications[$proxyServer])) { + $pushNotifications[$proxyServer] = []; + } + $pushNotifications[$proxyServer][] = $payload; + } catch (InvalidTokenException $e) { + // Token does not exist anymore, should drop the push device entry + $this->deletePushToken($device['token']); + } catch (\InvalidArgumentException $e) { + // Failed to encrypt message for device: public key is invalid + $this->deletePushToken($device['token']); + } + } + + if (empty($pushNotifications)) { + return; + } + + $client = $this->clientService->newClient(); + foreach ($pushNotifications as $proxyServer => $notifications) { + try { + $response = $client->post($proxyServer . '/notifications', [ + 'body' => [ + 'notifications' => $notifications, + ], + ]); + } catch (\Exception $e) { + $this->log->logException($e, [ + 'app' => 'notifications', + 'level' => $e->getCode() === Http::STATUS_BAD_REQUEST ? ILogger::INFO : ILogger::WARN, + ]); + continue; + } + + $status = $response->getStatusCode(); + if ($status !== Http::STATUS_OK && $status !== Http::STATUS_SERVICE_UNAVAILABLE) { + $body = $response->getBody(); + $this->log->error('Could not send notification to push server [{url}]: {error}',[ + 'error' => \is_string($body) ? $body : 'no reason given', + 'url' => $proxyServer, + 'app' => 'notifications', + ]); + } else if ($status === Http::STATUS_SERVICE_UNAVAILABLE && $this->config->getSystemValue('debug', false)) { + $body = $response->getBody(); + $this->log->debug('Could not send notification to push server [{url}]: {error}',[ + 'error' => \is_string($body) ? $body : 'no reason given', + 'url' => $proxyServer, + 'app' => 'notifications', + ]); + } + } + } + /** * @param Key $userKey * @param array $device @@ -224,6 +294,47 @@ class Push { } /** + * @param Key $userKey + * @param array $device + * @param int $id + * @return array + * @throws InvalidTokenException + * @throws \InvalidArgumentException + */ + protected function encryptAndSignDelete(Key $userKey, array $device, int $id): array { + // Check if the token is still valid... + $this->tokenProvider->getTokenById($device['token']); + + if ($id === 0) { + $data = [ + 'delete-all' => true, + ]; + } else { + $data = [ + 'nid' => $id, + 'delete' => true, + ]; + } + + if (!openssl_public_encrypt(json_encode($data), $encryptedSubject, $device['devicepublickey'], OPENSSL_PKCS1_PADDING)) { + $this->log->error(openssl_error_string(), ['app' => 'notifications']); + throw new \InvalidArgumentException('Failed to encrypt message for device'); + } + + openssl_sign($encryptedSubject, $signature, $userKey->getPrivate(), OPENSSL_ALGO_SHA512); + $base64EncryptedSubject = base64_encode($encryptedSubject); + $base64Signature = base64_encode($signature); + + return [ + 'deviceIdentifier' => $device['deviceidentifier'], + 'pushTokenHash' => $device['pushtokenhash'], + 'subject' => $base64EncryptedSubject, + 'signature' => $base64Signature, + 'priority' => 'normal', + ]; + } + + /** * @param string $uid * @return array[] */ |