diff options
author | Côme Chilliet <91878298+come-nc@users.noreply.github.com> | 2023-03-15 11:09:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-15 11:09:51 +0100 |
commit | 8568c11d24736d4df1b9781c4121cf7165f40f65 (patch) | |
tree | aed92ac29df87aa6f5232cb71b9ae1d3be94ab40 | |
parent | 325c3100c7b1b6a5cbde4e0b2675f237735a7599 (diff) | |
parent | e97540b9c61e736e9d541dffedd6064680418bc3 (diff) | |
download | nextcloud-server-8568c11d24736d4df1b9781c4121cf7165f40f65.tar.gz nextcloud-server-8568c11d24736d4df1b9781c4121cf7165f40f65.zip |
Merge pull request #36033 from nextcloud/invalidateTokensWhenDeletingOAuthClientMaster
[master] invalidate existing tokens when deleting an oauth client
-rw-r--r-- | apps/oauth2/lib/Controller/SettingsController.php | 21 | ||||
-rw-r--r-- | apps/oauth2/tests/Controller/SettingsControllerTest.php | 58 | ||||
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | lib/private/Authentication/Token/Manager.php | 12 | ||||
-rw-r--r-- | lib/private/Server.php | 2 | ||||
-rw-r--r-- | lib/public/Authentication/Token/IProvider.php | 41 | ||||
-rw-r--r-- | tests/lib/Authentication/Token/ManagerTest.php | 44 |
8 files changed, 172 insertions, 8 deletions
diff --git a/apps/oauth2/lib/Controller/SettingsController.php b/apps/oauth2/lib/Controller/SettingsController.php index 046e6d77041..c24308140ec 100644 --- a/apps/oauth2/lib/Controller/SettingsController.php +++ b/apps/oauth2/lib/Controller/SettingsController.php @@ -30,6 +30,7 @@ declare(strict_types=1); */ namespace OCA\OAuth2\Controller; +use OCP\Authentication\Token\IProvider as IAuthTokenProvider; use OCA\OAuth2\Db\AccessTokenMapper; use OCA\OAuth2\Db\Client; use OCA\OAuth2\Db\ClientMapper; @@ -38,6 +39,8 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\IL10N; use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; use OCP\Security\ISecureRandom; class SettingsController extends Controller { @@ -49,7 +52,12 @@ class SettingsController extends Controller { private $accessTokenMapper; /** @var IL10N */ private $l; - + /** @var IAuthTokenProvider */ + private $tokenProvider; + /** + * @var IUserManager + */ + private $userManager; public const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; public function __construct(string $appName, @@ -57,13 +65,17 @@ class SettingsController extends Controller { ClientMapper $clientMapper, ISecureRandom $secureRandom, AccessTokenMapper $accessTokenMapper, - IL10N $l + IL10N $l, + IAuthTokenProvider $tokenProvider, + IUserManager $userManager ) { parent::__construct($appName, $request); $this->secureRandom = $secureRandom; $this->clientMapper = $clientMapper; $this->accessTokenMapper = $accessTokenMapper; $this->l = $l; + $this->tokenProvider = $tokenProvider; + $this->userManager = $userManager; } public function addClient(string $name, @@ -92,6 +104,11 @@ class SettingsController extends Controller { public function deleteClient(int $id): JSONResponse { $client = $this->clientMapper->getByUid($id); + + $this->userManager->callForAllUsers(function (IUser $user) use ($client) { + $this->tokenProvider->invalidateTokensOfUser($user->getUID(), $client->getName()); + }); + $this->accessTokenMapper->deleteByClientId($id); $this->clientMapper->delete($client); return new JSONResponse([]); diff --git a/apps/oauth2/tests/Controller/SettingsControllerTest.php b/apps/oauth2/tests/Controller/SettingsControllerTest.php index 216655190ae..e79d7cbe34e 100644 --- a/apps/oauth2/tests/Controller/SettingsControllerTest.php +++ b/apps/oauth2/tests/Controller/SettingsControllerTest.php @@ -26,6 +26,8 @@ */ namespace OCA\OAuth2\Tests\Controller; +use OC\Authentication\Token\IToken; +use OCP\Authentication\Token\IProvider as IAuthTokenProvider; use OCA\OAuth2\Controller\SettingsController; use OCA\OAuth2\Db\AccessTokenMapper; use OCA\OAuth2\Db\Client; @@ -34,9 +36,14 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; use OCP\IL10N; use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; use OCP\Security\ISecureRandom; use Test\TestCase; +/** + * @group DB + */ class SettingsControllerTest extends TestCase { /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ private $request; @@ -46,8 +53,14 @@ class SettingsControllerTest extends TestCase { private $secureRandom; /** @var AccessTokenMapper|\PHPUnit\Framework\MockObject\MockObject */ private $accessTokenMapper; + /** @var IAuthTokenProvider|\PHPUnit\Framework\MockObject\MockObject */ + private $authTokenProvider; + /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ + private $userManager; /** @var SettingsController */ private $settingsController; + /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + private $l; protected function setUp(): void { parent::setUp(); @@ -56,18 +69,22 @@ class SettingsControllerTest extends TestCase { $this->clientMapper = $this->createMock(ClientMapper::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->accessTokenMapper = $this->createMock(AccessTokenMapper::class); - $l = $this->createMock(IL10N::class); - $l->method('t') + $this->authTokenProvider = $this->createMock(IAuthTokenProvider::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->l = $this->createMock(IL10N::class); + $this->l->method('t') ->willReturnArgument(0); - $this->settingsController = new SettingsController( 'oauth2', $this->request, $this->clientMapper, $this->secureRandom, $this->accessTokenMapper, - $l + $this->l, + $this->authTokenProvider, + $this->userManager ); + } public function testAddClient() { @@ -113,6 +130,23 @@ class SettingsControllerTest extends TestCase { } public function testDeleteClient() { + + $userManager = \OC::$server->getUserManager(); + // count other users in the db before adding our own + $count = 0; + $function = function (IUser $user) use (&$count) { + $count++; + }; + $userManager->callForAllUsers($function); + $user1 = $userManager->createUser('test101', 'test101'); + $tokenProviderMock = $this->getMockBuilder(IAuthTokenProvider::class)->getMock(); + + // expect one call per user and ensure the correct client name + $tokenProviderMock + ->expects($this->exactly($count + 1)) + ->method('invalidateTokensOfUser') + ->with($this->isType('string'), 'My Client Name'); + $client = new Client(); $client->setId(123); $client->setName('My Client Name'); @@ -129,12 +163,26 @@ class SettingsControllerTest extends TestCase { ->method('deleteByClientId') ->with(123); $this->clientMapper + ->expects($this->once()) ->method('delete') ->with($client); - $result = $this->settingsController->deleteClient(123); + $settingsController = new SettingsController( + 'oauth2', + $this->request, + $this->clientMapper, + $this->secureRandom, + $this->accessTokenMapper, + $this->l, + $tokenProviderMock, + $userManager + ); + + $result = $settingsController->deleteClient(123); $this->assertInstanceOf(JSONResponse::class, $result); $this->assertEquals([], $result->getData()); + + $user1->delete(); } public function testInvalidRedirectUri() { diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 1ecd8152c0f..d21eb18cc5a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -96,6 +96,7 @@ return array( 'OCP\\Authentication\\IProvideUserSecretBackend' => $baseDir . '/lib/public/Authentication/IProvideUserSecretBackend.php', 'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php', 'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php', + 'OCP\\Authentication\\Token\\IProvider' => $baseDir . '/lib/public/Authentication/Token/IProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php', 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php', 'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2127835e514..b4f25f90d7c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -129,6 +129,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Authentication\\IProvideUserSecretBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IProvideUserSecretBackend.php', 'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php', 'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php', + 'OCP\\Authentication\\Token\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/Token/IProvider.php', 'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php', 'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php', 'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php', diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php index 59c7ca714c6..761e799d298 100644 --- a/lib/private/Authentication/Token/Manager.php +++ b/lib/private/Authentication/Token/Manager.php @@ -32,8 +32,9 @@ use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Exceptions\WipeTokenException; +use OCP\Authentication\Token\IProvider as OCPIProvider; -class Manager implements IProvider { +class Manager implements IProvider, OCPIProvider { /** @var PublicKeyTokenProvider */ private $publicKeyTokenProvider; @@ -239,4 +240,13 @@ class Manager implements IProvider { public function updatePasswords(string $uid, string $password) { $this->publicKeyTokenProvider->updatePasswords($uid, $password); } + + public function invalidateTokensOfUser(string $uid, ?string $clientName) { + $tokens = $this->getTokenByUser($uid); + foreach ($tokens as $token) { + if ($clientName === null || ($token->getName() === $clientName)) { + $this->invalidateTokenById($uid, $token->getId()); + } + } + } } diff --git a/lib/private/Server.php b/lib/private/Server.php index 9a4ee0da198..f1e96170886 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -163,6 +163,7 @@ use OCA\Theming\Util; use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\Authentication\LoginCredentials\IStore; +use OCP\Authentication\Token\IProvider as OCPIProvider; use OCP\BackgroundJob\IJobList; use OCP\Collaboration\AutoComplete\IManager; use OCP\Collaboration\Reference\IReferenceManager; @@ -550,6 +551,7 @@ class Server extends ServerContainer implements IServerContainer { }); $this->registerAlias(IStore::class, Store::class); $this->registerAlias(IProvider::class, Authentication\Token\Manager::class); + $this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class); $this->registerService(\OC\User\Session::class, function (Server $c) { $manager = $c->get(IUserManager::class); diff --git a/lib/public/Authentication/Token/IProvider.php b/lib/public/Authentication/Token/IProvider.php new file mode 100644 index 00000000000..da2e400eb79 --- /dev/null +++ b/lib/public/Authentication/Token/IProvider.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Artur Neumann <artur@jankaritech.com> + * + * @author Artur Neumann <artur@jankaritech.com> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCP\Authentication\Token; + +/** + * @since 24.0.8 + */ +interface IProvider { + /** + * invalidates all tokens of a specific user + * if a client name is given only tokens of that client will be invalidated + * + * @param string $uid + * @param string|null $clientName + * @since 24.0.8 + * @return void + */ + public function invalidateTokensOfUser(string $uid, ?string $clientName); +} diff --git a/tests/lib/Authentication/Token/ManagerTest.php b/tests/lib/Authentication/Token/ManagerTest.php index 5f024bb1d43..de3e5e1c362 100644 --- a/tests/lib/Authentication/Token/ManagerTest.php +++ b/tests/lib/Authentication/Token/ManagerTest.php @@ -355,4 +355,48 @@ class ManagerTest extends TestCase { $this->manager->updatePasswords('uid', 'pass'); } + + public function testInvalidateTokensOfUserNoClientName() { + $t1 = new PublicKeyToken(); + $t2 = new PublicKeyToken(); + $t1->setId(123); + $t2->setId(456); + + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('getTokenByUser') + ->with('theUser') + ->willReturn([$t1, $t2]); + $this->publicKeyTokenProvider + ->expects($this->exactly(2)) + ->method('invalidateTokenById') + ->withConsecutive( + ['theUser', 123], + ['theUser', 456], + ); + $this->manager->invalidateTokensOfUser('theUser', null); + } + + public function testInvalidateTokensOfUserClientNameGiven() { + $t1 = new PublicKeyToken(); + $t2 = new PublicKeyToken(); + $t3 = new PublicKeyToken(); + $t1->setId(123); + $t1->setName('Firefox session'); + $t2->setId(456); + $t2->setName('My Client Name'); + $t3->setId(789); + $t3->setName('mobile client'); + + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('getTokenByUser') + ->with('theUser') + ->willReturn([$t1, $t2, $t3]); + $this->publicKeyTokenProvider + ->expects($this->once()) + ->method('invalidateTokenById') + ->with('theUser', 456); + $this->manager->invalidateTokensOfUser('theUser', 'My Client Name'); + } } |