Skip to content
Next Next commit
invalidate existing tokens when deleting an oauth client
Signed-off-by: Artur Neumann <[email protected]>
  • Loading branch information
individual-it committed May 19, 2023
commit 9f8f2d27b6861ecde0b479db2a9dd3ef0d395d67
28 changes: 26 additions & 2 deletions apps/oauth2/lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/
namespace OCA\OAuth2\Controller;

use OC\Authentication\Token\IProvider as IAuthTokenProvider;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\Client;
use OCA\OAuth2\Db\ClientMapper;
Expand All @@ -38,6 +39,8 @@
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 {
Expand All @@ -49,21 +52,30 @@ 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,
IRequest $request,
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,
Expand Down Expand Up @@ -92,6 +104,18 @@ public function addClient(string $name,

public function deleteClient(int $id): JSONResponse {
$client = $this->clientMapper->getByUid($id);

$this->userManager->callForAllUsers(function (IUser $user) use ($client) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be run for known users, not all users, and not in user facing requests as it may take ages

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same thing was merged in master and stable24 already :-/
Why would it take ages?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it will ask all connected backends to all users. not an issue on small local instance, but a factor on big setups.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blizzz do you mean using callForSeenUsers() instead of callForAllUsers()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blizzz do you mean using callForSeenUsers() instead of callForAllUsers()?

yes, and ideally it runs through background jobs only

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blizzz I've changed it to callForSeenUsers() in e25b640

But I think I would not put it into background jobs, because as admin I would expect the connections to be deleted immediately after I delete the oauth client and not only after a cron job eventually runs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the instance size it may cycle over x thousands of users.

$tokens = $this->tokenProvider->getTokenByUser($user->getUID());
foreach ($tokens as $token) {
if ($token->getName() === $client->getName()) {
$this->tokenProvider->invalidateTokenById(
$user->getUID(), $token->getId()
);
}
}
});

$this->accessTokenMapper->deleteByClientId($id);
$this->clientMapper->delete($client);
return new JSONResponse([]);
Expand Down
62 changes: 57 additions & 5 deletions apps/oauth2/tests/Controller/SettingsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*/
namespace OCA\OAuth2\Tests\Controller;

use OC\Authentication\Token\IToken;
use OC\Authentication\Token\IProvider as IAuthTokenProvider;
use OCA\OAuth2\Controller\SettingsController;
use OCA\OAuth2\Db\AccessTokenMapper;
use OCA\OAuth2\Db\Client;
Expand All @@ -34,9 +36,14 @@
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;
Expand All @@ -48,6 +55,8 @@ class SettingsControllerTest extends TestCase {
private $accessTokenMapper;
/** @var SettingsController */
private $settingsController;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
private $l;

protected function setUp(): void {
parent::setUp();
Expand All @@ -56,18 +65,20 @@ protected function setUp(): void {
$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->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->createMock(IAuthTokenProvider::class),
$this->createMock(IUserManager::class)
);

}

public function testAddClient() {
Expand Down Expand Up @@ -113,6 +124,34 @@ public function testAddClient() {
}

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');
$tokenMocks[0] = $this->getMockBuilder(IToken::class)->getMock();
$tokenMocks[0]->method('getName')->willReturn('Firefox session');
$tokenMocks[0]->method('getId')->willReturn(1);
$tokenMocks[1] = $this->getMockBuilder(IToken::class)->getMock();
$tokenMocks[1]->method('getName')->willReturn('My Client Name');
$tokenMocks[1]->method('getId')->willReturn(2);
$tokenMocks[2] = $this->getMockBuilder(IToken::class)->getMock();
$tokenMocks[2]->method('getName')->willReturn('mobile client');
$tokenMocks[2]->method('getId')->willReturn(3);

$tokenProviderMock = $this->getMockBuilder(IAuthTokenProvider::class)->getMock();
$tokenProviderMock->method('getTokenByUser')->willReturn($tokenMocks);

// expect one call per user and make sure the correct tokeId is selected
$tokenProviderMock
->expects($this->exactly($count + 1))
->method('invalidateTokenById')
->with($this->isType('string'), 2);

$client = new Client();
$client->setId(123);
$client->setName('My Client Name');
Expand All @@ -132,9 +171,22 @@ public function testDeleteClient() {
->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() {
Expand Down