Skip to content

Commit 64c8cd1

Browse files
IONOS(provisioning-api): Add user tokens invalidation to core api
Signed-off-by: Misha M.-Kupriyanov <[email protected]>
1 parent d04deee commit 64c8cd1

File tree

10 files changed

+393
-0
lines changed

10 files changed

+393
-0
lines changed

apps/provisioning_api/appinfo/routes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
['root' => '/cloud', 'name' => 'Users#editUser', 'url' => '/users/{userId}', 'verb' => 'PUT'],
3939
['root' => '/cloud', 'name' => 'Users#editUserMultiValue', 'url' => '/users/{userId}/{collectionName}', 'verb' => 'PUT', 'requirements' => ['collectionName' => '^(?!enable$|disable$)[a-zA-Z0-9_]*$']],
4040
['root' => '/cloud', 'name' => 'Users#wipeUserDevices', 'url' => '/users/{userId}/wipe', 'verb' => 'POST'],
41+
['root' => '/cloud', 'name' => 'Users#invalidateUserTokens', 'url' => '/users/{userId}/invalidate', 'verb' => 'POST'],
4142
['root' => '/cloud', 'name' => 'Users#deleteUser', 'url' => '/users/{userId}', 'verb' => 'DELETE'],
4243
['root' => '/cloud', 'name' => 'Users#enableUser', 'url' => '/users/{userId}/enable', 'verb' => 'PUT'],
4344
['root' => '/cloud', 'name' => 'Users#disableUser', 'url' => '/users/{userId}/disable', 'verb' => 'PUT'],

apps/provisioning_api/lib/Controller/UsersController.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace OCA\Provisioning_API\Controller;
1212

