diff options
author | Joas Schilling <coding@schilljs.com> | 2017-11-22 14:55:07 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-22 14:55:07 +0300 |
commit | 0cc01a3e149395c69e4a4f066ec53c3478e7840e (patch) | |
tree | a1e0d3b67731b37b652291affcfa20c22a3d9109 | |
parent | c77d4850075ebe98aa9e8d11a8f4f288a374b486 (diff) | |
parent | deba129d29d636da36ad204d3324cf33b44bb38d (diff) |
Merge pull request #99 from nextcloud/talk-special-handling
Talk special handling
-rwxr-xr-x | appinfo/database.xml | 6 | ||||
-rw-r--r-- | appinfo/info.xml | 2 | ||||
-rw-r--r-- | lib/Controller/PushController.php | 30 | ||||
-rw-r--r-- | lib/Push.php | 32 | ||||
-rw-r--r-- | tests/Unit/PushTest.php | 130 |
5 files changed, 191 insertions, 9 deletions
diff --git a/appinfo/database.xml b/appinfo/database.xml index 07febf9..0eca938 100755 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -166,6 +166,12 @@ <notnull>true</notnull> <length>256</length> </field> + <field> + <name>apptype</name> + <type>text</type> + <notnull>true</notnull> + <length>32</length> + </field> <index> <name>oc_notifpushtoken</name> diff --git a/appinfo/info.xml b/appinfo/info.xml index 25953e9..aa07848 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -15,7 +15,7 @@ <licence>AGPL</licence> <author>Joas Schilling</author> - <version>2.1.0</version> + <version>2.1.1</version> <types> <logging/> diff --git a/lib/Controller/PushController.php b/lib/Controller/PushController.php index aef6eb3..36d0237 100644 --- a/lib/Controller/PushController.php +++ b/lib/Controller/PushController.php @@ -117,7 +117,20 @@ class PushController extends OCSController { openssl_sign($deviceIdentifier, $signature, $key->getPrivate(), OPENSSL_ALGO_SHA512); $deviceIdentifier = base64_encode(hash('sha512', $deviceIdentifier, true)); - $created = $this->savePushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer); + $appType = 'unknown'; + if ($this->request->isUserAgent([ + IRequest::USER_AGENT_TALK_ANDROID, + IRequest::USER_AGENT_TALK_IOS, + ])) { + $appType = 'talk'; + } else if ($this->request->isUserAgent([ + IRequest::USER_AGENT_CLIENT_ANDROID, + IRequest::USER_AGENT_CLIENT_IOS, + ])) { + $appType = 'nextcloud'; + } + + $created = $this->savePushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer, $appType); return new DataResponse([ 'publicKey' => $key->getPublic(), @@ -158,9 +171,10 @@ class PushController extends OCSController { * @param string $devicePublicKey * @param string $pushTokenHash * @param string $proxyServer + * @param string $appType * @return bool If the hash was new to the database */ - protected function savePushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer) { + protected function savePushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer, $appType) { $query = $this->db->getQueryBuilder(); $query->select('*') ->from('notifications_pushtokens') @@ -171,10 +185,10 @@ class PushController extends OCSController { $result->closeCursor(); if (!$row) { - return $this->insertPushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer); + return $this->insertPushToken($user, $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer, $appType); } - return $this->updatePushToken($user, $token, $devicePublicKey, $pushTokenHash, $proxyServer); + return $this->updatePushToken($user, $token, $devicePublicKey, $pushTokenHash, $proxyServer, $appType); } /** @@ -184,9 +198,10 @@ class PushController extends OCSController { * @param string $devicePublicKey * @param string $pushTokenHash * @param string $proxyServer + * @param string $appType * @return bool If the entry was created */ - protected function insertPushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer) { + protected function insertPushToken(IUser $user, IToken $token, $deviceIdentifier, $devicePublicKey, $pushTokenHash, $proxyServer, $appType) { $devicePublicKeyHash = hash('sha512', $devicePublicKey); $query = $this->db->getQueryBuilder(); @@ -199,6 +214,7 @@ class PushController extends OCSController { 'devicepublickeyhash' => $query->createNamedParameter($devicePublicKeyHash), 'pushtokenhash' => $query->createNamedParameter($pushTokenHash), 'proxyserver' => $query->createNamedParameter($proxyServer), + 'apptype' => $query->createNamedParameter($appType), ]); return $query->execute() > 0; } @@ -209,9 +225,10 @@ class PushController extends OCSController { * @param string $devicePublicKey * @param string $pushTokenHash * @param string $proxyServer + * @param string $appType * @return bool If the entry was updated */ - protected function updatePushToken(IUser $user, IToken $token, $devicePublicKey, $pushTokenHash, $proxyServer) { + protected function updatePushToken(IUser $user, IToken $token, $devicePublicKey, $pushTokenHash, $proxyServer, $appType) { $devicePublicKeyHash = hash('sha512', $devicePublicKey); $query = $this->db->getQueryBuilder(); @@ -220,6 +237,7 @@ class PushController extends OCSController { ->set('devicepublickeyhash', $query->createNamedParameter($devicePublicKeyHash)) ->set('pushtokenhash', $query->createNamedParameter($pushTokenHash)) ->set('proxyserver', $query->createNamedParameter($proxyServer)) + ->set('apptype', $query->createNamedParameter($appType)) ->where($query->expr()->eq('uid', $query->createNamedParameter($user->getUID()))) ->andWhere($query->expr()->eq('token', $query->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT))); diff --git a/lib/Push.php b/lib/Push.php index 30b7763..f9e1b2b 100644 --- a/lib/Push.php +++ b/lib/Push.php @@ -89,10 +89,32 @@ class Push { $userKey = $this->keyManager->getKey($user); + $isTalkNotification = in_array($notification->getApp(), ['spreed', 'talk'], true) + && in_array($notification->getSubject(), ['invitation', 'call', 'mention'], true); + $talkApps = array_filter($devices, function($device) { + return $device['apptype'] === 'talk'; + }); + $hasTalkApps = !empty($talkApps); + $pushNotifications = []; foreach ($devices as $device) { + if (!$isTalkNotification && $device['apptype'] === 'talk') { + // The iOS app can not kill notifications, + // therefor we should only send relevant notifications to the Talk + // app, so it does not pollute the notifications bar with useless + // notifications, especially when the Sync client app is also installed. + continue; + } + if ($isTalkNotification && $hasTalkApps && $device['apptype'] !== 'talk') { + // Similar to the previous case, we also don't send Talk notifications + // to the Sync client app, when there is a Talk app installed. We only + // do this, when you don't have a Talk app on your device, so you still + // get the push notification. + continue; + } + try { - $payload = json_encode($this->encryptAndSign($userKey, $device, $notification)); + $payload = json_encode($this->encryptAndSign($userKey, $device, $notification, $isTalkNotification)); $proxyServer = rtrim($device['proxyserver'], '/'); if (!isset($pushNotifications[$proxyServer])) { @@ -150,11 +172,12 @@ class Push { * @param Key $userKey * @param array $device * @param INotification $notification + * @param bool $isTalkNotification * @return array * @throws InvalidTokenException * @throws \InvalidArgumentException */ - protected function encryptAndSign(Key $userKey, array $device, INotification $notification) { + protected function encryptAndSign(Key $userKey, array $device, INotification $notification, $isTalkNotification) { // Check if the token is still valid... $this->tokenProvider->getTokenById($device['token']); @@ -163,6 +186,11 @@ class Push { 'subject' => $notification->getParsedSubject(), ]; + if ($isTalkNotification) { + $data['type'] = $notification->getObjectType(); + $data['id'] = $notification->getObjectId(); + } + 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'); diff --git a/tests/Unit/PushTest.php b/tests/Unit/PushTest.php index 6399b37..9b0b7e0 100644 --- a/tests/Unit/PushTest.php +++ b/tests/Unit/PushTest.php @@ -224,6 +224,7 @@ class PushTest extends TestCase { ->willReturn([[ 'proxyserver' => 'proxyserver1', 'token' => 23, + 'apptype' => 'other', ]]); $this->config->expects($this->once()) @@ -280,6 +281,7 @@ class PushTest extends TestCase { ->willReturn([[ 'proxyserver' => 'proxyserver1', 'token' => 23, + 'apptype' => 'other', ]]); $this->config->expects($this->once()) @@ -346,22 +348,27 @@ class PushTest extends TestCase { [ 'proxyserver' => 'proxyserver1', 'token' => 16, + 'apptype' => 'other', ], [ 'proxyserver' => 'proxyserver1/', 'token' => 23, + 'apptype' => 'other', ], [ 'proxyserver' => 'badrequest', 'token' => 42, + 'apptype' => 'other', ], [ 'proxyserver' => 'unavailable', 'token' => 48, + 'apptype' => 'other', ], [ 'proxyserver' => 'ok', 'token' => 64, + 'apptype' => 'other', ], ]); @@ -484,4 +491,127 @@ class PushTest extends TestCase { $push->pushToDevice($notification); } + + public function dataPushToDeviceTalkNotification() { + return [ + [['nextcloud'], false, 0], + [['nextcloud'], true, 0], + [['nextcloud', 'talk'], false, 0], + [['nextcloud', 'talk'], true, 1], + [['talk', 'nextcloud'], false, 1], + [['talk', 'nextcloud'], true, 0], + [['talk'], false, null], + [['talk'], true, 0], + ]; + } + + /** + * @dataProvider dataPushToDeviceTalkNotification + * @param string[] $deviceTypes + * @param bool $isTalkNotification + * @param int $pushedDevice + */ + public function testPushToDeviceTalkNotification(array $deviceTypes, $isTalkNotification, $pushedDevice) { + $push = $this->getPush(['getDevicesForUser', 'encryptAndSign', 'deletePushToken']); + + /** @var INotification|\PHPUnit_Framework_MockObject_MockObject $notification */ + $notification = $this->createMock(INotification::class); + $notification->expects($this->exactly(3)) + ->method('getUser') + ->willReturn('valid'); + + if ($isTalkNotification) { + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('spreed'); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn('call'); + } else { + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('notifications'); + $notification->expects($this->never()) + ->method('getSubject'); + } + + /** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */ + $user = $this->createMock(IUser::class); + + $this->userManager->expects($this->once()) + ->method('get') + ->with('valid') + ->willReturn($user); + + $devices = []; + foreach ($deviceTypes as $deviceType) { + $devices[] = [ + 'proxyserver' => 'proxyserver', + 'token' => strlen($deviceType), + 'apptype' => $deviceType, + ]; + } + $push->expects($this->once()) + ->method('getDevicesForUser') + ->willReturn($devices); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('valid', 'core', 'lang', 'en') + ->willReturn('ru'); + + $this->notificationManager->expects($this->once()) + ->method('prepare') + ->with($notification, 'ru') + ->willReturnArgument(0); + + /** @var Key|\PHPUnit_Framework_MockObject_MockObject $key */ + $key = $this->createMock(Key::class); + + $this->keyManager->expects($this->once()) + ->method('getKey') + ->with($user) + ->willReturn($key); + + if ($pushedDevice === null) { + $push->expects($this->never()) + ->method('encryptAndSign'); + + $this->clientService->expects($this->never()) + ->method('newClient'); + } else { + $push->expects($this->exactly(1)) + ->method('encryptAndSign') + ->with($this->anything(), $devices[$pushedDevice], $this->anything(), $isTalkNotification) + ->willReturn(['Payload']); + + /** @var IClient|\PHPUnit_Framework_MockObject_MockObject $client */ + $client = $this->createMock(IClient::class); + + $this->clientService->expects($this->once()) + ->method('newClient') + ->willReturn($client); + + /** @var IResponse|\PHPUnit_Framework_MockObject_MockObject $response1 */ + $response = $this->createMock(IResponse::class); + $response->expects($this->once()) + ->method('getStatusCode') + ->willReturn(Http::STATUS_BAD_REQUEST); + $response->expects($this->once()) + ->method('getBody') + ->willReturn(null); + $client->expects($this->once()) + ->method('post') + ->with('proxyserver/notifications', [ + 'body' => [ + 'notifications' => ['["Payload"]'], + ], + ]) + ->willReturn($response); + } + + $push->pushToDevice($notification); + } + + } |