From 1f964b5d7ad0cfa6abaac75c6ec81be4e3ca6215 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Wed, 12 Feb 2025 17:21:43 +0100 Subject: [PATCH] fix(push): Check last activity and last check for age Signed-off-by: Joas Schilling --- lib/Push.php | 37 +++++++++++++++++++++++++++++++++++-- tests/Unit/PushTest.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/Push.php b/lib/Push.php index 44c9b530b..48e6b1e40 100644 --- a/lib/Push.php +++ b/lib/Push.php @@ -18,6 +18,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; @@ -537,10 +538,23 @@ protected function validateToken(int $tokenId, int $maxAge): bool { try { // Check if the token is still valid... $token = $this->tokenProvider->getTokenById($tokenId); - $this->cache->set('t' . $tokenId, $token->getLastCheck(), 600); + $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 anymore, should drop the push device entry + // 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); @@ -557,6 +571,25 @@ protected function validateToken(int $tokenId, int $maxAge): bool { 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 null; + } + /** * @param Key $userKey * @param array $device diff --git a/tests/Unit/PushTest.php b/tests/Unit/PushTest.php index 6df9870fd..1f7758b0f 100644 --- a/tests/Unit/PushTest.php +++ b/tests/Unit/PushTest.php @@ -12,11 +12,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; @@ -783,4 +785,31 @@ public function testPushToDeviceTalkNotification(array $deviceTypes, bool $isTal $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])); + } }