1313
use InvalidArgumentException;
14+
use OC\Authentication\Token\Invalidator;
1415
use OC\Authentication\Token\RemoteWipe;
1516
use OC\KnownUser\KnownUserService;
1617
use OC\User\Backend;
@@ -71,6 +72,7 @@ public function __construct(
7172
private NewUserMailHelper $newUserMailHelper,
7273
private ISecureRandom $secureRandom,
7374
private RemoteWipe $remoteWipe,
75+
private Invalidator $invalidator,
7476
private KnownUserService $knownUserService,
7577
private IEventDispatcher $eventDispatcher,
7678
private IPhoneNumberUtil $phoneNumberUtil,
@@ -1250,6 +1252,51 @@ public function wipeUserDevices(string $userId): DataResponse {
12501252
return new DataResponse();
12511253
}
12521254

1255+
1256+
/**
1257+
* Invalidate all tokens of a user
1258+
*
1259+
* @param string $userId ID of the user
1260+
*
1261+
* @return DataResponse<Http::STATUS_OK, array<empty>, array{}>
1262+
*
1263+
* @throws OCSException
1264+
*
1265+
* 200: Invalidated all user tokens successfully
1266+
*/
1267+
#[NoAdminRequired]
1268+
public function invalidateUserTokens(string $userId): DataResponse {
1269+
/** @var IUser $currentLoggedInUser */
1270+
$currentLoggedInUser = $this->userSession->getUser();
1271+
1272+
$targetUser = $this->userManager->get($userId);
1273+
1274+
if ($targetUser === null) {
1275+
throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1276+
}
1277+
1278+
if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1279+
throw new OCSException('', 101);
1280+
}
1281+
1282+
// If not permitted
1283+
$subAdminManager = $this->groupManager->getSubAdmin();
1284+
$isAdmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID());
1285+
$isDelegatedAdmin = $this->groupManager->isDelegatedAdmin($currentLoggedInUser->getUID());
1286+
if (!$isAdmin && !($isDelegatedAdmin && !$this->groupManager->isInGroup($targetUser->getUID(), 'admin')) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1287+
throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1288+
}
1289+
1290+
$this->logger->warning('Invalidating all tokens for user ' . $userId . ' by user ' . $currentLoggedInUser->getUID(), [
1291+
'app' => 'ocs_api',
1292+
'adminUserId' => $currentLoggedInUser->getUID(),
1293+
'accountId' => $userId,
1294+
]);
1295+
1296+
$this->invalidator->invalidateAllUserTokens($targetUser->getUID());
1297+
1298+
return new DataResponse();
1299+
}
12531300
/**
12541301
* Delete a user
12551302
*

apps/provisioning_api/openapi-full.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,6 +3214,74 @@
32143214
}
32153215
}
32163216
},
3217+
"/ocs/v2.php/cloud/users/{userId}/invalidate": {
3218+
"post": {
3219+
"operationId": "users-invalidate-user-tokens",
3220+
"summary": "Invalidate all tokens of a user",
3221+
"tags": [
3222+
"users"
3223+
],
3224+
"security": [
3225+
{
3226+
"bearer_auth": []
3227+
},
3228+
{
3229+
"basic_auth": []
3230+
}
3231+
],
3232+
"parameters": [
3233+
{
3234+
"name": "userId",
3235+
"in": "path",
3236+
"description": "ID of the user",
3237+
"required": true,
3238+
"schema": {
3239+
"type": "string"
3240+
}
3241+
},
3242+
{
3243+
"name": "OCS-APIRequest",
3244+
"in": "header",
3245+
"description": "Required to be true for the API request to pass",
3246+
"required": true,
3247+
"schema": {
3248+
"type": "boolean",
3249+
"default": true
3250+
}
3251+
}
3252+
],
3253+
"responses": {
3254+
"200": {
3255+
"description": "Invalidated all user tokens successfully",
3256+
"content": {
3257+
"application/json": {
3258+
"schema": {
3259+
"type": "object",
3260+
"required": [
3261+
"ocs"
3262+
],
3263+
"properties": {
3264+
"ocs": {
3265+
"type": "object",
3266+
"required": [
3267+
"meta",
3268+
"data"
3269+
],
3270+
"properties": {
3271+
"meta": {
3272+
"$ref": "#/components/schemas/OCSMeta"
3273+
},
3274+
"data": {}
3275+
}
3276+
}
3277+
}
3278+
}
3279+
}
3280+
}
3281+
}
3282+
}
3283+
}
3284+
},
32173285
"/ocs/v2.php/cloud/users/{userId}/enable": {
32183286
"put": {
32193287
"operationId": "users-enable-user",

apps/provisioning_api/openapi.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,74 @@
21512151
}
21522152
}
21532153
},
2154+
"/ocs/v2.php/cloud/users/{userId}/invalidate": {
2155+
"post": {
2156+
"operationId": "users-invalidate-user-tokens",
2157+
"summary": "Invalidate all tokens of a user",
2158+
"tags": [
2159+
"users"
2160+
],
2161+
"security": [
2162+
{
2163+
"bearer_auth": []
2164+
},
2165+
{
2166+
"basic_auth": []
2167+
}
2168+
],
2169+
"parameters": [
2170+
{
2171+
"name": "userId",
2172+
"in": "path",
2173+
"description": "ID of the user",
2174+
"required": true,
2175+
"schema": {
2176+
"type": "string"
2177+
}
2178+
},
2179+
{
2180+
"name": "OCS-APIRequest",
2181+
"in": "header",
2182+
"description": "Required to be true for the API request to pass",
2183+
"required": true,
2184+
"schema": {
2185+
"type": "boolean",
2186+
"default": true
2187+
}
2188+
}
2189+
],
2190+
"responses": {
2191+
"200": {
2192+
"description": "Invalidated all user tokens successfully",
2193+
"content": {
2194+
"application/json": {
2195+
"schema": {
2196+
"type": "object",
2197+
"required": [
2198+
"ocs"
2199+
],
2200+
"properties": {
2201+
"ocs": {
2202+
"type": "object",
2203+
"required": [
2204+
"meta",
2205+
"data"
2206+
],
2207+
"properties": {
2208+
"meta": {
2209+
"$ref": "#/components/schemas/OCSMeta"
2210+
},
2211+
"data": {}
2212+
}
2213+
}
2214+
}
2215+
}
2216+
}
2217+
}
2218+
}
2219+
}
2220+
}
2221+
},
21542222
"/ocs/v2.php/cloud/users/{userId}/enable": {
21552223
"put": {
21562224
"operationId": "users-enable-user",

apps/provisioning_api/tests/Controller/UsersControllerTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace OCA\Provisioning_API\Tests\Controller;
1010

1111
use Exception;
12+
use OC\Authentication\Token\Invalidator;
1213
use OC\Authentication\Token\RemoteWipe;
1314
use OC\Group\Manager;
1415
use OC\KnownUser\KnownUserService;
@@ -73,6 +74,8 @@ class UsersControllerTest extends TestCase {
7374
/** @var RemoteWipe|MockObject */
7475
private $remoteWipe;
7576
/** @var KnownUserService|MockObject */
77+
private $invalidator;
78+
/** @var KnownUserService|MockObject */
7679
private $knownUserService;
7780
/** @var IEventDispatcher|MockObject */
7881
private $eventDispatcher;
@@ -95,6 +98,7 @@ protected function setUp(): void {
9598
$this->newUserMailHelper = $this->createMock(NewUserMailHelper::class);
9699
$this->secureRandom = $this->createMock(ISecureRandom::class);
97100
$this->remoteWipe = $this->createMock(RemoteWipe::class);
101+
$this->invalidator = $this->createMock(Invalidator::class);
98102
$this->knownUserService = $this->createMock(KnownUserService::class);
99103
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
100104
$this->phoneNumberUtil = new PhoneNumberUtil();
@@ -119,6 +123,7 @@ protected function setUp(): void {
119123
$this->newUserMailHelper,
120124
$this->secureRandom,
121125
$this->remoteWipe,
126+
$this->invalidator,
122127
$this->knownUserService,
123128
$this->eventDispatcher,
124129
$this->phoneNumberUtil,
@@ -513,6 +518,7 @@ public function testAddUserSuccessfulWithDisplayName() {
513518
$this->newUserMailHelper,
514519
$this->secureRandom,
515520
$this->remoteWipe,
521+
$this->invalidator,
516522
$this->knownUserService,
517523
$this->eventDispatcher,
518524
$this->phoneNumberUtil,
@@ -3133,6 +3139,34 @@ public function testRemoveFromGroupWithNoTargetGroup() {
31333139
$this->api->removeFromGroup('TargetUser', '');
31343140
}
31353141

3142+
public function testInvalidateUserTokensSuccess(): void {
3143+
$currentUser = $this->createMock(IUser::class);
3144+
$targetUser = $this->createMock(IUser::class);
3145+
3146+
$this->userSession->method('getUser')->willReturn($currentUser);
3147+
$currentUser->method('getUID')->willReturn('currentUserId');
3148+
$targetUser->method('getUID')->willReturn('targetUserId');
3149+
3150+
$this->userManager->method('get')->with('targetUserId')->willReturn($targetUser);
3151+
3152+
$this->groupManager->method('isAdmin')->with('currentUserId')->willReturn(true);
3153+
3154+
$this->invalidator->expects($this->once())->method('invalidateAllUserTokens')->with('targetUserId');
3155+
3156+
$this->logger
3157+
->expects($this->once())
3158+
->method('warning')
3159+
->with('Invalidating all tokens for user targetUserId by user currentUserId', [
3160+
'app' => 'ocs_api',
3161+
'adminUserId' => 'currentUserId',
3162+
'accountId' => 'targetUserId',
3163+
]);
3164+
3165+
$response = $this->api->invalidateUserTokens('targetUserId');
3166+
3167+
$this->assertInstanceOf(DataResponse::class, $response);
3168+
$this->assertEquals([], $response->getData());
3169+
}
31363170

31373171
public function testRemoveFromGroupWithEmptyTargetGroup() {
31383172
$this->expectException(\OCP\AppFramework\OCS\OCSException::class);
@@ -3785,6 +3819,7 @@ public function testGetCurrentUserLoggedIn() {
37853819
$this->newUserMailHelper,
37863820
$this->secureRandom,
37873821
$this->remoteWipe,
3822+
$this->invalidator,
37883823
$this->knownUserService,
37893824
$this->eventDispatcher,
37903825
$this->phoneNumberUtil,
@@ -3873,6 +3908,7 @@ public function testGetUser() {
38733908
$this->newUserMailHelper,
38743909
$this->secureRandom,
38753910
$this->remoteWipe,
3911+
$this->invalidator,
38763912
$this->knownUserService,
38773913
$this->eventDispatcher,
38783914
$this->phoneNumberUtil,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OC\Authentication\Events;
11+
12+
use OCP\EventDispatcher\Event;
13+
14+
abstract class AUserTokensInvalidationEvent extends Event {
15+
public function __construct(
16+
protected string $uid
17+
) {
18+
parent::__construct();
19+
}
20+
21+
public function getUserId(): string {
22+
return $this->uid;
23+
}
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OC\Authentication\Events;
10+
11+
class TokensInvalidationFinished extends AUserTokensInvalidationEvent {
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OC\Authentication\Events;
10+
11+
class TokensInvalidationStarted extends AUserTokensInvalidationEvent {
12+
}

0 commit comments

Comments
 (0)