From ada65d3b0797614cd426796dc21292964b50f41f Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 26 May 2023 17:58:44 +0200 Subject: [PATCH] feat(API): Allow to block older clients Signed-off-by: Joas Schilling --- lib/Middleware/CanUseTalkMiddleware.php | 101 +++++++++++++++--- .../UnsupportedClientVersionException.php | 34 ++++++ 2 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 lib/Middleware/Exceptions/UnsupportedClientVersionException.php diff --git a/lib/Middleware/CanUseTalkMiddleware.php b/lib/Middleware/CanUseTalkMiddleware.php index f659cf95252..abe788621bd 100644 --- a/lib/Middleware/CanUseTalkMiddleware.php +++ b/lib/Middleware/CanUseTalkMiddleware.php @@ -29,6 +29,7 @@ use OCA\Talk\Exceptions\ForbiddenException; use OCA\Talk\Middleware\Attribute\RequireCallEnabled; use OCA\Talk\Middleware\Exceptions\CanNotUseTalkException; +use OCA\Talk\Middleware\Exceptions\UnsupportedClientVersionException; use OCA\Talk\Room; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -39,31 +40,61 @@ use OCP\AppFramework\OCSController; use OCP\IConfig; use OCP\IGroupManager; +use OCP\IRequest; use OCP\IUser; use OCP\IUserSession; class CanUseTalkMiddleware extends Middleware { - private IUserSession $userSession; - private IGroupManager $groupManager; - private Config $talkConfig; - private IConfig $serverConfig; + /** + * Talk Desktop user agent but with a regex match for the version + * @see IRequest::USER_AGENT_TALK_DESKTOP + */ + public const USER_AGENT_TALK_DESKTOP = '/^Mozilla\/5\.0 \((?!Android|iOS)[A-Za-z ]+\) Nextcloud-Talk v([^ ]*).*$/'; + public const TALK_DESKTOP_MIN_VERSION = '1.6.0'; + + /** + * Talk Android user agent but with a regex match for the version + * @see IRequest::USER_AGENT_TALK_ANDROID + */ + public const USER_AGENT_TALK_ANDROID = '/^Mozilla\/5\.0 \(Android\) Nextcloud\-Talk v([^ ]*).*$/'; + public const TALK_ANDROID_MIN_VERSION = '15.0.0'; + + /** + * Talk iOS user agent but with a regex match for the version + * @see IRequest::USER_AGENT_TALK_IOS + */ + public const USER_AGENT_TALK_IOS = '/^Mozilla\/5\.0 \(iOS\) Nextcloud\-Talk v([^ ]*).*$/'; + public const TALK_IOS_MIN_VERSION = '15.0.0'; + public function __construct( - IUserSession $userSession, - IGroupManager $groupManager, - Config $talkConfig, - IConfig $serverConfig, + protected IUserSession $userSession, + protected IGroupManager $groupManager, + protected Config $talkConfig, + protected IConfig $serverConfig, + protected IRequest $request, ) { - $this->userSession = $userSession; - $this->groupManager = $groupManager; - $this->talkConfig = $talkConfig; - $this->serverConfig = $serverConfig; } /** * @throws CanNotUseTalkException + * @throws UnsupportedClientVersionException */ public function beforeController(Controller $controller, string $methodName): void { + if ($this->request->isUserAgent([ + IRequest::USER_AGENT_TALK_DESKTOP, + IRequest::USER_AGENT_TALK_ANDROID, + IRequest::USER_AGENT_TALK_IOS, + ])) { + if ($this->request->isUserAgent([IRequest::USER_AGENT_TALK_DESKTOP])) { + $this->throwIfUnsupportedClientVersion('desktop', $this->request->getHeader('USER_AGENT')); + } elseif ($this->request->isUserAgent([IRequest::USER_AGENT_TALK_ANDROID])) { + $this->throwIfUnsupportedClientVersion('android', $this->request->getHeader('USER_AGENT')); + } elseif ($this->request->isUserAgent([IRequest::USER_AGENT_TALK_IOS])) { + $this->throwIfUnsupportedClientVersion('ios', $this->request->getHeader('USER_AGENT')); + } + } + $user = $this->userSession->getUser(); if ($user instanceof IUser && $this->talkConfig->isDisabledForUser($user)) { if ($methodName === 'getWelcomeMessage' @@ -93,6 +124,14 @@ public function beforeController(Controller $controller, string $methodName): vo * @return Response */ public function afterException($controller, $methodName, \Exception $exception): Response { + if ($exception instanceof UnsupportedClientVersionException) { + if ($controller instanceof OCSController) { + throw new OCSException($exception->getMessage(), Http::STATUS_UPGRADE_REQUIRED); + } + + return new RedirectToDefaultAppResponse(); + } + if ($exception instanceof CanNotUseTalkException || $exception instanceof ForbiddenException) { if ($controller instanceof OCSController) { @@ -104,4 +143,42 @@ public function afterException($controller, $methodName, \Exception $exception): throw $exception; } + + /** + * @param string $client + * @param string $userAgent + * @throws UnsupportedClientVersionException + */ + protected function throwIfUnsupportedClientVersion(string $client, string $userAgent): void { + $configMinVersion = $this->serverConfig->getAppValue('spreed', 'minimum.supported.' . $client . '.version'); + + if ($client === 'desktop') { + $versionRegex = self::USER_AGENT_TALK_DESKTOP; + $minVersion = self::TALK_DESKTOP_MIN_VERSION; + } elseif ($client === 'android'){ + $versionRegex = self::USER_AGENT_TALK_ANDROID; + $minVersion = self::TALK_ANDROID_MIN_VERSION; + } elseif ($client === 'ios') { + $versionRegex = self::USER_AGENT_TALK_IOS; + $minVersion = self::TALK_IOS_MIN_VERSION; + } else { + return; + } + + preg_match($versionRegex, $userAgent, $matches); + + if (isset($matches[1])) { + $clientVersion = $matches[1]; + + // API requirement and safety net + if (version_compare($clientVersion, $minVersion, '<')) { + throw new UnsupportedClientVersionException(); + } + + // Admin option to be more pushy + if ($configMinVersion && version_compare($clientVersion, $configMinVersion, '<')) { + throw new UnsupportedClientVersionException(); + } + } + } } diff --git a/lib/Middleware/Exceptions/UnsupportedClientVersionException.php b/lib/Middleware/Exceptions/UnsupportedClientVersionException.php new file mode 100644 index 00000000000..cb184d537f6 --- /dev/null +++ b/lib/Middleware/Exceptions/UnsupportedClientVersionException.php @@ -0,0 +1,34 @@ + + * + * @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\Talk\Middleware\Exceptions; + +use OCP\AppFramework\Http; + +class UnsupportedClientVersionException extends \Exception { + public function __construct() { + parent::__construct('Unsupported client version', Http::STATUS_UPGRADE_REQUIRED); + } +}