diff --git a/lib/Push.php b/lib/Push.php index e4d3cb2d4..77e45d5ea 100644 --- a/lib/Push.php +++ b/lib/Push.php @@ -35,6 +35,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Authentication\Exceptions\InvalidTokenException; +use OCP\Authentication\Token\IToken; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Http\Client\IClientService; use OCP\ICache; @@ -593,27 +594,61 @@ protected function sendNotificationsToProxies(): void { protected function validateToken(int $tokenId, int $maxAge): bool { $age = $this->cache->get('t' . $tokenId); - if ($age !== null) { - return $age > $maxAge; + + if ($age === null) { + try { + // Check if the token is still valid... + $token = $this->tokenProvider->getTokenById($tokenId); + $type = $this->callSafelyForToken($token, 'getType'); + if ($type === IToken::WIPE_TOKEN) { + // Token does not exist any more, should drop the push device entry + $this->printInfo('Device token is marked for remote wipe'); + $this->deletePushToken($tokenId); + $this->cache->set('t' . $tokenId, 0, 600); + return false; + } + + $age = $token->getLastCheck(); + $lastActivity = $this->callSafelyForToken($token, 'getLastActivity'); + if ($lastActivity) { + $age = max($age, $lastActivity); + } + $this->cache->set('t' . $tokenId, $age, 600); + } catch (InvalidTokenException) { + // Token does not exist any more, should drop the push device entry + $this->printInfo('InvalidTokenException is thrown'); + $this->deletePushToken($tokenId); + $this->cache->set('t' . $tokenId, 0, 600); + return false; + } } - try { - // Check if the token is still valid... - $token = $this->tokenProvider->getTokenById($tokenId); - $this->cache->set('t' . $tokenId, $token->getLastCheck(), 600); - if ($token->getLastCheck() > $maxAge) { - $this->printInfo('Device token is valid'); - } else { - $this->printInfo('Device token "last checked" is older than 60 days: ' . $token->getLastCheck()); + if ($age > $maxAge) { + $this->printInfo('Device token is valid'); + return true; + } + + $this->printInfo('Device token "last checked" is older than 60 days: ' . $age); + return false; + } + + /** + * The functions are not part of public API so we are a bit more careful + * @param IToken $token + * @param 'getLastActivity'|'getType' $method + * @return int|null + */ + protected function callSafelyForToken(IToken $token, string $method): ?int { + if (method_exists($token, $method) || method_exists($token, '__call')) { + try { + $result = $token->$method(); + if (is_int($result)) { + return $result; + } + } catch (\BadFunctionCallException) { } - return $token->getLastCheck() > $maxAge; - } catch (InvalidTokenException $e) { - // Token does not exist anymore, should drop the push device entry - $this->printInfo('InvalidTokenException is thrown'); - $this->deletePushToken($tokenId); - $this->cache->set('t' . $tokenId, 0, 600); - return false; } + return null; } /** diff --git a/tests/Unit/PushTest.php b/tests/Unit/PushTest.php index 91ece39a8..237acad6b 100644 --- a/tests/Unit/PushTest.php +++ b/tests/Unit/PushTest.php @@ -26,11 +26,13 @@ use GuzzleHttp\Exception\ServerException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\IProvider; +use OC\Authentication\Token\PublicKeyToken; use OC\Security\IdentityProof\Key; use OC\Security\IdentityProof\Manager; use OCA\Notifications\Push; use OCP\AppFramework\Http; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Token\IToken as OCPIToken; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; @@ -847,4 +849,31 @@ public function testPushToDeviceTalkNotification(array $deviceTypes, $isTalkNoti $push->pushToDevice(200718, $notification); } + + public static function dataValidateToken(): array { + return [ + [1239999999, 1230000000, OCPIToken::WIPE_TOKEN, false], + [1230000000, 1239999999, OCPIToken::WIPE_TOKEN, false], + [1230000000, 1239999999, OCPIToken::PERMANENT_TOKEN, true], + [1239999999, 1230000000, OCPIToken::PERMANENT_TOKEN, true], + [1230000000, 1230000000, OCPIToken::PERMANENT_TOKEN, false], + ]; + } + + /** + * @dataProvider dataValidateToken + */ + public function testValidateToken(int $lastCheck, int $lastActivity, int $type, bool $expected): void { + $token = PublicKeyToken::fromParams([ + 'lastCheck' => $lastCheck, + 'lastActivity' => $lastActivity, + 'type' => $type, + ]); + + $this->tokenProvider->method('getTokenById') + ->willReturn($token); + + $push = $this->getPush(); + $this->assertSame($expected, self::invokePrivate($push, 'validateToken', [42, 1234567890])); + } }